Здивований поведінкою cp з жорсткими посиланнями


20

Я дуже добре розумію поняття жорстких посилань і неодноразово читав головні сторінки для основних інструментів, таких як cp--- та навіть останні специфікації POSIX ---. І все-таки я здивовано спостерігав таку поведінку:

$ echo john > john
$ cp -l john paul
$ echo george > george

У цей момент johnі paulбуде однаковий inode (і зміст), і georgeбуде відрізнятися в обох аспектах. Тепер ми робимо:

$ cp george paul

У цей момент я очікував georgeі paulмати різні номери inode, але однаковий зміст --- це очікування було виконано ---, але я також очікував, paulщо тепер буде інше число inode від john, і для того, johnщоб все-таки мати вміст john. Тут я здивувався. Виявляється, що копіювання файлу в шлях призначення paulтакож має результат встановлення того самого файлу (того ж inode) на всіх інших шляхах призначення, які мають спільний paulinode. Я думав, що cpстворює новий файл і переміщує його на місце, яке раніше займав старий файл paul. Замість цього, здається, відкрити існуючий файл paul, обрізати його та написатиgeorgeвміст у тому наявному файлі. Отже, будь-які "інші" файли з тим самим inode одночасно оновлюють "свій" вміст.

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

Відповіді:


4

По-перше, чому це робиться саме так? Одна з причин є історичною: саме так було зроблено в Unix First Edition .

Файли беруться парами; перший відкривається для читання, другий створений режим 17. Потім перший копіюється у другий.

"Створено" позначає creatсистемний виклик (той, у якого, очевидно, відсутній e ), який обрізає існуючий файл за вказаним іменем, якщо такий є.

А ось вихідний код cpу Unix Second Edition (я не можу знайти вихідний код First Edition). Ви можете бачити дзвінки до openвихідного файлу та creatдругого файлу; і, як покращення до Першої версії, якщо другий файл є вже наявним каталогом, cpстворює файл у цьому каталозі.

Але, ви можете запитати, чому це було зроблено саме тоді? Відповідь "чому Unix спочатку робив це так" майже завжди є простотою. cpвідкриває джерело для читання і створює його призначення - і системний виклик для створення файлу перезаписує існуючий файл, відкриваючи його для запису, оскільки це дозволяє абоненту нав'язувати вміст файлу за вказаним іменем, чи вже існував файл або ні.

Тепер про те, де це зафіксовано: на сторінці FreeBSD .

Для кожного файлу призначення, який вже існує, його вміст перезаписується, якщо дозволяють дозволи. Його режим, ідентифікатор користувача та ідентифікатор групи не змінюються, якщо не було вказано параметр -p.

Таке формулювання було принаймні ще в 1990 році (тоді, коли BSD склав 4,3BSD). Існує подібне формулювання в Solaris 10 :

Якщо target_file існує, cp перезаписує його вміст, але режим (та ACL, якщо це застосовно), власник та пов’язана з ним група не змінюються.

Ваш випадок навіть прописаний у посібнику HP-UX 10 :

Якщо new_file - це посилання на існуючий файл з іншими посиланнями, перезаписує існуючий файл і зберігає всі посилання.

POSIX ставить його у стандарти. Цитування з одного UNIX v2 :

Якщо dest_file існує, виконуються наступні кроки: (…) Дескриптор файлу dest_file буде отриманий, виконавши дії, еквівалентні функції XSH специфікації open (), викликаної з використанням dest_file як аргументу шляху, і побітовим включенням АБО O_WRONLY та O_TRUNC як аргумент регламенту.

Сторінки man і специфікації, які я цитував далі, вказує, що якщо -fпараметр буде передано, а спроба відкрити / створити цільовий файл не вдасться (як правило, через відсутність дозволу на запис файлу), cpнамагається видалити ціль і створити файл знову . Це перерве міцний зв’язок у вашому сценарії.

Ви можете повідомити про помилку в документації щодо посібника GNU coreutils , оскільки він не документально підтверджує цю поведінку. Навіть з опису --preserve=links, який у вашому сценарії призведе до paulвидалення посилання та створення нового файлу, не дає зрозуміти, що відбувається --preserve=links. Опис -fроду передбачає те, що відбувається без нього, але не вимовляє його ("Коли копіювання без цієї опції і існуючий файл призначення не може бути відкрито для запису, копія не працює. Однак, з --force, ...").


чому ви говорите "тому, що це дозволяє абоненту взяти право власності на ім'я файлу, чи існує вже файл чи ні"? Cp не приймає право власності на наявний файл.
jrw32982 підтримує Моніку

@ jrw32982 Я мав на увазі право власності в сенсі вирішення того, що йде у файл, а не власності в сенсі метаданих файлу. Я переписав це речення.
Жил "ТАК - перестань бути злим"

20

cpдокументи, що він перезаписує файл призначення, якщо файл призначення вже присутній. Ви маєте рацію, що в ньому детально не вказано, що означає "перезаписати", але напевно сказано "перезаписати", а не "замінити". Якщо ви хочете бути педантичними, ви можете стверджувати, що "перезапис" - це саме те, що cpробить, і поведінка, яку ви очікували, буде правильно називатися "замінити".

Також зауважте, що якщо cp"замінити" попередньо існуючі файли призначення, це, можливо, вважатиметься дивним або неправильним, ймовірно, більше, ніж "перезаписом". Наприклад:

  • Якщо cpспочатку видалити старий файл, а потім створити новий, тоді буде проміжок часу, протягом якого файл буде відсутній, що було б дивно.
  • Якщо cpспершу створили тимчасовий файл, а потім перенесли його на місце, то він, ймовірно, повинен це документувати, завдяки тому, що такі тимчасові файли із незнайомими іменами час від часу помічаються ... але це не так.
  • Якщо cpчерез дозволи не вдалося створити новий файл у тому ж каталозі, що і старий файл, тоді це було б прикро (особливо, якщо він вже видалив старий).
  • Якщо файл не належить управлінню користувачем cpі працює користувач cpні , rootто було б неможливо , щоб відповідати власнику & дозвіл нового файлу тим нового файлу.
  • Якщо файл має фантазійні спеціальні атрибути, про які cpне відомо, то вони втрачаються в копії. Сьогодні реалізація cpповинна надійно розуміти такі речі, як розширені атрибути, але це було не завжди так. І є інші речі, наприклад, вилки ресурсів MacOS або, віддалені файлові системи, в основному будь-що.

Отже, на закінчення: тепер ви знаєте, що cpнасправді робить. Ви ніколи цього не здивуєте! Чесно кажучи, я думаю, що те саме могло трапитися і зі мною багато років тому.


Потрібно перевірити посилання POSIX, але насправді manсторінки для cpBSD (принаймні, OSX) та Gnu версій cpне настільки явні щодо "перезапису". Це слово використовується лише в коментарях до варіантів -iта -n. Сторінка Gnu є особливо неінформативною, починаючи з сторінки Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.BSD / Mac, принаймні, сказаноIn the first synopsis form, the cp utility copies the contents of the source_file to the target_file.
сумнівно

Інформаційна сторінка Gnu coreutils розпочинається:‘cp’ copies files (or, optionally, directories). The copy is completely independent of the original.
сумнівним

2
Я бачу, що стандарт POSIX 2008 дійсно визначає спостережувану поведінку; Я додам відповідь.
сумнівним

16

Я бачу, що стандарт POSIX 2013 дійсно визначає спостережувану поведінку . Він говорить:

  1. Якщо файл source_file має звичайний файл типу, слід зробити наступні кроки:

    а. ... якщо dest_file існує, слід зробити наступні кроки:

    i. Якщо -iопція діє, cpутиліта повинна записувати стандартну помилку та читати рядок зі стандартного вводу. Якщо відповідь не є позитивною, cpне слід більше нічого робити з source_file і переходити до будь-яких інших файлів.

    ii. Файл дескриптор для dest_file повинен бути отриманий шляхом виконання дії еквівалентно open()функції , визначеної в обсязі системи Інтерфейси POSIX.1-2008 викликається з використанням dest_file в якості аргументу шляху, і побітовий включені ORв O_WRONLYі в O_TRUNCякості Oflag аргументу.

    iii. Якщо спроба отримати дескриптор файлу не вдалася, а -fпараметр діє, слід cpспробувати видалити файл, виконавши дії, еквівалентні unlink()функції, визначеній в томі системних інтерфейсів POSIX.1-2008, викликаних з використанням dest_file в якості аргументу шляху. Якщо ця спроба виявиться успішною, слід cpпродовжити етап 3b.

    ...

    г. Вміст source_file записується в дескриптор файлу. Будь-які помилки в записі повинні спричинити cpнаписання діагностичного повідомлення до стандартної помилки та продовжити крок 3е.

    е. Дескриптор файлу закривається.


1
Цікаво. Як і ви, я припускав, що cpце дасть подібні результати mvта порушить будь-які жорсткі посилання, до яких входила доля. Але тепер, коли я замислююся над цим, це означало б, що воно повинно було б конкретно вказати unlink(2)ціль ( cp -f), або створити тимчасово інше ім'я, а потім rename(2)його. Безпосередня реалізація полягає у тому, щоб просто відкрити файл для перезапису, для чого потрібен POSIX. Це еквівалентноcat src > dest
Пітер Кордес

2

Якщо ви можете сказати: "копіювання файлу до шляху призначення paul також копіює той самий файл (той самий inode) на всі інші шляхи призначення, які мають спільний paulinode". Прошу пробачення, що ви не розумієте поняття " жорсткі посилання дуже добре. Якщо я дам яблуко серу Маккартні, я дав яблуко Полу, і я дав яблуко партнеру з написання пісень Джона Леннона. Але я не видав трьох яблук; Я дав яблуко людині, яка має кілька імен / назв / дескрипторів.

Точно так же, коли ви копіюєте georgeдо paul, ви не також копіювати його john. Швидше, ви копіюєте georgeдані у файл, на вкладку якого вказує paulзапис каталогу.

Крок за кроком:   коли ви це зробите

echo john > john

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

cp -l john paul

або

ln john paul

ви не створили новий файл; скоріше, ви дали вашому існуючому файлу нове ім’я. Тепер у вас є файл з двома іменами: johnі paul. І коли ти кажеш

cp george paul

ви перезаписуєте цей файл . Той факт, що він має дві назви, не має значення; він може мати 42 імена, можливо, в місцях, до яких ви навіть не можете отримати доступ, і ця команда не буде копіювати george\nдані на всі ці імена (шляхи); це просто копіювання даних у один файл, який має кілька імен.


1
Спасибі. Правильно, я усвідомлював необхідний персонаж, який вимагає цитат, що я писав, коли я його писав: johnі paulпочати як два імені для одного файлу. Але це було найпростішим способом, який я міг придумати, щоб висловити себе. Я не думаю, що просте поняття міцного зв’язку, правильно зрозуміле, диктує будь-яку з двох форм поведінки cp(без -l).
сумнівним

Але дякую за пробування; Я намагався уточнити формулювання.
сумнівним
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.