É seguro mover um arquivo ao qual está sendo anexado?

28

Eu tenho um processo node.js que usa fs.appendFilepara adicionar linhas file.log. Apenas linhas completas de cerca de 40 caracteres por linha são anexadas, por exemplo, as chamadas são como fs.appendFile("start-end"), e não duas chamadas como fs.appendFile("start-")e fs.appendFile("end"). Se eu mover esse arquivo para, file2.logposso ter certeza de que nenhuma linha foi perdida ou copiada parcialmente?

Fofo
fonte

Respostas:

36

Desde que você não mova o arquivo pelas bordas do sistema de arquivos, a operação deve ser segura. Isto é devido ao mecanismo, como "mover" realmente é feito.

Se você mvpossui um arquivo no mesmo sistema de arquivos, o arquivo não é realmente tocado, mas apenas a entrada do sistema de arquivos é alterada.

$ mv foo bar

realmente faz algo como

$ ln foo bar
$ rm foo

Isso criaria um duro link (uma segunda entrada de diretório) para o arquivo (na verdade, o inode apontado por entrada do sistema de arquivos) foocom o nome bare remover a fooentrada. Como agora, ao remover foo, existe uma segunda entrada do sistema de arquivos apontando para fooo inode, remover a entrada antiga foona verdade não remove nenhum bloco pertencente ao inode.

Seu programa seria felizmente acrescentado ao arquivo de qualquer maneira, uma vez que seu identificador de arquivo aberto aponta para o inode do arquivo, não para a entrada do sistema de arquivos.

Nota: Se o seu programa fechar e reabrir o arquivo entre gravações, você poderá criar um novo arquivo com a entrada antiga do sistema de arquivos!

Movimentos cruzados do sistema de arquivos:

Se você mover o arquivo pelas bordas do sistema de arquivos, as coisas ficarão feias. Nesse caso, você não pode garantir que seu arquivo seja consistente, pois mvisso realmente

  • crie um novo arquivo no sistema de arquivos de destino
  • copie o conteúdo do arquivo antigo para o novo arquivo
  • remova o arquivo antigo

or

$ cp /path/to/foo /path/to/bar
$ rm /path/to/foo

resp.

$ touch /path/to/bar
$ cat < /path/to/foo > /path/to/bar
$ rm /path/to/foo

Dependendo se a cópia atinge o final do arquivo durante a gravação do seu aplicativo, pode acontecer que você tenha apenas metade de uma linha no novo arquivo.

Além disso, se seu aplicativo não fechar e reabrir o arquivo antigo, ele continuará gravando no arquivo antigo, mesmo que pareça ser excluído: o kernel sabe quais arquivos estão abertos e, embora exclua a entrada do sistema de arquivos, não excluirá o inode do arquivo antigo e os blocos associados até que seu aplicativo feche seu identificador de arquivo aberto.

Andreas Wiese
fonte
3
Para sua informação, as primeiras versões do Unix não tinham uma rename()chamada de sistema. Portanto, a versão original de mvfato chamou link()para criar o link físico, seguido por unlink()para remover o nome original. rename()foi adicionado no FreeBSD, para implementar isso atomicamente no kernel.
Barmar 29/10
Me desculpe, mas o que é file-system borders?
precisa saber é
1
@ laike9m - As bordas do sistema de arquivos se referem ao fato de que um sistema de arquivos simples deve residir em uma partição em um dispositivo de memória como uma unidade de disco. Se você renomear um arquivo no sistema de arquivos, tudo o que muda é o nome em uma entrada de diretório. Ele ainda possui o mesmo inode - se estivesse em um sistema de arquivos baseado em inodes - para começar - como a maioria dos sistemas de arquivos Linux. Porém, se você mover o arquivo para outro sistema de arquivos, os dados reais deverão ser movidos e o arquivo receberá um novo inode do novo sistema de arquivos. Isso interromperia quaisquer operações no arquivo que estavam em andamento quando isso ocorreu.
Joe
9

Como você diz que está usando o node.js, presumo que você esteja usando fs.rename()(ou fs.renameSync()) para renomear os arquivos. Este método node.js está documentado para usar a chamada do sistema rename (2) , que não toca no arquivo em si de forma alguma, mas apenas altera o nome com o qual está listado no sistema de arquivos:

" rename () renomeia um arquivo, movendo-o entre diretórios, se necessário. Quaisquer outros links físicos para o arquivo (criados usando o link (2) ) não são afetados. Os descritores de arquivo aberto para o caminho antigo também não são afetados."

Em particular, observe a última frase citada acima, que diz que qualquer descritor de arquivo aberto (como o seu programa usaria para gravar no arquivo) continuará apontando para ele, mesmo depois que ele for renomeado. Portanto, não haverá perda ou corrupção de dados, mesmo que o arquivo seja renomeado enquanto estiver sendo gravado simultaneamente.


Como Andreas Weise observa em sua resposta , a chamada do sistema renomear (2) (e, portanto, fs.rename()no node.js) não funcionará além dos limites do sistema de arquivos. Portanto, tentar mover um arquivo para um sistema de arquivos diferente dessa maneira simplesmente falhará.

O mvcomando Unix tenta ocultar essa limitação detectando o erro e, em vez disso, movendo o arquivo, copiando seu conteúdo para um novo arquivo e excluindo o original. Infelizmente, mover arquivos como este faz a perda de dados de risco se o arquivo é movido enquanto ele está sendo escrito. Assim, se você deseja renomear com segurança arquivos que podem ser gravados simultaneamente em, você deve não usar mv(ou, pelo menos, você deve fazer absolutamente certo de que o novo e o velho caminho estão no mesmo sistema de arquivos).

Ilmari Karonen
fonte