Вопросы с собеседований. Часть 1. Про hardlink и symlink.

Привет. В процессе собеседований на Linux-администратора испытуемым задается большое количество интересных вопросов. В большинстве своём это отличные векторы для изучения системы и мне показалось хорошей идеей разобрать часть из них. Сегодня поговорим про вопросы, связанные с линками в Linux.

1. Что такое hard-link, чем он отличается от sym-link?
2. Как найти все хард-линки, ведущие в одно и тоже место?

К любому из этих вопросов можно задать усложняющие — простых ответов вида «ln и find» вам может не хватить, поэтому давайте попробуем копнуть глубже.

Начнем с того, что Linux работает с файловыми системами не напрямую, а использует для этого VFS(Virtual File System) — абстракцию, которая позволяет собрать в одном корне партиции с разными файловыми системами. Секрет заключается в том, что интерфейс VFS предоставляет для драйверов файловых систем механизм взаимодействия, который описывает типичные действия с файловой системой(например — создание файла). Схема взаимодействия выглядит так:

Если заглянуть в исходный код ядра — вы увидите, что на уровне VFS есть некоторое количество структур, которыми описываются сущности ФС — в контексте этого поста нас интересует две из них. Первая — dentry, которая позволяет организовать древовидную структуру файловой системы Linux. Давайте посмотрим на её код:

Из интересного здесь стоит отметить следующие элементы: d_parent, d_name, d_inode и d_child. Давайте рассмотрим, как это работает.
Например, мы пытаемся обратиться к /bin/bash. При обращении путь будет разбит на 3 элемента: [ / , bin, bash ] (в первом случае «/» означает корень, представляя отдельную сущность, в остальных — просто разделитель). Каждый из них будет представлен отдельной dentry-записью, имеющей имя d_name, указатель на связанную иноду, а так же — связи вверх и вниз по дереву через элементы d_parent и d_child. Ядро будет скользить вниз по указателям дабы добраться до конечного элемента и посмотреть на его иноду(на самом деле — на попутные иноды из дерева оно тоже будет смотреть, дабы убедиться, что у вас есть права на этот и следующие элементы дерева).

Что касается второй, интересующей нас, структуры, то это Inode(index node) — конструкция, в которой описывается практически всё, что необходимо знать ядру о файле. Листинг кода, описывающий её, можно увидеть ниже.

Здесь есть множество полей, но в контексте рассмотрения линков нас интересует поле i_dentry, которое является списком объектов типа dentry(directory entry) и отвечает за именование элементов.

Таким образом мы видим, что у каждой иноды может быть более одного имени. Именно так и работают хард-линки: создавая хардлинк мы просто добавляем элементы в массив i_dentry(в свою очередь, у созданного объекта dentry в соответствующем поле будет указатель на эту же иноду). Ядро будет считать иноду востребованной до тех пор, пока к ней хоть что-нибудь ведёт(случай, расширяющий этот кейс, мы рассмотрим отдельно).
Обратите внимание на то, что права доступа описываются на уровне иноды, а значит — права будут общими для всех хард-линков, ведущих на одну иноду. Так же в структуре иноды есть указатель на связанное блочное устройство — по этой причине хард-линки работают только в рамках одной точки монтирования.

Теперь давайте поговорим про символические ссылки(symlink). В книге Роберта Лава к этому нашлись весьма косвенные отсылки, поэтому я решил поискать хвосты самостоятельно. Если мы открутим пост в самое начало — увидим, что у структуры dentry есть поле для флагов — d_flags. В том же файле(include/linux/dcache.h) найдется такое:

такое:

и такое:

То есть ядро узнает о том, что попало на симлинк ещё на этапе прохождения по дереву, из специального флага в dentry(к слову, через похожий флаг оно узнает о том, что это mountpoint и дальше начинается другая ФС). Но как оно узнает, куда ведёт ссылка? Очевидно — из внутренностей т.к. симлинка — полноценный объект ФС со своим инодом:

Теперь зайдем с другой стороны и увидим, что символические ссылки делаются через специальный системный вызов symlink:

В исходниках ядра(fs/namei.c) находим объявление этого syscall`a:

Идем смотреть код указанной функции, из неё перебираемся в sys_symlinkat(), потом — в vfs_symlink(), а там находим:

Эту штуку стоит читать так: из иноды директории сходи в структуру i_op и там дерни метод symlink. Дело в том, что i_ops — это под-структура, которая являет собой список указателей на функции для действий с инодами. В этом месте нам пора выбрать свою ФС и перебраться в её драйвер — более подробных особенностей реализации VFS не описывает.

Я выбрал ext4 и решил поверить докам на ядро, а там сказано следующее:

Depending on the type of file an inode describes, the 60 bytes of storage in inode.i_block can be used in different ways. In general, regular files and directories will use it for file block indexing information, and special files will use it for special purposes.

The target of a symbolic link will be stored in this field if the target string is less than 60 bytes long. Otherwise, either extents or block maps will be used to allocate data blocks to store the link target.

Иными словами, если у нас короткий путь в точку назначения(до 60 символов) — пишем в поле i_block самой иноды(которое обычно используется для перечисления привязанных блоков с данными), в противном случае — привязываем блок с данными и пишем в него.

Давайте убедимся, что всё так и попробуем почитать блок данных для имеющейся у нас симлинки(номер её иноды мы взяли выше — это 934556):

Не получилось, что логично — привязанного блока данных нет, но когда мы посмотрели в структуру иноды — увидели, что секция Fast_link_dest в теле самой иноды, которая хранит нужные нам данные.

Теперь сгенерим ссылку такой длины, чтобы её путь не влез в i_block и попробуем снова:

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

Напоследок давайте посмотрим, как искать все хардлинки, ведущие к выбранной иноде. Возьмём, например, /bin/ls , по счастливой случайности у меня к нему был прибит дополнительный хардлинк.

Видим, что ссылок две, а номер иноды — 871. Возьмём find, натравим его на корень и будем искать, у него как раз есть ключик на эту тему:

-samefile name
File refers to the same inode as name. When -L is in effect, this can include symbolic links.

Если запустить find через strace — станет понятно, что никакой магии не происходит, он просто ходит и делает на все файлы newfstatat, и если в ответе на вызов видит такой же номер иноды — выводит имя файла на экран.

На этом у меня всё, спасибо за внимание.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *