Corrigindo "Text file busy"
O que significa "Text file busy"?
Conclusao: Voce esta tentando sobrescrever ou truncar o arquivo que sustenta um programa atualmente em execucao (ou uma biblioteca compartilhada em uso). O kernel retorna
ETXTBSY(errno 26). Pare o processo em execucao, ou mude de "sobrescrever" para "substituir" (rename) e o erro desaparece.
Geralmente aparece ao copiar, compilar ou fazer deploy de um binario:
cp: cannot create regular file '/usr/local/bin/myapp': Text file busy
O "text" em Text file busy e o antigo termo Unix para o segmento de texto de um programa (seu codigo executavel). Nao tem nada a ver com o arquivo conter texto legivel -- significa "o arquivo que contem codigo executavel esta em uso".
O kernel retorna esse erro principalmente para estas operacoes:
- Abrir um binario em execucao para escrita (sobrescrever com
cp, redirecionar com> file) - Sobrescrever uma biblioteca compartilhada (
.so) em uso (ja mapeada na memoria com mmap) - Tentar
execve()em um arquivo que ainda esta aberto para escrita (uma condicao de corrida no build)
Text file busy nao e o mesmo que Permission denied ou Read-only file system. As permissoes e o sistema de arquivos podem estar perfeitamente corretos -- a escrita e rejeitada unicamente porque o arquivo esta sendo executado. Interpretar o erro errado leva a correcao errada.
Por que um binario em execucao nao pode ser sobrescrito?
Conclusao: O Linux protege o inode de um executavel em execucao (e qualquer biblioteca compartilhada carregada) negando escritas. Modificar codigo em execucao no meio do caminho causaria crashes ou comportamento indefinido, entao o kernel bloqueia antecipadamente com
ETXTBSY.
Quando um programa inicia, o kernel mapeia o executavel na memoria e o executa. Paginas sao carregadas sob demanda, entao o arquivo no disco continua sendo referenciado durante toda a vida do processo.
Se alguem reescreve o arquivo agora, uma pagina ainda nao carregada pode mudar para outra coisa e quebrar a consistencia. 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 contem codigo executavel tambem, entao recebem a mesma protecao.
A protecao funciona em ambas as direcoes. Se voce abrir um arquivo para escrita e depois tentar execve() nesse mesmo arquivo, voce tambem recebe ETXTBSY. Um script de build que esquece de fechar seu arquivo de saida antes de executa-lo encontra exatamente esse caso.
Note que shell scripts geralmente nao disparam esse erro. Um script e apenas lido como dados por um interpretador como o bash; ele nunca se torna uma imagem executavel protegida. ETXTBSY afeta principalmente binarios ELF e bibliotecas compartilhadas.
Como encontrar o processo que segura o arquivo?
Conclusao: Use
lsof <arquivo>oufuser <arquivo>para listar o processo executando aquele binario. O PID que ele exibe e o que voce precisa parar. Se ele e gerenciado por um servico, pare comsystemctlem vez de mata-lo diretamente.
Inspecionar por arquivo com lsof
Passe o caminho que voce quer sobrescrever para o lsof, e ele mostra qual processo tem o arquivo aberto ou em execucao.
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 executavel (codigo)". Aquele myapp (PID 4821) e o culpado. Para uma biblioteca compartilhada ele aparece como mem (mmap'd).
Obter o PID rapidamente com fuser
Se voce quer apenas o PID rapidamente, use fuser.
fuser /usr/local/bin/myapp
/usr/local/bin/myapp: 4821e
O e no final significa executavel sendo executado. Adicione fuser -v para ver tambem USER / COMMAND.
Se quem segura o arquivo e um servico ou daemon de longa duracao, mata-lo diretamente com kill pode disparar um auto-restart ou deixa-lo em um estado de atualizacao parcial. Pare servicos com systemctl stop e so considere kill para processos iniciados manualmente.
Como substituir com seguranca em vez de sobrescrever?
Conclusao: Se voce nao pode parar o processo em execucao, 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 diretorio, entao o processo em execucao mantem seu inode antigo e nunca colide com o novo binario.
Por que mv funciona mas cp falha
cp new /usr/local/bin/myapp-- abre o inode existente comO_TRUNCe reescreve no lugar ->ETXTBSYporque esta em execucaomv new /usr/local/bin/myapp-- renomeia um novo inode paramyappe substitui o nome antigo -> nunca toca o inode em execucao, entao funciona
O ponto chave e manter dentro do mesmo sistema de arquivos. Executar mv de /tmp (um sistema de arquivos diferente) se transforma em copia-mais-exclusao internamente e pode tomar o caminho de sobrescrita. O padrao confiavel e colocar o novo arquivo no mesmo diretorio, 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 execucao continua executando o binario antigo (desapareceu do diretorio, mas seu inode ainda esta vivo) e pega o novo na proxima inicializacao.
install e ainda mais limpo
install remove o arquivo existente e cria um novo (ele nunca sobrescreve o inode em execucao no lugar), entao evita ETXTBSY enquanto define permissoes e propriedade de uma vez -- ideal para deploy.
sudo install -m 0755 -o root -g root myapp.new /usr/local/bin/myapp
Se voce precisa escrever no mesmo inode, remova-o primeiro
Exclua a entrada do diretorio com rm, depois escreva -- torna-se uma criacao nova e evita ETXTBSY (unlink em si e permitido mesmo durante a execucao).
sudo rm /usr/local/bin/myapp # unlink funciona mesmo durante a execucao sudo cp myapp.new /usr/local/bin/myapp
A opcao mais confiavel e "pare o processo, depois sobrescreva": para um servico, systemctl stop myapp && cp ... && systemctl start myapp. Use a abordagem rename/install quando precisar trocar sem downtime -- essa divisao torna a decisao rapida.
Como evitar que se repita em deploys e builds?
Conclusao: Faca os deploys como "substituicao atomica via rename", nao "sobrescrever". Em builds, feche confiavelmente o file descriptor de saida e nunca escreva diretamente no binario que esta em execucao no momento.
Pontos de verificacao para prevenir recorrencia:
- Elimine escritas diretas com
cp -fem scripts de deploy -- prepare sob um nome temporario, depois troque commv/install. Muitas ferramentas de deploy (substituindo a saida de umgo build, etc.) ja funcionam assim - Pare, atualize, reinicie servicos em execucao por padrao -- se voce precisa de atualizacoes sem downtime, use a abordagem rename, ou considere o
ExecReloaddo systemd / socket activation - Feche o fd de saida 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 atualizacoes de bibliotecas compartilhadas da mesma forma -- nunca sobrescreva um
.soem uso; substitua com rename. Gerenciadores de pacotes (apt/dnf) fazem isso internamente, entao evitecpmanual sobre um.so
Em alguns sistemas de arquivos de rede como NFS, o comportamento do ETXTBSY nem sempre e consistente para um binario executando em um host diferente. Ao atualizar um executavel em armazenamento compartilhado, o caminho seguro e parar o processo em cada host antes de fazer a troca.