Удивлен поведением cp с hardlinks

Я очень хорошо понимаю понятие hardlinks и читаю man-страницы для базовых инструментов, таких как 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) на всех других путях назначения, которые делят inode paul . Я думал, что cp создает новый файл и перемещает его в место, ранее занятое старым файлом paul . Вместо этого он пытается открыть существующий файл paul , обрезать его и записать содержимое george в этот существующий файл. Следовательно, любые «другие» файлы с одним и тем же индексом одновременно обновляются «их».

Хорошо, это систематическое поведение, и теперь, когда я знаю, что это возможно, я смогу выяснить, как его обойти или использовать в случае необходимости. Что меня озадачивает, где я должен был видеть это поведение задокументированным? Я был бы удивлен, если бы он не был документирован где-то в документах, на которые я уже смотрел. Но, видимо, я пропустил это и теперь не могу найти источник, который обсуждает это поведение.

4 Solutions collect form web for “Удивлен поведением cp с hardlinks”

Во-первых, почему это делается так? Одна из причин имеет исторический характер: так было сделано в Unix First Edition .

Файлы берутся парами; первый открывается для чтения, второй созданный режим 17. Затем первый копируется во второй.

«Создано» относится к системному вызову creat (тот, который лишен пропуски e ), который обрезает существующий файл по данному имени, если он есть.

И вот исходный код cp в Unix Second Edition (я не могу найти исходный код First Edition). Вы можете видеть вызовы для open исходного файла и файла для второго файла; и в качестве улучшения для первого издания, если второй файл является существующим каталогом, cp создает файл в этом каталоге.

Но вы можете спросить, почему в то время это было сделано? Ответ на вопрос «почему Unix изначально сделал это именно так» – это почти всегда простота. cp открывает свой источник для чтения и создает свой пункт назначения – и системный вызов для создания файла перезаписывает существующий файл, открывая его для записи, поскольку это позволяет вызывающему пользователю навязывать содержимое файла по указанному имени независимо от того, существовал ли файл или нет.

Теперь о том, где это задокументировано: на странице руководства FreeBSD .

Для каждого файла назначения, который уже существует, его содержимое перезаписывается, если разрешены разрешения. Его режим, идентификатор пользователя и идентификатор группы не изменяются, если не указана опция -p.

Эта формулировка была представлена по крайней мере еще в 1990 году (назад, когда BSD была 4.3BSD). Аналогичная формулировка на Solaris 10 :

Если target_file существует, cp перезаписывает его содержимое, но режим (и ACL, если применимо), владелец и связанная с ним группа не изменяются.

Ваше дело даже указано в руководстве HP-UX 10 :

Если new_file является ссылкой на существующий файл с другими ссылками, перезаписывает существующий файл и сохраняет все ссылки.

POSIX ставит его стандартно. Цитата из Single UNIX v2 :

Если dest_file существует, выполняются следующие шаги: (…) Дескриптор файла для файла dest_file будет получен путем выполнения действий, эквивалентных функции open () функции XSH, вызываемой с использованием dest_file в качестве аргумента пути, и побитового включения OR O_WRONLY и O_TRUNC как аргумент oflag.

Страницы руководства и спецификация, которые я цитировал далее, указывают, что если параметр -f передан, и попытка открыть / создать целевой файл не удалась (как правило, из-за отсутствия разрешения на запись файла), cp пытается удалить цель и создать файл снова. Это нарушит жесткую ссылку в вашем сценарии.

Возможно, вы захотите сообщить об ошибке документации в отношении руководства GNU coreutils , так как оно не документирует это поведение. Даже описание --preserve=links , которое в вашем сценарии приведет к --preserve=links на paul и созданию нового файла, не дает понять, что происходит без --preserve=links . Описание -f вида подразумевает, что происходит без него, но не говорит о нем («Если копирование без этой опции и существующий файл назначения невозможно открыть для записи, копия завершится неудачно. Однако с помощью –force, …» ).

cp документы, что он перезаписывает файл назначения, если файл назначения уже присутствует. Вы правы, что в нем подробно не указано, что означает «перезаписать», но он определенно говорит «переписать», а не «заменить». Если вы хотите быть педантичным, вы можете утверждать, что «перезаписать» – это именно то, что делает cp , и поведение, которое вы ожидали, было бы правильно названо «replace».

