A maneira normal de escrever um arquivo atomicamente com segurança X
no Unix é:
- Grave o novo conteúdo do arquivo em um arquivo temporário
Y
. rename(2)
Y
paraX
Em duas etapas, parece que não fizemos nada além de mudar X
"no local".
Ele é protegido contra condições de corrida e perda de dados não intencional (onde X
é destruído, mas Y
está incompleto ou destruído).
A desvantagem (neste caso) disso é que ele não grava o inode referido por X
in-place; rename(2)
faz X
referência a um novo número de inode.
Quando X
era um arquivo com contagem de links > 1 (um link físico explícito), agora ele não se refere ao mesmo inode de antes, o link físico está quebrado.
A maneira óbvia de eliminar a desvantagem é escrever o arquivo no local, mas isso não é atômico, pode falhar, pode resultar em perda de dados etc.
Existe alguma maneira de fazer isso atomicamente, rename(2)
mas preservar links físicos?
Talvez para alterar o número do inode de Y
(o arquivo temporário) para o mesmo que X
, e dar o X
nome? Um "renomear" no nível do inode.
Isso efetivamente escreveria o inode referido por X
with Y
's new contents, mas não quebraria sua propriedade de link físico e manteria o nome antigo.
Se o hipotético inode "rename" fosse atômico, acho que isso seria atômico e protegido contra perda de dados / corridas.
O problema
Você tem uma lista (principalmente) exaustiva de chamadas de sistema aqui .
Você notará que não há nenhuma chamada "substituir o conteúdo deste inode". Modificar esse conteúdo sempre implica:
O passo 4 pode ser feito antes. Existem também alguns atalhos, como pwrite , que grava diretamente em um deslocamento especificado, combinando as etapas #2 e #3, ou escrita de dispersão .
Uma maneira alternativa é usar um mapeamento de memória , mas fica pior, pois cada byte gravado pode ser enviado para o arquivo subjacente de forma independente (conceitualmente, como se cada gravação fosse uma
write
chamada de 1 byte).→ O ponto é que o melhor cenário que você pode ter ainda são 2 operações: uma
write
e umatruncate
.Qualquer que seja a ordem em que você os execute, você ainda corre o risco de outro processo mexer com o arquivo no meio e acabar com um arquivo corrompido.
Soluções
Solução normal
Como você notou, é por isso que a abordagem canônica é criar um novo arquivo, você sabe que é o único escritor de (você pode até garantir isso combinando
O_TMPFILE
elinkat
), então redirecione atomicamente o nome antigo para o novo arquivo.Existem duas outras opções, no entanto, ambas falham de alguma forma:
Bloqueio obrigatório
Ele permite que o acesso ao arquivo seja negado a outros processos definindo uma combinação especial de sinalizadores. Parece a ferramenta para o trabalho, certo? No entanto:
Isso é apenas lógico, pois o Unix sempre evitou os bloqueios. Eles são propensos a erros e é impossível cobrir todos os casos extremos e garantir nenhum impasse.
Bloqueio consultivo
Ele é definido usando a chamada de sistema fcntl . No entanto, é apenas um conselho, e a maioria dos programas simplesmente o ignora.
Na verdade, só é bom para gerenciar bloqueios em arquivos compartilhados entre vários processos cooperando.
Conclusão
Não.
Os inodes são de baixo nível, quase um detalhe de implementação. Muito poucas APIs reconhecem sua existência (acredito que a
stat
família de chamadas seja a única).O que quer que você tente fazer provavelmente depende do mau uso do design dos sistemas de arquivos Unix ou simplesmente pedir demais para isso.
Isso poderia ser um problema XY ?