Quero copiar um arquivo de A para B, que pode estar em diferentes sistemas de arquivos.
Existem alguns requisitos adicionais:
- A cópia é tudo ou nada, nenhum arquivo B parcial ou corrompido foi deixado no local;
- Não substitua um arquivo B existente;
- Não concorra com uma execução simultânea do mesmo comando, no máximo é possível ter sucesso.
Eu acho que isso se aproxima:
cp A B.part && \
ln B B.part && \
rm B.part
Mas 3. é violado pelo cp e não falha se B.part existir (mesmo com o sinalizador -n). Posteriormente, 1. poderá falhar se o outro processo 'vencer' o cp e o arquivo vinculado no local estiver incompleto. B.part também pode ser um arquivo não relacionado, mas estou feliz por falhar sem tentar outros nomes ocultos nesse caso.
Acho bash noclobber ajuda, isso funciona totalmente? Existe uma maneira de obter sem o requisito da versão bash?
#!/usr/bin/env bash
set -o noclobber
cat A > B.part && \
ln B.part B && \
rm B.part
Acompanhamento, eu sei que alguns sistemas de arquivos falharão nisso de qualquer maneira (NFS). Existe uma maneira de detectar esses sistemas de arquivos?
Algumas outras questões relacionadas, mas não exatamente as mesmas:
Movimento atômico aproximado entre sistemas de arquivos?
https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html
mv
sobrescreverá um arquivo B.mv -n
não notificará que falhou.ln(1)
(rename(2)
) falhará se B já existir.Respostas:
rsync
faz esse trabalho. Um arquivo temporário éO_EXCL
criado por padrão (somente desativado se você usar--inplace
) e depoisrenamed
sobre o arquivo de destino. Usar--ignore-existing
para não substituir B, se existir.Na prática, nunca tive problemas com isso em montagens ext4, zfs ou mesmo NFS.
fonte
Não se preocupe,
noclobber
é um recurso padrão .fonte
Você perguntou sobre o NFS. É provável que esse tipo de código seja quebrado no NFS, pois a verificação
noclobber
envolve duas operações NFS separadas (verifique se existe um arquivo, crie um novo arquivo) e dois processos de dois clientes NFS separados podem entrar em uma condição de corrida em que ambos são bem-sucedidos ( ambos verificam seB.part
ainda não existe e, em seguida, continuam a criá-lo com êxito, como resultado da substituição um do outro.)Não há realmente uma verificação genérica para saber se o sistema de arquivos para o qual você está escrevendo suportará algo como
noclobber
atomicamente ou não. Você pode verificar o tipo de sistema de arquivos, seja NFS, mas isso seria uma heurística e não necessariamente uma garantia. Sistemas de arquivos como SMB / CIFS (Samba) provavelmente sofrem dos mesmos problemas. Os sistemas de arquivos expostos através do FUSE podem ou não se comportar corretamente, mas isso depende principalmente da implementação.Uma abordagem possivelmente melhor é evitar a colisão na
B.part
etapa, usando um nome de arquivo exclusivo (através da cooperação com outros agentes) para que você não precise depender denoclobber
. Por exemplo, você pode incluir, como parte do nome do arquivo, seu nome de host, PID e um carimbo de data / hora (+ possivelmente um número aleatório). Como deve haver um único processo em execução em um PID específico em um host a qualquer momento, isso deve garantir exclusividade.Então, um dos seguintes:
Ou:
Portanto, se você tiver uma condição de corrida entre dois agentes, os dois continuarão com a operação, mas a última operação será atômica; portanto, B existe com uma cópia completa de A ou B não existe.
Você pode reduzir o tamanho da corrida verificando novamente após a cópia e antes da operação
mv
ouln
, mas ainda há uma pequena condição de corrida lá. Mas, independentemente da condição de corrida, o conteúdo de B deve ser consistente, supondo que os dois processos estejam tentando criá-lo a partir de A (ou uma cópia de um arquivo válido como origem).Observe que na primeira situação em que
mv
, quando existe uma corrida, o último processo é aquele que vence, pois renomear (2) substituirá atomicamente um arquivo existente:Portanto, é bem possível que processos que consomem B ao mesmo tempo possam ver versões diferentes (inodes diferentes) durante esse processo. Se todos os escritores estiverem tentando copiar o mesmo conteúdo e os leitores estiverem consumindo o conteúdo do arquivo, isso pode ser bom, se eles tiverem inodes diferentes para arquivos com o mesmo conteúdo, eles ficarão felizes da mesma forma.
A segunda abordagem, usando um link físico , parece melhor, mas eu me lembro de fazer experimentos com links físicos em um loop restrito no NFS de muitos clientes simultâneos e contar com sucesso, e ainda parecia haver algumas condições de corrida, onde parecia que dois clientes emitiam um link físico operação ao mesmo tempo, com o mesmo destino, ambos pareciam ter sucesso. (É possível que esse comportamento tenha sido relacionado à implementação específica do servidor NFS, YMMV.) De qualquer forma, esse provavelmente é o mesmo tipo de condição de corrida, em que você pode acabar recebendo dois inodes separados para o mesmo arquivo nos casos em que há muito tráfego. concorrência entre escritores para acionar essas condições de corrida. Se seus escritores são consistentes (ambos copiam de A para B) e seus leitores estão consumindo apenas o conteúdo, isso pode ser suficiente.
Finalmente, você mencionou o bloqueio. Infelizmente, o bloqueio está em falta, pelo menos no NFSv3 (não tenho certeza sobre o NFSv4, mas aposto que também não é bom.) Se você está pensando em bloquear, deve procurar em diferentes protocolos para bloqueio distribuído, possivelmente fora de banda com o cópias de arquivos reais, mas isso é perturbador, complexo e propenso a problemas como conflitos, então eu diria que é melhor evitar.
Para obter mais informações sobre atomicidade no NFS, convém ler no formato de caixa de correio Maildir , criado para evitar bloqueios e trabalhar de maneira confiável, mesmo no NFS. Isso é feito mantendo nomes de arquivos exclusivos em todos os lugares (para que você nem obtenha um B final no final).
Talvez um pouco mais interessante para o seu caso em particular, o formato Maildir ++ estende o Maildir para adicionar suporte à cota da caixa de correio e o faz atualizando atomicamente um arquivo com um nome fixo dentro da caixa de correio (para que possa estar mais próximo do seu B.) Acho que o Maildir ++ tenta anexar, o que não é realmente seguro no NFS, mas existe uma abordagem de recálculo que usa um procedimento semelhante a esse e é válido como uma substituição atômica.
Espero que todos esses indicadores sejam úteis!
fonte
Você pode escrever um programa para isso.
Use
open(O_CREAT|O_RDWD)
para abrir o arquivo de destino, leia todos os bytes e metadados para verificar se o arquivo de destino é completo, caso contrário, existem duas possibilidades,Gravação incompleta
Outro processo está executando o mesmo programa.
Tente adquirir um bloqueio de descrição de arquivo aberto no arquivo de destino.
Falha significa que há um processo simultâneo, o processo atual deve existir.
Sucesso significa que a última gravação travou; você deve recomeçar ou tentar corrigi-lo gravando no arquivo.
Observe também que é melhor
fsync()
depois de gravar no arquivo de destino antes de fechar o arquivo e liberar o bloqueio, ou outro processo pode ler dados que ainda não estão no disco.https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html
Isso é importante para ajudá-lo a distinguir entre um programa em execução simultâneo e a operação travada por último.
fonte
Você obterá o resultado correto fazendo um
cp
conjunto commv
. Isso substituirá "B" por uma nova cópia de "A" ou deixará "B" como estava anteriormente.atualização para acomodar existente
B
:Isso não é 100% atômico, mas chega perto. Há uma condição de corrida em que duas dessas coisas estão em execução, ambas entram no
if
teste ao mesmo tempo, ambas veem queB
não existe e, então, executam amv
.fonte
mv B.tmp B
não será executado a menos que sejacp A B.tmp
executado pela primeira vez e retorne um código de resultado bem-sucedido. como isso é um fracasso? Além disso, concordo quecp A B.tmp
substituiria um existenteB.tmp
que é o que você deseja fazer. As&&
garantias de que o segundo comando será executado se e somente se o primeiro for concluído normalmente.