Jak transakcyjnie skopiować plik?

9

Chcę skopiować plik z A do B, który może znajdować się w różnych systemach plików.

Istnieją pewne dodatkowe wymagania:

  1. Kopia jest w całości lub w ogóle, brak częściowego lub uszkodzonego pliku B po awarii;
  2. Nie zastępuj istniejącego pliku B;
  3. Nie konkuruj z jednoczesnym wykonywaniem tego samego polecenia, co najwyżej można odnieść sukces.

Myślę, że to się zbliża:

cp A B.part && \
ln B B.part && \
rm B.part

Ale 3. zostaje naruszone przez cp nie zawodzi, jeśli B.part istnieje (nawet z flagą -n). Następnie 1. może się nie powieść, jeśli inny proces „wygra” cp, a plik połączony na miejscu jest niekompletny. B.part może być również niepowiązanym plikiem, ale cieszę się, że nie udało mi się spróbować innych ukrytych nazw w tym przypadku.

Myślę, że bash noclobber pomaga, czy to działa w pełni? Czy istnieje sposób na obejście się bez wymagania wersji bash?

#!/usr/bin/env bash
set -o noclobber
cat A > B.part && \
ln B.part B && \
rm B.part

W dalszym ciągu wiem, że niektóre systemy plików i tak się nie sprawdzą (NFS). Czy istnieje sposób na wykrycie takich systemów plików?

Niektóre inne powiązane, ale niezupełnie te same pytania:

Przybliżanie ruchu atomowego między systemami plików?

Czy mv jest atomowy na moim FS?

jest sposób na atomowe przenoszenie pliku i katalogu z tempfs na partycję ext4 na eMMC

https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html

Evan Benn
źródło
2
Czy obawiasz się tylko o jednoczesne wykonanie tego samego polecenia (tj. Czy wystarczy zablokowanie w narzędziu), czy też o inne zewnętrzne zakłócenia plików?
Michael Homer
3
„Transactional” może być lepszy
muru 26.07.19
1
@MichaelHomer w narzędziu jest wystarczająco dobry, myślę, że na zewnątrz mogłoby to bardzo utrudnić! Jeśli to możliwe z blokadami plików ...
Evan Benn
1
@marcelm mvnadpisze istniejący plik B. mv -nnie powiadomi o niepowodzeniu. ln(1)( rename(2)) zakończy się niepowodzeniem, jeśli B już istnieje.
Evan Benn
1
@EvanBenn Dobra uwaga! Powinienem był lepiej przeczytać twoje wymagania. (
Zwykle

Odpowiedzi:

11

rsyncwykonuje tę pracę. Plik tymczasowy jest O_EXCLtworzony domyślnie (wyłączony, jeśli używasz --inplace), a następnie renamednad plikiem docelowym. Użyj, --ignore-existingaby nie zastępować B, jeśli istnieje.

W praktyce nigdy nie spotkałem się z tym z żadnymi problemami na wierzchowcach ext4, zfs, a nawet NFS.

Hermann
źródło
rsync prawdopodobnie robi to dobrze, ale bardzo skomplikowana strona podręcznika mnie przeraża. opcje sugerujące inne opcje, niezgodne ze sobą itp.
Evan Benn
Rsync nie pomaga w wymaganiu nr 3, o ile wiem. Mimo to jest to fantastyczne narzędzie i nie powinieneś unikać czytania stron podręcznika. Możesz także wypróbować github.com/tldr-pages/tldr/blob/master/pages/common/rsync.md lub cheat.sh/rsync . (tldr i cheat to dwa różne projekty, które mają pomóc rozwiązać opisany problem, a mianowicie: „strona podręcznika to TL; DR”; obsługiwanych jest wiele typowych poleceń i zobaczysz najczęściej używane zastosowania.
sitaram
@EvanBenn rsync to niesamowite narzędzie i warte nauki! Ta strona podręcznika jest skomplikowana, ponieważ jest tak wszechstronna. Nie bądź zastraszony :)
Josh
@sitaram, nr 3 można rozwiązać za pomocą pliku pid. Mały skrypt jak w odpowiedzi tutaj .
Robert Riedl
2
To najlepsza odpowiedź. Rsync jest standardem branżowym dla transferów plików atomowych, a w różnych konfiguracjach może spełnić wszystkie Twoje wymagania.
wKavey
4

Nie martw się, noclobberto standardowa funkcja .

ilkkachu
źródło
Dzięki, kusiło mnie, by przyjąć tę zwięzłą odpowiedź. Wszelkie uwagi na temat podejrzanych systemów plików, takich jak NFS?
Evan Benn
@EvanBenn, chciałem dodać, że nie jestem pewien, czy NFS zamierza cię tutaj zepsuć, ale zapomniałem.
ilkkachu
4

Zapytałeś o NFS. Ten rodzaj kodu najprawdopodobniej noclobberulegnie awarii w systemie plików NFS, ponieważ sprawdzenie dotyczy dwóch osobnych operacji NFS (sprawdź, czy plik istnieje, utwórz nowy plik), a dwa procesy z dwóch oddzielnych klientów NFS mogą przejść w stan wyścigu, w którym oba się powiedzie ( oba sprawdzają, że B.partjeszcze nie istnieje, a następnie oba z powodzeniem go tworzą, w wyniku czego się wzajemnie nadpisują).

Naprawdę nie ma potrzeby ogólnego sprawdzania, czy system plików, w którym piszesz, będzie obsługiwał coś takiego jak noclobberatomowy, czy nie. Możesz sprawdzić typ systemu plików, czy to NFS, ale byłby to heurystyka i niekoniecznie gwarancja. Systemy plików takie jak SMB / CIFS (Samba) prawdopodobnie cierpią z powodu tych samych problemów. Systemy plików narażone przez FUSE mogą, ale nie muszą zachowywać się poprawnie, ale to zależy głównie od implementacji.


Prawdopodobnie lepszym rozwiązaniem jest uniknięcie kolizji na B.partetapie, poprzez użycie unikalnej nazwy pliku (dzięki współpracy z innymi agentami), dzięki czemu nie trzeba polegać noclobber. Na przykład możesz dołączyć jako część nazwy pliku nazwę hosta, identyfikator PID i znacznik czasu (+ ewentualnie liczbę losową). Ponieważ w danym momencie powinien istnieć pojedynczy proces o określonym identyfikatorze PID na hoście, powinno to być gwarantuje wyjątkowość.

Więc jedno z:

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.

Lub:

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"

Więc jeśli masz warunki wyścigu między dwoma agentami, obaj będą kontynuować operację, ale ostatnia operacja będzie atomowa, więc albo B istnieje z pełną kopią A, albo B nie istnieje.

Możesz zmniejszyć rozmiar wyścigu, sprawdzając ponownie po kopii i przed operacją mvlub ln, ale nadal istnieje mały warunek wyścigu. Jednak niezależnie od warunków wyścigu zawartość B powinna być spójna, zakładając, że oba procesy próbują utworzyć ją z A (lub kopii z prawidłowego pliku jako źródła).

Zauważ, że w pierwszej sytuacji mv, gdy wyścig istnieje, ostatni wygrywa ten proces, ponieważ zmiana nazwy (2) atomowo zastąpi istniejący plik:

Jeśli newpath już istnieje, zostanie on zastąpiony atomowo, tak że nie będzie punktu, w którym inny proces próbujący uzyskać dostęp do newpath nie uzna go za brakujący. [...]

Jeśli newpath istnieje, ale operacja z jakiegoś powodu nie powiedzie się, rename()gwarantuje pozostawienie instancji newpath na miejscu.

Jest więc całkiem możliwe, że procesy zużywające B w tym czasie mogą zobaczyć różne jego wersje (różne i-węzły) podczas tego procesu. Jeśli wszyscy autorzy próbują po prostu skopiować tę samą zawartość, a czytelnicy po prostu zużywają zawartość pliku, to może być w porządku, jeśli otrzymają różne i-węzły dla plików o tej samej zawartości, będą zadowoleni tak samo.

Drugie podejście z wykorzystaniem twardego łącza wygląda lepiej, ale przypominam sobie przeprowadzanie eksperymentów z linkami twardymi w ciasnej pętli na NFS od wielu równoległych klientów i liczenie sukcesu, i nadal zdawały się istnieć pewne warunki wyścigowe, gdzie wydawało się, że dwóch klientów wydało hardlink Operacja w tym samym czasie, z tym samym miejscem docelowym, wydawała się udana. (Możliwe, że to zachowanie było związane z konkretną implementacją serwera NFS, YMMV.) W każdym razie jest to prawdopodobnie ten sam rodzaj wyścigu, w którym możesz uzyskać dwa osobne i-węzły dla tego samego pliku, w przypadkach, gdy jest ciężki współbieżność między pisarzami, aby uruchomić te warunki wyścigu. Jeśli twoi autorzy są konsekwentni (oba kopiują od A do B), a czytelnicy zużywają tylko zawartość, może to wystarczyć.

Wreszcie wspomniałeś o blokowaniu. Niestety blokowanie jest poważnie brakuje, przynajmniej w NFSv3 (nie jestem pewien co do NFSv4, ale założę się, że to też nie jest dobre). Jeśli zastanawiasz się nad blokowaniem, powinieneś rozważyć różne protokoły blokowania rozproszonego, być może poza pasmem z rzeczywiste kopie plików, ale jest to zarówno destrukcyjne, złożone, jak i podatne na problemy, takie jak zakleszczenia, więc powiedziałbym, że lepiej tego unikać.


Aby uzyskać więcej informacji na temat atomowości na NFS, możesz przeczytać w formacie skrzynki pocztowej Maildir , który został stworzony w celu uniknięcia blokad i niezawodnej pracy nawet w NFS. Robi to, utrzymując wszędzie unikalne nazwy plików (dzięki czemu nie dostajesz nawet końcowego B na końcu).

Być może nieco bardziej interesujący dla twojego konkretnego przypadku, format Maildir ++ rozszerza Maildir o obsługę limitu skrzynki pocztowej i robi to poprzez atomową aktualizację pliku o stałej nazwie w skrzynce pocztowej (aby być bliżej twojego B.) Myślę, że Maildir ++ próbuje do dołączania, co nie jest tak naprawdę bezpieczne w systemie plików NFS, ale istnieje metoda przeliczania, która wykorzystuje procedurę podobną do tej i jest ważna jako zamiana atomowa.

Mam nadzieję, że wszystkie te wskaźniki będą przydatne!

filbranden
źródło
2

Możesz do tego napisać program.

Użyj, open(O_CREAT|O_RDWD)aby otworzyć plik docelowy, przeczytaj wszystkie bajty i metadane, aby sprawdzić, czy plik docelowy jest kompletny, jeśli nie, istnieją dwie możliwości,

  1. Niekompletne pisanie

  2. Inny proces uruchamia ten sam program.

Spróbuj uzyskać otwartą blokadę opisu pliku na pliku docelowym.

Niepowodzenie oznacza, że ​​istnieje równoległy proces, obecny proces powinien istnieć.

Sukces oznacza, że ​​ostatni zapis się zawiesił, powinieneś zacząć od nowa lub spróbować naprawić, pisząc do pliku.

Zauważ też, że lepiej byłoby fsync()po zapisaniu do pliku docelowego przed zamknięciem pliku i zwolnieniem blokady, w przeciwnym razie inny proces może odczytać dane, które nie znajdują się jeszcze na dysku.

https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html

Jest to ważne, aby pomóc Ci rozróżnić między jednocześnie uruchomionym programem a operacją, która zakończyła się awarią.

炸鱼 薯条 德里克
źródło
Dzięki za informacje, jestem zainteresowany wdrożeniem tego samodzielnie i spróbuję. Dziwię się, że nie istnieje on już jako część pakietu coreutils / podobnego!
Evan Benn
To podejście nie może spełnić żadnego częściowego lub uszkodzonego pliku B pozostawionego na miejscu w przypadku awarii . Naprawdę najlepiej jest zastosować standardowe podejście polegające na skopiowaniu pliku do tymczasowej nazwy, a następnie przeniesieniu go na miejsce: ruch może być atomowy, a kopiowanie nie.
reinierpost
@reinierpost W przypadku awarii, ale dane nie są w pełni kopiowane, częściowo skopiowane dane pozostaną bez względu na wszystko. Ale moje podejście to wykryje i naprawi. Przenoszenie pliku nie może być atomowe, żadne dane zapisane na dysku w fizycznym sektorze nie będą atomowe, ale oprogramowanie (np. Sterownik systemu plików OS, takie podejście) może to naprawić (jeśli rw) lub zgłosić spójny stan (jeśli ro) , jak wspomniano w sekcji komentarza pytania. Pytanie dotyczy także kopiowania, a nie przenoszenia.
炸鱼 薯条 德里克
Widziałem też O_TMPFILE, co prawdopodobnie by pomogło. (i jeśli nie jest dostępny na FS, powinien spowodować błąd)
Evan Benn
@Evan czy przeczytałeś dokument lub zastanawiałeś się kiedyś, dlaczego O_TMPFILE miałby polegać na obsłudze systemu plików?
炸鱼 薯条 德里克
0

Otrzymasz poprawny wynik, robiąc cpwspólnie z mv. Spowoduje to albo zastąpienie „B” świeżą kopią „A”, albo pozostawienie „B”, jak było wcześniej.

cp A B.tmp && mv B.tmp B

aktualizacja w celu dostosowania istniejących B:

cp A B.tmp && if [ ! -e B ]; then mv B.tmp B; else rm B.tmp; fi

To nie jest w 100% atomowy, ale się zbliża. Występują warunki wyścigu, w których dwie z tych rzeczy są uruchomione, obie przystępują do iftestu w tym samym czasie, obie widzą, że Bnie istnieje, a następnie obie wykonują mv.

kaan
źródło
mv B.tmp B nadpisze wcześniej istniejący B. cp A B.tmp nadpisze wcześniej istniejący B.tmp, oba niepowodzenia.
Evan Benn
mv B.tmp Bnie uruchomi się, dopóki cp A B.tmppierwszy nie uruchomi się i nie zwróci kodu wyniku sukcesu. jak to jest porażka? zgadzam się również, że cp A B.tmpnadpisze B.tmpto, co chcesz zrobić. Na &&gwarancji, że 2nd komenda uruchomi wtedy i tylko wtedy, gdy pierwszy zakończy się normalnie.
kaan
W pytaniu sukces definiuje się jako nie zastępowanie istniejącego pliku B. Użycie B.tmp jest jednym mechanizmem, ale również nie może zastąpić żadnego wcześniej istniejącego pliku.
Evan Benn
Zaktualizowałem swoją odpowiedź. Ostatecznie, jeśli potrzebujesz w pełni 100% atomowości, gdy pliki mogą istnieć lub nie, oraz wielu wątków, potrzebujesz gdzieś jednej wyłącznej blokady (utwórz specjalny plik, użyj bazy danych lub ...), którą wszyscy przestrzegają w ramach proces kopiowania / przenoszenia.
kaan
Ta aktualizacja wciąż zastępuje B.tmp i ma warunek wyścigu między testem a mv. Tak, chodzi o to, aby robić rzeczy poprawnie, nie mniej więcej może wystarczająco dobrze. Inne odpowiedzi pokazują, dlaczego blokady i bazy danych nie są potrzebne.
Evan Benn