Як скопіювати файл транзакційно?


9

Я хочу скопіювати файл з А в В, який може бути в різних файлових системах.

Є деякі додаткові вимоги:

  1. Копія - це все або нічого, жоден частковий або пошкоджений файл B не залишається на місці при збої;
  2. Не перезаписувати наявний файл B;
  3. Не змагайтеся з одночасним виконанням однієї і тієї ж команди, щонайменше, одна може досягти успіху.

Я думаю, що це наближається:

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

Але 3. порушується тим, що CP не виходить з ладу, якщо існує B.part (навіть із прапорцем -n). Згодом 1. може вийти з ладу, якщо інший процес "виграє" CP і файл, пов'язаний на місці, є неповним. B.part також може бути неспорідненим файлом, але я радий зазнати невдачі без спроб інших прихованих імен у цьому випадку.

Я думаю, що bash noclobber допомагає, чи працює це повноцінно? Чи є спосіб отримати без вимоги версії bash?

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

Подальші відомості, я знаю, що деякі файлові системи в цьому випадку не вдасться (NFS). Чи є спосіб виявити такі файлові системи?

Деякі інші, але не зовсім ті самі питання:

Приблизний атомний хід через файлові системи?

Чи є mv атомний на моєму фс?

чи є спосіб атомним переміщенням файлів і каталогів з tempfs в розділ ext4 на eMMC

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


2
Ви стурбовані лише одночасним виконанням тієї самої команди (тобто, можливо, блокування у вашому інструменті) чи іншими зовнішніми втручаннями у файли?
Майкл Гомер,

3
"Трансакційний" може бути кращим
муру

1
@MichaelHomer в інструменті досить хороший, я думаю, що зовні це зробить дуже важкі речі! Якщо це можливо з блокування файлів, хоча ...
Еван Бенн,

1
@marcelm mvзамінить існуючий файл B. mv -nНе сповістить про помилку . ln(1)( rename(2)) не вдасться, якщо B вже існує.
Еван Бенн

1
@EvanBenn Добрий момент! Я мав би краще прочитати ваші вимоги. (Я, як правило, потребую атомних оновлень існуючої цілі, і я відповідав на це на увазі)
marcelm

Відповіді:


11

rsyncробить цю роботу. Тимчасовий файл O_EXCLстворюється за замовчуванням (вимикається лише якщо ви використовуєте --inplace), а потім renamedнад цільовим файлом. Використовуйте, --ignore-existingщоб не перезаписати B, якщо він існує.

На практиці я ніколи не відчував проблем із цим на кріпленнях ext4, zfs або навіть NFS.


rsync, ймовірно, робить це добре, але надзвичайно складна сторінка людини мене лякає. варіанти, що передбачають інші варіанти, несумісні між собою тощо
Еван Бенн,

Наскільки я можу сказати, Rsync не допомагає вимозі №3. І все-таки це фантастичний інструмент, і вам не слід ухилятися від читання читацької сторінки. Ви також можете спробувати або github.com/tldr-pages/tldr/blob/master/pages/common/rsync.md або cheat.sh/rsync . (tldr та чіт - це два різних проекти, які мають на меті допомогти у вирішенні вказаної вами проблеми, наприклад, "man page - TL; DR"; підтримується багато загальних команд, і ви побачите найбільш показані звичаї.
sitaram

@EvanBenn rsync - це дивовижний інструмент і варто вартувати навчання! Сторінка man є складною, оскільки вона така універсальна. Не
Josh

@sitaram, №3 можна вирішити файлом pid. Невеликий сценарій, як у відповіді тут .
Роберт

2
Це найкраща відповідь. Rsync - це галузевий стандарт переходу атомних файлів, і в різних конфігураціях може задовольнити всі ваші вимоги.
wKavey

4

Дякую, спокусився прийняти цю стислу відповідь. Будь-який коментар щодо химерних файлових систем, таких як NFS?
Еван Бенн

@EvanBenn, я мав намір додати, що я не впевнений, чи NFS хоч якось зіпсує тебе тут, але я забув.
ilkkachu

4

Ви запитали про NFS. Цей тип коду, швидше за все, порушиться під NFS, оскільки перевірка на наявність noclobberвключає дві окремі операції NFS (перевірити, чи існує файл, створити новий файл), і два процеси з двох окремих клієнтів NFS можуть потрапити в перегоновий стан, коли вони обидва досягають успіху ( обидва переконайтесь, що B.partще не існує, а потім обидва приступають до успішного створення, в результаті вони перезаписують один одного.)

Насправді не потрібно робити загальну перевірку того, чи підтримуватиме файлова система, для якої ви пишете, noclobberатомарне чи ні. Ви можете перевірити тип файлової системи, чи це NFS, але це було б евристичним, а не обов'язково гарантією. Такі файльні системи, як SMB / CIFS (Samba), швидше за все, будуть страждати. Файлові системи відкриваються через FUSE можуть або можуть поводитись неправильно, але це в основному залежить від реалізації.


Можливий кращий підхід - уникнути зіткнення на B.partетапі, використовуючи унікальне ім’я файлу (через співпрацю з іншими агентами), щоб вам не потрібно було залежати від цього noclobber. Наприклад, ви можете включити, як частину імені файлу, ваше ім'я хоста, PID та часову позначку (+ можливо випадкове число.) Оскільки у будь-який момент часу повинен бути єдиний процес, що працює під певним PID у хості, це має бути гарантія унікальності.

Тож один із:

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.

Або:

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"

Отже, якщо у вас є умова перегонів між двома агентами, вони обидва продовжать операцію, але остання операція буде атомною, тому або B існує з повною копією A, або B не існує.

Ви можете зменшити розмір гонки, перевіривши ще раз після копії та перед mvабо lnоперацією, але там все ще є невеликий стан гонки. Але, незалежно від умови перегонів, вміст B повинен бути послідовним, якщо припустити, що обидва процеси намагаються створити його з A (або копії з дійсного файлу як джерела.)

Зауважте, що в першій ситуації mv, коли гонка існує, останній процес - це той, хто виграє, оскільки перейменування (2) атомно замінить існуючий файл:

Якщо newpath вже існує, він буде атомно замінений, так що немає жодного моменту, коли інший процес, який намагається отримати доступ до newpath, визнає його відсутнім. [...]

Якщо newpath існує, але операція з якоїсь причини не вдається, rename()гарантує залишити екземпляр newpath на місці.

Отже, цілком можливі процеси, що споживають B у той час, можуть бачити різні його версії (різні вставки) під час цього процесу. Якщо автори просто намагаються скопіювати один і той же вміст, а читачі просто споживають вміст файлу, це може бути добре, якщо вони отримають різні вставки для файлів з однаковим вмістом, вони будуть щасливі однаково.

Другий підхід із використанням жорсткого посилання виглядає краще, але я пам’ятаю, що робив експерименти із жорсткими посиланнями в тісному циклі на NFS від багатьох одночасних клієнтів і рахував успіх, і все ще, здавалося, існували деякі умови гонки там, де, здавалося, два клієнти видали жорстке посилання операція одночасно, з одним і тим же призначенням, обидва здавалося успішними. (Можливо, що така поведінка була пов’язана з конкретною реалізацією сервера NFS, YMMV.) У будь-якому випадку, це, мабуть, той самий вид перегонів, де ви, в кінцевому підсумку, отримаєте два окремих введення для одного файлу у випадках, коли важкі паралельність між авторами, щоб викликати ці перегони. Якщо ваші автори послідовні (обидва копіюють від А до Б), а ваші читачі споживають лише вміст, цього може бути достатньо.

Нарешті, ви згадали про блокування. На жаль, блокування сильно не вистачає, принаймні, у NFSv3 (не впевнений у NFSv4, але я б обміняв, що це теж не добре.) Якщо ви розглядаєте можливість блокування, вам слід розглянути різні протоколи розподіленого блокування, можливо, поза діапазоном із фактичні копії файлів, але це є як руйнівним, складним, так і схильним до таких питань, як тупики, тому я б сказав, що цього краще уникати.


Для отримання додаткової інформації щодо атомності на NFS, ви можете прочитати у форматі поштової скриньки Maildir , який був створений для уникнення блокування та надійної роботи навіть на NFS. Це робиться, зберігаючи унікальні імена файлів скрізь (так що ви навіть не отримаєте остаточний B в кінці).

Можливо, дещо цікавіше вашому конкретному випадку, формат Maildir ++ розширює Maildir, щоб додати підтримку квоти поштової скриньки, і робить це шляхом атомного оновлення файлу з фіксованим іменем всередині поштової скриньки (щоб це було ближче до вашого Б.). Я думаю, що Maildir ++ намагається додавати, що насправді не є безпечним для NFS, але існує підхід для перерахунку, який використовує процедуру, аналогічну цій, і діє як атомна заміна.

Сподіваємось, всі ці покажчики будуть корисні!


2

Ви можете написати програму для цього.

Використовуйте, open(O_CREAT|O_RDWD)щоб відкрити цільовий файл, прочитати всі байти та метадані, щоб перевірити, чи цільовий файл є повним, якщо ні, то є дві можливості,

  1. Неповне написання

  2. Інший процес працює за тією ж програмою.

Спробуйте придбати блокування опису відкритого файлу на цільовому файлі.

Відмова означає, що є паралельний процес, поточний процес повинен існувати.

Успіх означає, що минуле запитання закінчилось, вам слід почати спочатку або спробувати виправити це, записавши у файл.

Також зауважте, що вам краще fsync()після запису до цільового файлу, перш ніж закрити файл і відпустити блокування, або інший процес може прочитати дані, які ще не є на диску.

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

Це важливо, щоб допомогти вам розрізнити одночасно запущену програму та останнє збій роботи.


Дякую за інформацію, я зацікавлений, щоб реалізувати це сам, і я підкажу це. Я здивований, що вона вже не існує як частина ядра / подібного пакету!
Еван Бенн

Цей підхід не може відповідати жодному частковому або пошкодженому файлу B, залишеному на місці за вимогою аварії Дійсно найкраще використовувати стандартний підхід копіювання файлу до тимчасового імені, а потім переміщення його на місце: хід може бути атомним, копіювання якого не може бути.
reinierpost

@reinierpost Якщо збій, але дані не скопійовані повністю, частково скопійовані дані залишаться незважаючи ні на що. Але мій підхід виявить це і виправить. Переміщення файлу не може бути атомним, будь-які дані, записані на дисковий міжфіз. Сектор, не будуть атомними, але програмне забезпечення (наприклад, драйвер файлової системи ОС, такий підхід) може виправити це (якщо rw) або повідомити про послідовний стан (якщо ro) , про що йдеться у коментарному розділі запитання. Також питання стосується копіювання, а не переміщення.
炸鱼 薯条 德里克

Я також бачив O_TMPFILE, який, ймовірно, допоможе. (і якщо вона не доступна на FS, це може призвести до помилки)
Еван Бенн,

@ Чи читали ви документ чи коли-небудь думали, чому O_TMPFILE покладається на підтримку файлової системи?
炸鱼 薯条 德里克

0

Правильний результат ви отримаєте, cpспільно з mv. Це або замінить "B" на свіжу копію "A", або залишить "B", як це було раніше.

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

оновлення для розміщення існуючих B:

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

Це не 100% атомне, але воно наближається. Існує умова гонки, коли працюють дві з цих речей, обидва входять в ifтест одночасно, обидва бачать, що Bне існує, а потім обидва виконують тест mv.


mv B.tmp B замінить попередній B. cp A B.tmp замінить попередній B.tmp, обидва відмови.
Еван Бенн

mv B.tmp Bне запускається, якщо cp A B.tmpспочатку не запускається і не повертається результат результату успіху. як це невдача? Крім того, я погоджуюся, що cp A B.tmpзамість існуючого B.tmp, що ви хочете зробити. Ці &&гарантії , що друга команда буде працювати , якщо і тільки якщо перший один завершується нормально.
Каан

У питанні успіх визначається як не перезаписування наявного файлу B. Використання B.tmp є одним із механізмів, але також не повинно перезаписувати жодного попереднього файлу.
Еван Бенн

Я оновив свою відповідь. Зрештою, якщо вам потрібна повністю 100% атомність, коли файли можуть бути, а можуть і не існувати, і кілька потоків, вам потрібно десь один ексклюзивний замок (створити спеціальний файл, або використовувати базу даних, або ...), за яким усі слідкують як частину процес копіювання / переміщення.
Каан

Це оновлення все ще переписує B.tmp та має перегоновий стан між тестом та mv. Так, справа в тому, щоб робити все правильно не приблизно, можливо, досить добре, сподіваємось. Інші відповіді показують, чому блокування та бази даних не потрібні.
Еван Бенн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.