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()
- Criação de temporário seguro: o utilitário chama
mkstemp()para gerar um arquivo temporário exclusivo em/tmp/crontab.XXXXXX. - Atribuição de dono: com
chown(), ele garante que o usuário executor seja o dono do arquivo temporário. - Invocação do editor: via
execve(), ele abre o editor definido em$VISUALou$EDITOR(como nano ou vim). - Validação sintática: ao fechar o editor, o binário lê o arquivo e verifica a estrutura dos campos de tempo e comando.
- Substituição atômica: se a sintaxe estiver correta, o arquivo temporário é movido para
/var/spool/cron/rootcom a chamadarename(). No VFS do Linux,rename()é uma operação atômica: o daemon nunca lê um arquivo parcialmente gravado. - 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, executastat()no diretório/var/spool/cron/. Se ost_mtimefor mais recente que o carimbo guardado na memória, ele relê os arquivos. - Abordagem moderna com
inotify: em kernels recentes, daemons comocronieusam a APIinotify_init1()e configuram um relógio (inotify_add_watch()) no diretório de spool para capturar eventosIN_MODIFYeIN_MOVED_TO. Quando orename()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ística | Família RHEL / AlmaLinux | Família Debian / Ubuntu |
|---|---|---|
| Pacote do daemon | cronie | cron |
| Destino de log padrão | /var/log/cron | /var/log/syslog |
| Mecanismo primário | rsyslog com seletor dedicado | systemd-journald e rsyslog |
| Nome da unidade systemd | crond.service | cron.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óriodb/.retry: nome do banco de dicas (hints database) que será limpo. O Exim também mantém bases comowait-remote_smtp,callouteratelimit.
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):
- Ele grava uma chave com o destino (exemplo:
H:destino.com:192.0.2.1). - 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).
- 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:
- 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 viarename(). - 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 detidydbnão está rodando. - Prefira
kill -HUPasystemctl restartno cron: quando precisar recarregar tabelas do cron em servidores com jobs em andamento, o sinal HUP é muito mais seguro do que reiniciar o daemon. - Use
journalctl -uem distribuições modernas: abandone caminhos legados como/var/log/cronem 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:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários