Eu quero copiar um arquivo de A para B, que pode estar em sistemas de arquivos diferentes.
Existem alguns requisitos adicionais:
- A cópia é tudo ou nada, nenhum arquivo B parcial ou corrompido foi deixado no local na falha;
- Não sobrescreva um arquivo B existente;
- Não concorra com uma execução concorrente do mesmo comando, no máximo um pode ter sucesso.
Acho que isso se aproxima:
cp A B.part && \
ln B B.part && \
rm B.part
Mas 3. é violado pelo cp não falhando se B.part existir (mesmo com o sinalizador -n). Subsequentemente 1. pode falhar se o outro processo 'ganhar' o cp e o arquivo vinculado ao local estiver incompleto. B.part também pode ser um arquivo não relacionado, mas fico feliz em falhar sem tentar outros nomes ocultos nesse caso.
Acho que o bash noclobber ajuda, isso funciona totalmente? Existe uma maneira de obter sem o requisito de versão do 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 de qualquer maneira (NFS). Existe uma maneira de detectar esses sistemas de arquivos?
Algumas outras perguntas relacionadas, mas não exatamente as mesmas:
Aproximando o movimento atômico entre sistemas de arquivos?
https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html
rsync
faz esse trabalho. Um arquivo temporário éO_EXCL
criado por padrão (desativado apenas se você usar--inplace
) e, em seguida,renamed
sobre o arquivo de destino. Use--ignore-existing
para não substituir B se existir.Na prática, nunca tive problemas com isso em montagens ext4, zfs ou mesmo NFS.
Não se preocupe,
noclobber
é um recurso padrão .Você perguntou sobre NFS. É provável que esse tipo de código seja interrompido no NFS, pois a verificação
noclobber
envolve duas operações NFS separadas (verificar se o arquivo existe, criar 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 queB.part
ainda não existe, então ambos procedem para criá-lo com sucesso, como resultado, eles estão substituindo um ao outro.)Não há realmente para fazer uma verificação genérica para saber se o sistema de arquivos no qual você está escrevendo suportará algo como
noclobber
atomicamente ou não. Você pode verificar o tipo de sistema de arquivos, se é NFS, mas isso seria uma heurística e não necessariamente uma garantia. Sistemas de arquivos como SMB/CIFS (Samba) provavelmente sofrerão 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). garantem a exclusividade.Então, qualquer um:
Ou:
Portanto, se você tiver uma condição de corrida entre dois agentes, ambos prosseguirã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. Mas, independentemente da condição de corrida, o conteúdo de B deve ser consistente, assumindo que ambos os processos estão tentando criá-lo de A (ou uma cópia de um arquivo válido como origem).Observe que na primeira situação com
mv
, quando existe uma corrida, o último processo é quem vence, pois rename(2) substituirá atomicamente um arquivo existente:Portanto, é bem possível que os processos que consomem B no momento possam ver diferentes versões dele (diferentes inodes) durante esse processo. Se os escritores estão apenas tentando copiar o mesmo conteúdo, e os leitores estão simplesmente consumindo o conteúdo do arquivo, tudo bem, se eles obtiverem 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 me lembro de fazer experimentos com links físicos em um loop apertado no NFS de muitos clientes simultâneos e contando o sucesso e ainda parecia haver algumas condições de corrida lá, onde parecia que dois clientes emitiram um link físico operação ao mesmo tempo, com o mesmo destino, ambos pareciam ter sucesso. (É possível que esse comportamento esteja relacionado à implementação específica do servidor NFS, YMMV.) Em qualquer caso, esse é provavelmente o mesmo tipo de condição de corrida, onde você pode acabar obtendo dois inodes separados para o mesmo arquivo nos casos em que há simultaneidade entre escritores para acionar essas condições de corrida. Se seus escritores são consistentes (ambos copiando 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). cópias de arquivos reais, mas isso é perturbador, complexo e propenso a problemas como deadlocks, então eu diria que é melhor evitar.
Para obter mais informações sobre o assunto de atomicidade em NFS, você pode querer ler sobre o formato de caixa de correio Maildir , que foi criado para evitar bloqueios e trabalhar de forma confiável mesmo em NFS. Ele faz isso 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 particular, o formato Maildir++ estende Maildir para adicionar suporte para cota de caixa de correio e faz isso 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 para anexar, o que não é realmente seguro no NFS, mas há uma abordagem de recálculo que usa um procedimento semelhante a este e é válido como uma substituição atômica.
Espero que todas essas dicas sejam úteis!
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 obter 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 falhou, você deve começar de novo ou tentar corrigi-lo gravando no arquivo.
Observe também que é melhor
fsync()
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 concorrente e uma operação travada por último.
Você obterá o resultado correto fazendo um
cp
junto commv
. Isso substituirá "B" por uma nova cópia de "A" ou deixará "B" como estava antes.atualização para acomodar existente
B
:Isso não é 100% atômico, mas chega perto. Existe uma condição de corrida onde duas dessas coisas estão rodando, ambas entram no
if
teste ao mesmo tempo, ambas veem queB
não existe, então ambas executam omv
.Você pode fazer isso criando um arquivo temporário adequado no diretório de destino, copiando esse arquivo temporário e vinculando o arquivo temporário ao destino, como estava fazendo na pergunta.
Isso depende apenas de
linkat(2)
ser atômico para o sistema de arquivos de destinoRsync é a ferramenta apropriada para usar, eu acho.
Você deve usar rsync -Pahn --checksum /path/from/source /destination/path
No entanto, tenha cuidado, os arquivos que você possui são muito grandes ...