rsync
fa questo lavoro. Un file temporaneo è O_EXCL
creato per impostazione predefinita (disabilitato solo se usi --inplace
) e poi renamed
sul file di destinazione. Usa --ignore-existing
per non sovrascrivere B se esiste.
In pratica, non ho mai avuto problemi con questo su ext4, zfs o persino su mount NFS.
Non preoccuparti, noclobber
è una funzionalità standard.
Hai chiesto informazioni su NFS. È probabile che questo tipo di codice non funzioni sotto NFS, dal momento che il controllo per noclobber
coinvolge due operazioni NFS separate (verificare se il file esiste, creare un nuovo file) e due processi da due client NFS separati possono entrare in una race condition in cui entrambi riescono (entrambi verificano che B.part
non esiste ancora, allora entrambi procedono a crearlo con successo, di conseguenza si stanno sovrascrivendo a vicenda.)
Non c'è davvero da fare un controllo generico per verificare se il filesystem su cui stai scrivendo supporterà qualcosa come noclobber
atomicamente o meno. Potresti controllare il tipo di filesystem, se è NFS, ma sarebbe un'euristica e non necessariamente una garanzia. È probabile che filesystem come SMB/CIFS (Samba) soffrano degli stessi problemi. I filesystem esposti tramite FUSE possono o non possono comportarsi correttamente, ma ciò dipende principalmente dall'implementazione.
Un approccio possibilmente migliore è evitare la collisione nel B.part
passaggio, utilizzando un nome file univoco (attraverso la cooperazione con altri agenti) in modo da non dover dipendere da noclobber
. Ad esempio, potresti includere, come parte del nome del file, il tuo nome host, PID e un timestamp (+possibilmente un numero casuale). garantire l'unicità.
Quindi uno di:
test -f B && continue # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
# Maybe check for existance of B again, remove
# the temporary file and bail out in that case.
mv B.part."$unique" B
# mv (rename) should always succeed, overwrite a
# previously copied B if one exists.
Oppure:
test -f B && continue # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
if ln B.part."$unique" B ; then
echo "Success creating B"
else
echo "Failed creating B, already existed"
fi
# Both cases require cleanup.
rm B.part."$unique"
Quindi, se hai una race condition tra due agenti, entrambi procederanno con l'operazione, ma l'ultima operazione sarà atomica, quindi o B esiste con una copia completa di A, oppure B non esiste.
Puoi ridurre le dimensioni della gara ricontrollando dopo la copia e prima del mv
o ln
operazione, ma c'è ancora una piccola condizione di competizione lì. Ma, indipendentemente dalla race condition, il contenuto di B dovrebbe essere coerente, supponendo che entrambi i processi stiano tentando di crearlo da A (o una copia da un file valido come origine.)
Nota che nella prima situazione con mv
, quando esiste una gara, l'ultimo processo è quello che vince, poiché rename(2) sostituirà atomicamente un file esistente:
Se nuovopercorso esiste già, verrà sostituito atomicamente, in modo che non vi sia alcun punto in cui un altro processo tenti di accedere a newpath lo troverà mancante. [...]
Se nuovopercorso esiste ma l'operazione fallisce per qualche motivo,
rename()
garantisce di lasciare un'istanza di newpath al suo posto.
Quindi, è del tutto possibile che i processi che consumano B in quel momento possano vederne versioni diverse (inodi diversi) durante questo processo. Se gli autori stanno solo cercando di copiare gli stessi contenuti e i lettori stanno semplicemente consumando il contenuto del file, potrebbe andare bene, se ottengono inode diversi per file con gli stessi contenuti, saranno felici lo stesso.
Il secondo approccio utilizza un hard link sembra meglio, ma ricordo di aver fatto esperimenti con collegamenti fisici in un ciclo stretto su NFS da molti client simultanei e di aver contato il successo e sembrava che ci fossero ancora alcune condizioni di competizione lì, dove sembrava che due client emettessero un'operazione di collegamento fisico contemporaneamente, con il stessa destinazione, entrambi sembravano avere successo. (È possibile che questo comportamento fosse correlato alla particolare implementazione del server NFS, YMMV.) In ogni caso, è probabilmente lo stesso tipo di race condition, in cui potresti finire per ottenere due inode separati per lo stesso file nei casi in cui ci sono concorrenza tra scrittori per innescare queste condizioni di competizione. Se i tuoi scrittori sono coerenti (copiando entrambi da A a B) e i tuoi lettori stanno solo consumando i contenuti, potrebbe essere sufficiente.
Infine, hai menzionato il blocco. Sfortunatamente il blocco è gravemente carente, almeno in NFSv3 (non sono sicuro di NFSv4, ma scommetto che non va neanche bene.) Se stai considerando il blocco, dovresti esaminare diversi protocolli per il blocco distribuito, possibilmente fuori banda con il copie di file effettive, ma è sia dirompente, complesso e soggetto a problemi come deadlock, quindi direi che è meglio evitarlo.
Per ulteriori informazioni sull'argomento dell'atomicità su NFS, potresti voler leggere sul formato della casella di posta Maildir, che è stato creato per evitare blocchi e funzionare in modo affidabile anche su NFS. Lo fa mantenendo nomi di file univoci ovunque (quindi non ottieni nemmeno una B finale alla fine.)
Forse un po' più interessante per il tuo caso particolare, il formato Maildir++ estende Maildir per aggiungere il supporto per la quota della casella di posta e lo fa aggiornando atomicamente un file con un nome fisso all'interno della casella di posta (quindi potrebbe essere più vicino al tuo B.) Penso che Maildir++ ci provi da aggiungere, che non è realmente sicuro su NFS, ma esiste un approccio di ricalcolo che utilizza una procedura simile a questa ed è valido come sostituzione atomica.
Speriamo che tutti questi suggerimenti siano utili!