Percio Castelo.
EN Início
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 · E-mail · 723 visualizações

Ao alterar uma tarefa agendada no Linux ou lidar 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.

Este artigo analisa 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:

  • Abordagem clássica por consulta (stat()): o daemon roda em loop e, periodicamente, executa stat() no diretório /var/spool/cron/. Se o st_mtime for mais recente que o carimbo guardado na memória, ele relê os arquivos.
  • Abordagem moderna com inotify: em kernels recentes, daemons como cronie usam a API inotify_init1() e configuram um relógio (inotify_add_watch()) no diretório de spool para capturar eventos IN_MODIFY e IN_MOVED_TO. Quando o rename() ocorre, o kernel envia o evento diretamente para o descritor de arquivo do daemon, que atualiza a tabela imediatamente.

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:

  • O Vim, por padrão, salva criando um novo arquivo e renomeando sobre o original, gerando um novo inode.
  • Se o daemon estiver monitorando o inode antigo em vez do diretório, ele perderá o rastro do arquivo até que o serviço seja reiniciado ou ocorra uma checagem de salvaguarda.

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
  • -t 1d: define a idade máxima de corte (1 dia, equivalente a 86.400 segundos). Registros mais antigos que esse intervalo são removidos.
  • /var/spool/exim: diretório raiz do spool. O utilitário procura automaticamente o subdiretório db/.
  • retry: nome do banco de dicas (hints database) que será limpo. O Exim também mantém bases como wait-remote_smtp, callout e ratelimit.

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:

723 leituras
Creative Commons Atribuição-NãoComercial

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários