Substituindo arquivos em geral
Primeiro, existem várias estratégias para substituir um arquivo:
Abra o arquivo existente para gravação, trunque-o no tamanho 0 e escreva o novo conteúdo. (Uma variante menos comum é abrir o arquivo existente, substituir o conteúdo antigo pelo novo, truncar o arquivo para o novo tamanho, se for menor.) Em termos de shell:
echo 'new content' >somefile
Remova o arquivo antigo e crie um novo arquivo com o mesmo nome. Em termos de shell:
rm somefile
echo 'new content' >somefile
Escreva em um novo arquivo com um nome temporário e mova o novo arquivo para o nome existente. A movimentação exclui o arquivo antigo. Em termos de shell:
echo 'new content' >somefile.new
mv somefile.new somefile
Não vou listar todas as diferenças entre as estratégias, apenas mencionarei algumas que são importantes aqui. Com a estratégia 1, se algum processo estiver usando o arquivo no momento, o processo verá o novo conteúdo enquanto ele está sendo atualizado. Isso pode causar alguma confusão se o processo esperar que o conteúdo do arquivo permaneça o mesmo. Observe que isso é apenas sobre processos que têm o arquivo aberto (como visível em lsof
ou em ; aplicativos interativos que têm um documento aberto (por exemplo, abrir um arquivo em um editor) geralmente não mantêm o arquivo aberto, eles carregam o conteúdo do arquivo durante o processo. Operação "abrir documento" e eles substituem o arquivo (usando uma das estratégias acima) durante a operação "salvar documento"./proc/PID/fd/
Nas estratégias 2 e 3, se algum processo tiver o arquivo somefile
aberto, o arquivo antigo permanecerá aberto durante a atualização do conteúdo. Com a estratégia 2, a etapa de remover o arquivo remove apenas a entrada do arquivo no diretório O arquivo em si só é removido quando não possui uma entrada de diretório que o conduza (em sistemas de arquivos Unix típicos, pode haver mais de uma entrada de diretório para o mesmo arquivo ) e nenhum processo o abre. Aqui está uma maneira de observar isso - o arquivo é removido apenas quando o sleep
processo é interrompido ( rm
apenas remove sua entrada de diretório).
echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .
Com a estratégia 3, a etapa de mover o novo arquivo para o nome existente remove a entrada de diretório que leva ao conteúdo antigo e cria uma entrada de diretório que leva ao novo conteúdo. Isso é feito em uma operação atômica, portanto, essa estratégia tem uma grande vantagem: se um processo abrir o arquivo a qualquer momento, ele verá o conteúdo antigo ou o novo conteúdo - não há risco de obter conteúdo misto ou o arquivo não existir.
Substituindo Executáveis
Se você tentar a estratégia 1 com um executável em execução no Linux, receberá um erro.
cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy
Um "arquivo de texto" significa um arquivo que contém código executável por razões históricas obscuras . O Linux, como muitas outras variantes do unix, se recusa a sobrescrever o código de um programa em execução; algumas variantes do unix permitem isso, causando falhas, a menos que o novo código seja uma modificação muito boa do código antigo.
No Linux, você pode sobrescrever o código de uma biblioteca carregada dinamicamente. É provável que leve a uma falha do programa que o está usando. (Talvez você não consiga observar isso sleep
porque ele carrega todo o código da biblioteca necessário ao iniciar. Tente um programa mais complexo que faça algo útil depois de dormir, por exemplo perl -e 'sleep 9; print lc $ARGV[0]'
).
Se um intérprete estiver executando um script, o arquivo de script será aberto normalmente pelo intérprete, portanto, não haverá proteção contra a substituição do script. Alguns intérpretes leem e analisam o script inteiro antes de começarem a executar a primeira linha, outros leem o script conforme necessário. Consulte O que acontece se você editar um script durante a execução? e Como o Linux lida com scripts de shell? para mais detalhes.
As estratégias 2 e 3 também são seguras para os executáveis: embora a execução de executáveis (e bibliotecas carregadas dinamicamente) não sejam arquivos abertos no sentido de ter um descritor de arquivo, eles se comportam de maneira muito semelhante. Enquanto algum programa estiver executando o código, o arquivo permanecerá em disco, mesmo sem uma entrada de diretório.
Atualizando um aplicativo
A maioria dos gerenciadores de pacotes usa a estratégia 3 para substituir arquivos, devido à grande vantagem mencionada acima - a qualquer momento, a abertura do arquivo leva a uma versão válida dele.
Onde as atualizações de aplicativos podem ser interrompidas é que, enquanto a atualização de um arquivo é atômica, a atualização do aplicativo como um todo não ocorre se o aplicativo consistir em vários arquivos (programa, bibliotecas, dados, ...). Considere a seguinte sequência de eventos:
- Uma instância do aplicativo é iniciada.
- O aplicativo é atualizado.
- O aplicativo de instância em execução abre um de seus arquivos de dados.
Na etapa 3, a instância em execução da versão antiga do aplicativo está abrindo um arquivo de dados da nova versão. Se isso funciona ou não, depende do aplicativo, de qual arquivo é e de quanto foi modificado.
Após uma atualização, você notará que o programa antigo ainda está em execução. Se você deseja executar a nova versão, precisará sair do programa antigo e executar a nova versão. Os gerenciadores de pacotes geralmente matam e reiniciam daemons em uma atualização, mas deixam os aplicativos do usuário final em paz.
Alguns daemons têm procedimentos especiais para lidar com atualizações sem precisar matar o daemon e aguardar a nova instância reiniciar (o que causa uma interrupção no serviço). Isso é necessário no caso do init , que não pode ser morto; Os sistemas init fornecem uma maneira de solicitar que a instância em execução chame execve
para se substituir pela nova versão.