Anatomia do crontab, comportamento do kernel e manutenção do spool de e-mails no Exim
Voltar para blog

Anatomia do crontab, comportamento do kernel e manutenção do spool de e-mails no Exim

10/10/2026 · 7 min · Infraestrutura

Quando alteramos uma tarefa agendada no Linux ou lidamos com filas travadas de e-mail, é fácil tratar comandos como caixas-pretas. No entanto, entender o que acontece por baixo dos panos na camada do kernel e no sistema de arquivos evita dores de cabeça com scripts que parecem não rodar ou mensagens retidas sem motivo aparente.

Neste artigo, vamos analisar três pontos práticos da rotina em servidores Linux: o ciclo de vida do comando crontab -e e suas chamadas de sistema (syscalls), por que os logs do cron somem em migrações entre distribuições e como o Exim gerencia seu banco de dados de retentativas (retry.db) com o utilitário exim_tidydb.


1. O ciclo de vida do crontab e a camada do kernel#

Ao editar as tarefas agendadas do usuário com crontab -e, surge uma dúvida clássica: é necessário reiniciar o serviço do cron para que as alterações entrem em vigor?

A resposta é não. Mas entender a razão técnica exige acompanhar como o comando manipula o sistema de arquivos virtual (VFS).

O fluxo de execução do binário crontab -e#

O executável /usr/bin/crontab possui o bit SUID ativo para conseguir gravar em diretórios restritos do sistema. Ao chamá-lo, ele não edita o arquivo final de spool diretamente:

[crontab -e] ──> mkstemp() em /tmp ──> [Editor] ──> Sintaxe válida?
                                                        │
  ┌─────────────────────────────────────────────────────┘
  ▼
rename("/tmp/crontab.XXXXXX", "/var/spool/cron/root") ──> utimensat()
  1. Criação de temporário seguro: o utilitário chama mkstemp() para gerar um arquivo temporário exclusivo em /tmp/crontab.XXXXXX.
  2. Atribuição de dono: com chown(), ele garante que o usuário executor seja o dono do arquivo temporário.
  3. Invocação do editor: via execve(), ele abre o editor definido em $VISUAL ou $EDITOR (como nano ou vim).
  4. Validação sintática: ao fechar o editor, o binário lê o arquivo e verifica a estrutura dos campos de tempo e comando.
  5. Substituição atômica: se a sintaxe estiver correta, o arquivo temporário é movido para /var/spool/cron/root com a chamada rename(). No VFS do Linux, rename() é uma operação atômica: o daemon nunca lê um arquivo parcialmente gravado.
  6. Atualização de carimbos de tempo: a substituição altera o mtime (tempo de modificação) do diretório pai /var/spool/cron/.

Como o daemon crond detecta as mudanças#

O daemon do cron monitora o sistema de arquivos para saber quando recarregar suas tabelas em memória:


2. Cenários de borda na edição do crontab#

Existem situações em que o cron deixa de executar jobs recém-adicionados por detalhes de permissão ou método de edição:

Cenário a: edição direta com editores de texto#

Se você rodar vim /var/spool/cron/root diretamente em vez de usar crontab -e:

Para forçar a atualização imediata dos carimbos sem reiniciar o serviço:

touch /var/spool/cron/root

Cenário b: permissões inseguras ignoradas pelo daemon#

O código-fonte do cron (função load_database()) descarta silenciosamente arquivos com permissões abertas demais por segurança. O arquivo do root deve pertencer exclusivamente ao usuário root e ter permissões restritas:

# Verificar permissões e dono
stat -c "%a %U %G" /var/spool/cron/root

# Corrigir caso esteja diferente de 600 root root
chown root:root /var/spool/cron/root && chmod 600 /var/spool/cron/root

3. Rastreamento prático com procfs e strace#

Se você precisa comprovar se o daemon do cron realmente recarregou as tabelas sem reiniciar o serviço, o Linux oferece ferramentas nativas.

Verificando instâncias de inotify via /proc#

# Identificar o PID do processo
PID=$(pgrep -x crond || pgrep -x cron)

