Corrigindo "Text file busy"
O que significa "Text file busy"?
Conclusão: Você está tentando sobrescrever ou truncar o arquivo que sustenta um programa atualmente em execução (ou uma biblioteca compartilhada em uso). O kernel retorna
ETXTBSY(errno 26). Pare o processo em execução, ou mude de "sobrescrever" para "substituir" (rename) e o erro desaparece.
Geralmente aparece ao copiar, compilar ou fazer deploy de um binário:
cp: cannot create regular file '/usr/local/bin/myapp': Text file busy
O "text" em Text file busy é o antigo termo Unix para o segmento de texto de um programa (seu código executável). Não tem nada a ver com o arquivo conter texto legível -- significa "o arquivo que contém código executável está em uso".
O kernel retorna esse erro principalmente para estas operações:
- Abrir um binário em execução para escrita (sobrescrever com
cp, redirecionar com> file) - Sobrescrever uma biblioteca compartilhada (
.so) em uso (já mapeada na memória com mmap) - Tentar
execve()em um arquivo que ainda está aberto para escrita (uma condição de corrida no build)
Text file busy não é o mesmo que Permission denied ou Read-only file system. As permissões e o sistema de arquivos podem estar perfeitamente corretos -- a escrita é rejeitada unicamente porque o arquivo está sendo executado. Interpretar o erro errado leva à correção errada.
Por que um binário em execução não pode ser sobrescrito?
Conclusão: O Linux protege o inode de um executável em execução (e qualquer biblioteca compartilhada carregada) negando escritas. Modificar código em execução no meio do caminho causaria crashes ou comportamento indefinido, então o kernel bloqueia antecipadamente com
ETXTBSY.
Quando um programa inicia, o kernel mapeia o executável na memória e o executa. Páginas são carregadas sob demanda, então o arquivo no disco continua sendo referenciado durante toda a vida do processo.
Se alguém reescreve o arquivo agora, uma página ainda não carregada pode mudar para outra coisa e quebrar a consistência. Para prevenir isso, o kernel marca o inode como "sendo executado" e rejeita aberturas para escrita (O_WRONLY / O_RDWR) e truncamento com ETXTBSY. Bibliotecas compartilhadas contêm código executável também, então recebem a mesma proteção.
A proteção funciona em ambas as direções. Se você abrir um arquivo para escrita e depois tentar execve() nesse mesmo arquivo, você também recebe ETXTBSY. Um script de build que esquece de fechar seu arquivo de saída antes de executá-lo encontra exatamente esse caso.
Note que shell scripts geralmente não disparam esse erro. Um script é apenas lido como dados por um interpretador como o bash; ele nunca se torna uma imagem executável protegida. ETXTBSY afeta principalmente binários ELF e bibliotecas compartilhadas.
Como encontrar o processo que segura o arquivo?
Conclusão: Use
lsof <arquivo>oufuser <arquivo>para listar o processo executando aquele binário. O PID que ele exibe é o que você precisa parar. Se ele é gerenciado por um serviço, pare comsystemctlem vez de matá-lo diretamente.
Inspecionar por arquivo com lsof
Passe o caminho que você quer sobrescrever para o lsof, e ele mostra qual processo tem o arquivo aberto ou em execução.
lsof /usr/local/bin/myapp
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME myapp 4821 deploy txt REG 259,1 6291456 131080 /usr/local/bin/myapp
O txt na coluna FD significa "em uso como texto executável (código)". Aquele myapp (PID 4821) é o culpado. Para uma biblioteca compartilhada ele aparece como mem (mmap'd).
Obter o PID rapidamente com fuser
Se você quer apenas o PID rapidamente, use fuser.
fuser /usr/local/bin/myapp
/usr/local/bin/myapp: 4821e
O e no final significa executável sendo executado. Adicione fuser -v para ver também USER / COMMAND.
Se quem segura o arquivo é um serviço ou daemon de longa duração, matá-lo diretamente com kill pode disparar um auto-restart ou deixá-lo em um estado de atualização parcial. Pare serviços com systemctl stop e só considere kill para processos iniciados manualmente.
Como substituir com segurança em vez de sobrescrever?
Conclusão: Se você não pode parar o processo em execução, pare de usar
cppara sobrescrever e usemv(rename) ouinstallpara trocar por um novo arquivo. Rename cria um novo inode e re-aponta a entrada do diretório, então o processo em execução mantém seu inode antigo e nunca colide com o novo binário.
Por que mv funciona mas cp falha
cp new /usr/local/bin/myapp-- abre o inode existente comO_TRUNCe reescreve no lugar ->ETXTBSYporque está em execuçãomv new /usr/local/bin/myapp-- renomeia um novo inode paramyappe substitui o nome antigo -> nunca toca o inode em execução, então funciona
O ponto chave é manter dentro do mesmo sistema de arquivos. Executar mv de /tmp (um sistema de arquivos diferente) se transforma em cópia-mais-exclusão internamente e pode tomar o caminho de sobrescrita. O padrão confiável é colocar o novo arquivo no mesmo diretório, depois renomear.
# Baixar/compilar no mesmo diretorio, depois renomear cp myapp.new /usr/local/bin/myapp.new # preparar sob um nome temporario primeiro mv /usr/local/bin/myapp.new /usr/local/bin/myapp # troca atomica
O processo em execução continua executando o binário antigo (desapareceu do diretório, mas seu inode ainda está vivo) e pega o novo na próxima inicialização.
install é ainda mais limpo
install remove o arquivo existente e cria um novo (ele nunca sobrescreve o inode em execução no lugar), então evita ETXTBSY enquanto define permissões e propriedade de uma vez -- ideal para deploy.
sudo install -m 0755 -o root -g root myapp.new /usr/local/bin/myapp
Se você precisa escrever no mesmo inode, remova-o primeiro
Exclua a entrada do diretório com rm, depois escreva -- torna-se uma criação nova e evita ETXTBSY (unlink em si é permitido mesmo durante a execução).
sudo rm /usr/local/bin/myapp # unlink funciona mesmo durante a execucao sudo cp myapp.new /usr/local/bin/myapp
A opção mais confiável é "pare o processo, depois sobrescreva": para um serviço, systemctl stop myapp && cp ... && systemctl start myapp. Use a abordagem rename/install quando precisar trocar sem downtime -- essa divisão torna a decisão rápida.
Como evitar que se repita em deploys e builds?
Conclusão: Faça os deploys como "substituição atômica via rename", não "sobrescrever". Em builds, feche confiavelmente o file descriptor de saída e nunca escreva diretamente no binário que está em execução no momento.
Pontos de verificação para prevenir recorrência:
- Elimine escritas diretas com
cp -fem scripts de deploy -- prepare sob um nome temporário, depois troque commv/install. Muitas ferramentas de deploy (substituindo a saída de umgo build, etc.) já funcionam assim - Pare, atualize, reinicie serviços em execução por padrão -- se você precisa de atualizações sem downtime, use a abordagem rename, ou considere o
ExecReloaddo systemd / socket activation - Feche o fd de saída antes de executar um novo build -- encadear
make && ./outfalha comETXTBSYse o build deixou um handle de escrita aberto. Feche explicitamente ou divida em etapas separadas - Trate atualizações de bibliotecas compartilhadas da mesma forma -- nunca sobrescreva um
.soem uso; substitua com rename. Gerenciadores de pacotes (apt/dnf) fazem isso internamente, então evitecpmanual sobre um.so
Em alguns sistemas de arquivos de rede como NFS, o comportamento do ETXTBSY nem sempre é consistente para um binário executando em um host diferente. Ao atualizar um executável em armazenamento compartilhado, o caminho seguro é parar o processo em cada host antes de fazer a troca.