Также обратите внимание, что если cp был «заменить» ранее существовавшие файлы назначения, которые могут быть разумными считаться удивительными или неправильными, вероятно, более чем «переписывание». Например:

  • Если cp сначала удалил старый файл, а затем создал новый, тогда будет промежуток времени, в течение которого файл будет отсутствовать, что было бы удивительно.
  • Если cp сначала создал временный файл, а затем переместил его на место, он должен, вероятно, документировать это, из-за того, что иногда появлялись временные файлы со странными именами … но это не так.
  • Если cp не смог создать новый файл в том же каталоге, что и старый файл из-за разрешений, это было бы неудачно (особенно если он уже удалил старый).
  • Если файл не принадлежал пользователю, выполняющему cp а пользователь, выполняющий cp не был root тогда было бы невозможно сопоставить владельца и разрешения нового файла с файлами нового файла.
  • Если в файле есть специальные атрибуты, о которых cp не знает, они будут потеряны в копии. В настоящее время реализация cp должна надежно воспринимать такие вещи, как расширенные атрибуты, но это было не всегда так. И есть другие вещи, такие как вилки ресурсов MacOS, или, для удаленных файловых систем, в основном что угодно.

Итак, в заключение: теперь вы знаете, что действительно делает cp . Вы никогда не будете удивлены этим снова! Честно говоря, я думаю, что то же самое могло произойти и со мной много лет назад.

Я вижу, что стандарт POSIX 2013 указывает на наблюдаемое поведение . В нем говорится:

  1. Если source_file имеет тип обычного файла, должны выполняться следующие шаги:

    а. … если dest_file существует, должны быть выполнены следующие шаги:

    я. Если опция -i действует, утилита cp должна написать запрос к стандартной ошибке и прочитать строку со стандартного ввода. Если ответ не утвердительный, cp ничего не должен делать с исходным файлом и переходить к остальным файлам.

    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 записать диагностическое сообщение в стандартную ошибку и перейти к шагу 3e.

    е. Дескриптор файла должен быть закрыт.

Если вы можете сказать: «копирование файла в путь назначения» также копирует один и тот же файл (тот же inode) ко всем другим маршрутам назначения, которые делят paul inode ». Извините, что вы не понимаете, понятие жестких ссылок очень хорошо. Если я дам яблоко сэру Маккартни, я дал яблоко Павлу, и я дал яблоко партнеру по написанию Джона Леннона. Но я не выдал три яблоки; Я дал яблоко человеку, у которого есть несколько имен / названий / дескрипторов.

Точно так же, когда вы копируете george в paul , вы также не копируете его в john . Скорее, вы копируете данные george в файл, на индекс которого указана запись каталога paul .

Шаг за шагом: когда вы делаете

 echo john > john 

вы создали новый файл (предполагая, что в этом каталоге еще нет файла с именем john ). Или, если говорить более строго, это предполагает, что в этом каталоге еще не было записи каталога с именем john (потому что, строго говоря, в каталогах нет файлов, только записи в каталогах, которые указывают на inodes). После того, как вы

 cp -l john paul 

или

 ln john paul 

вы не создали новый файл; скорее, вы дали вашему существующему файлу новое имя. Теперь у вас есть файл с двумя именами: john и paul . И когда вы говорите

 cp george paul 

вы переписываете этот файл . Тот факт, что у него есть два имени, не имеет значения; он может иметь 42 имени, возможно, в тех местах, к которым вы даже не можете получить доступ, и эта команда не будет копировать данные george\n во все эти имена (пути); он просто копирует данные в один файл с несколькими именами.

  • Может ли жесткая связь нарушить структуру файловой системы?
  • Несколько дистрибутивов Linux или Unix, одинаковый домашний раздел или данные?
  • Преобразование идентичных файлов в hardlinks
  • Как скопировать каталоги с сохранением жестких ссылок?
  • Rm без удаления самого файла / каталога
  • Недопустимая ссылка между устройствами в режиме Hardlinking в той же файловой системе
  • Как указать тип файла в жесткой ссылке
  • Как разбить две папки по инодам
  • cp переписывание без перезаписи жестких ссылок на место назначения
  • Действительно ли жесткие ссылки занимают столько места на диске?
  • файл hardlink / softlink в один файл
  • Linux и Unix - лучшая ОС в мире.