Como você suspeitava, sistemas semelhantes ao Unix impedem que a maioria dos arquivos que estão sendo executados sejam sobrescritos. Veja o que a norma diz sobre a chamada do sistema aberta:
The open() function may fail if:
[ETXTBSY] The file is a pure procedure (shared text) file that is being executed and oflag is O_WRONLY or O_RDWR.
Mas um arquivo sendo executado ainda pode ser desvinculado , e é assim que os gerenciadores de pacotes fazem seu trabalho.
Veja o que o dpkg
no Debian e no Ubuntu faz ao instalar a nova versão do /bin/cpio
que acabou de sair:
open("/bin/cpio.dpkg-new", O_WRONLY|O_CREAT|O_EXCL, 0) = 10
// lots of reads and writes omitted from this listing.
// It's copying the new version into dpkg-new
fchown(10, 0, 0) = 0
fchmod(10, 0755) = 0
close(10) = 0
rename("/bin/cpio.dpkg-new", "/bin/cpio") = 0
Em detalhes, isso:
- copia a nova versão para
cpio.dpkg-new
no mesmo diretório que cpio
.
- define as permissões de proprietário e arquivo para o que o pacote disser que deve ser
- renomeia
cpio.dpkg-new
para cpio
A maneira de instalar com segurança uma nova versão de um arquivo sem estar na posição do arquivo que não existe, mesmo que por um instante, é usar o renomear chamada de sistema. Isso requer que ambos os arquivos estejam no mesmo sistema de arquivos, e é por isso que o dpkg criou a nova versão do cpio no mesmo diretório da versão antiga. O padrão diz:
int rename(const char *old, const char *new);
The rename() function shall change the name of a file. The old argument points to the pathname of the file to be renamed. The new argument points to the new pathname of the file.
If the link named by the new argument exists, it shall be removed and old renamed to new. In this case, a link named new shall remain visible to other processes throughout the renaming operation and refer either to the file referred to by new or old before the operation began.
A terminologia pode ser um pouco confusa porque os arquivos old e novos dados para rename
aqui são a nova versão do cpio e a versão antiga do cpio , respectivamente.
Por fim, aqui está a chave para responder sua pergunta:
If the link named by the new argument exists and the file's link count becomes 0 when it is removed and no process has the file open, the space occupied by the file shall be freed and the file shall no longer be accessible. If one or more processes have the file open when the last link is removed, the link shall be removed before rename() returns, but the removal of the file contents shall be postponed until all references to the file are closed.
A última frase significa que, se algum processo tiver cpio
aberto (ou estiver sendo executado) quando o rename
for concluído, ele continuará a ver o conteúdo anterior do arquivo até que ele feche o arquivo (ou saia).
O pacman
do Arch parece fazer praticamente a mesma coisa:
snprintf(checkfile, len, "%s.paccheck", filename);
...
if(perform_extraction(handle, archive, entry, checkfile, entryname_orig)) {
errors++;
goto needbackup_cleanup;
}
...
if(try_rename(handle, checkfile, filename)) {
errors++;
}
Não obstante os esforços dos gerenciadores de pacotes para instalar novos arquivos com segurança, pode haver alguns problemas quando um programa é atualizado enquanto estiver em uso. O pacote firefox, por exemplo, tem mais de uma dúzia de executáveis e objetos compartilhados. Alguém executando uma versão antiga do firefox durante uma atualização pode descobrir que uma extensão que ele chama após a conclusão da atualização é incompatível com a versão antiga do Firefox. Atualizei recentemente o firefox no Ubuntu e o apt-get foi impresso:
Please restart all running instances of firefox, or you will experience problems.
Se você é um administrador de sistema de um sistema multiusuário, é uma boa ideia anunciar atualizações pendentes para a comunidade de usuários.