# Inspecionar os descritores de inotify abertos
cat /proc/$PID/fdinfo/* 2>/dev/null | grep -E "inotify" -A 3

Rastreando chamadas de leitura em tempo real com strace#

Abra dois terminais. No primeiro, intercepte as chamadas de sistema do daemon:

strace -p $(pgrep -x crond || pgrep -x cron) -e trace=stat,fstat,openat,read,fstatat 2>&1 | grep -E "root|cron"

No segundo terminal, edite e salve o crontab com crontab -e. No terminal do strace, você verá a leitura imediata:

openat(AT_FDCWD, "/var/spool/cron/root", O_RDONLY) = 5
fstat(5, {st_mode=S_IFREG|0600, st_size=1240, ...}) = 0
read(5, "# Minuto Hora Dia ...", 4096) = 1240
close(5) = 0

4. Divergências de logs do cron entre distribuições#

Em migrações de sistemas legados (CentOS/RHEL) para ambientes baseados em Debian/Ubuntu, é comum procurar /var/log/cron e encontrar No such file or directory. Isso não significa falha no serviço, mas sim uma diferença de arquitetura de logs:

CaracterísticaFamília RHEL / AlmaLinuxFamília Debian / Ubuntu
Pacote do daemoncroniecron
Destino de log padrão/var/log/cron/var/log/syslog
Mecanismo primáriorsyslog com seletor dedicadosystemd-journald e rsyslog
Nome da unidade systemdcrond.servicecron.service

Comandos para acompanhar a execução em cada família#

No Debian ou Ubuntu:

# Via syslog tradicional
tail -f /var/log/syslog | grep -E -i "cron|RELOAD"

# Via journal binário do systemd
journalctl -u cron -f -n 50

No RHEL, AlmaLinux ou CloudLinux:

# Via log dedicado
tail -f /var/log/cron | grep -i "RELOAD"

# Via journal binário do systemd
journalctl -u crond -f -n 50

Recarregamento seguro com sinal SIGHUP#

Se você precisa forçar o cron a reler todas as tabelas imediatamente sem interromper scripts longos em execução, use o sinal SIGHUP em vez de reiniciar o serviço:

kill -HUP $(pgrep -x crond || pgrep -x cron)

O comando systemctl restart envia SIGTERM e pode abortar processos filhos no meio da execução. O SIGHUP apenas instrui o processo-pai a reler o diretório de spool, preservando os jobs em andamento.


5. A mecânica do spool do Exim e o utilitário exim_tidydb#

Em servidores de hospedagem com cPanel ou Exim dedicado, o correio eletrônico utiliza bancos de dados embarcados de chave-valor (historicamente Berkeley DB ou TDB) no diretório /var/spool/exim/db/ para tomar decisões rápidas de roteamento sem precisar reabrir logs textuais.

O principal desses bancos é o arquivo retry.

O que o comando exim_tidydb faz#

Para evitar que o banco de retentativas cresça indefinidamente, o Exim oferece o binário /usr/sbin/exim_tidydb:

/usr/sbin/exim_tidydb -t 1d /var/spool/exim retry

Como o banco retry impacta o envio de e-mails#

Quando o Exim tenta entregar uma mensagem e recebe uma falha temporária (código SMTP 4xx, como greylisting ou limite de taxa):

  1. Ele grava uma chave com o destino (exemplo: H:destino.com:192.0.2.1).
  2. O registro armazena o código de erro, a hora da primeira falha e a hora da próxima tentativa permitida (next try time), calculada pelo algoritmo de recuo exponencial (exponential backoff).
  3. Bloqueio preventivo: se novas mensagens para aquele mesmo domínio entrarem na fila antes do tempo de carência expirar, o Exim nem tenta abrir conexão TCP. Ele marca o e-mail diretamente como adiado (deferred).

Se o servidor de destino ficou indisponível por algumas horas e depois voltou a funcionar, o seu Exim continuará segurando mensagens até que os tempos de espera individuais expirem. Ao rodar exim_tidydb, os registros expirados são expurgados, permitindo novas tentativas de entrega imediatas.


6. Recuperação em caso de corrupção ou travamento do banco#

Corrupção da base de dados (bad magic number)#

Quedas de energia ou disco cheio podem corromper os índices do arquivo /var/spool/exim/db/retry:

exim_tidydb: DBM error on /var/spool/exim/db/retry: internal corruption / bad magic number

Como o banco retry guarda apenas registros de estado temporário (e não os e-mails em si), ele pode ser removido sem perda de mensagens. O Exim cria um arquivo novo e limpo na próxima tentativa de envio:

# 1. Parar o serviço temporariamente
systemctl stop exim

# 2. Remover o banco e seus arquivos de lock associados
rm -fv /var/spool/exim/db/retry*

# 3. Iniciar o serviço novamente
systemctl start exim

Contenção por lock exclusivo de arquivo#

O Exim utiliza travas de arquivo (flock() ou fcntl()) para evitar que dois processos alterem o banco ao mesmo tempo. Se um processo de entrega ficar preso em estado ininterrupto (D-state por lentidão de disco), o exim_tidydb ficará travado esperando o lock.

Para identificar e liberar:

# Listar processos segurando o arquivo
lsof /var/spool/exim/db/retry

# Se houver processo travado, inspecione e encerre
ps aux | grep exim
kill -9 <PID_DO_PROCESSO_TRAVADO>

7. Script de automação para manutenção preventiva do Exim#

Para automatizar a limpeza periódica do banco de retentativas com teste prévio de integridade, utilize o script abaixo:

#!/bin/bash
# ==============================================================================
# Manutenção preventiva do banco de retentativas do Exim
# ==============================================================================

set -o pipefail

EXIM_BINARY="/usr/sbin/exim"
TIDYDB_BINARY="/usr/sbin/exim_tidydb"
DUMPDB_BINARY="/usr/sbin/exim_dumpdb"
SPOOL_DIR="/var/spool/exim"
DB_TARGET="retry"
THRESHOLD="1d"

log_msg() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$1] $2"
}

if [[ $EUID -ne 0 ]]; then
   log_msg "ERRO" "Execute este script como root."
   exit 1
fi

if [[ ! -f "$TIDYDB_BINARY" ]]; then
    log_msg "ERRO" "Binário exim_tidydb não encontrado em $TIDYDB_BINARY"
    exit 1
fi

log_msg "INFO" "Testando integridade da base '$DB_TARGET'..."

# Teste prévio com exim_dumpdb
$DUMPDB_BINARY $SPOOL_DIR $DB_TARGET > /dev/null 2>&1
if [ $? -ne 0 ]; then
    log_msg "AVISO" "Base '$DB_TARGET' corrompida. Recriando estrutura..."
    systemctl stop exim
    rm -fv $SPOOL_DIR/db/${DB_TARGET}*
    systemctl start exim
    log_msg "OK" "Base recriada com sucesso."
    exit 0
fi

log_msg "INFO" "Limpando registros com mais de $THRESHOLD..."

if command -v ionice >/dev/null 2>&1; then
    ionice -c 3 $TIDYDB_BINARY -t $THRESHOLD $SPOOL_DIR $DB_TARGET
else
    $TIDYDB_BINARY -t $THRESHOLD $SPOOL_DIR $DB_TARGET
fi

if [ $? -eq 0 ]; then
    log_msg "OK" "Limpeza concluída com sucesso."
else
    log_msg "ERRO" "Falha na execução do exim_tidydb."
    exit 2
fi

exit 0

Boas práticas operacionais para manter tarefas agendadas e filas saudáveis#

Para manter o ambiente estável e previsível:

  1. Sempre use crontab -e: evite editar arquivos de spool diretamente com editores de texto. O utilitário oficial garante validação de sintaxe prévia e gravação atômica via rename().
  2. Monitore o crescimento de /var/spool/exim/db/: bancos de dicas com dezenas de megabytes indicam que o servidor está lidando com muitas rejeições externas ou que a rotina de tidydb não está rodando.
  3. Prefira kill -HUP a systemctl restart no cron: quando precisar recarregar tabelas do cron em servidores com jobs em andamento, o sinal HUP é muito mais seguro do que reiniciar o daemon.
  4. Use journalctl -u em distribuições modernas: abandone caminhos legados como /var/log/cron em distribuições Debian e Ubuntu; o journal estruturado é mais rápido e traz metadados completos de execução.

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários