вы в них встретитесь, так же присущи и другим операционным системам (кое-что можно, например, найти и в Windows), основная же «проблема» - гибкость Unix, которая необходима для нормальной многопользовательской работы.
В очередной заметке нашего гуру Игоря Облакова, ведущего постоянную рубрику, посвященную деталям функционирования ОС Unix и Unix-подобных систем, обсуждаются премудрости файловой системы.
Читателю для начала предлагается побыть в роли Ивана Павлова. Роль собачек выполнят команды cd и pwd, трубочки же мы организуем при помощи ln -s.
«Эти забавные животные»
Обращали ли вы внимание на такие забавные команды, как cd и pwd? Конечно же, вы пользовались ими много раз — что же в них забавного? Тогда попробуйте ответить на простой вопрос: что должна выдавать команда pwd в том случае, когда текущим каталогом является /a/b/c/d/e? Вы скажете, что именно /a/b/c/d/e команда и должна выдавать.
Иногда это действительно так. Но вот всегда ли? Задам наводящие вопросы. Зависит ли результат от каких-либо параметров контекста процесса? Зависит ли он от того, как мы именно пришли к данному каталогу? От каких-либо «фокусов» самой файловой системы?
Зависимость от контекста процесса очевидна. Например, команда chroot позволяет переопределить корень дерева для процесса и, как следствие, изменить имя выдаваемого каталога на экран. Интереснее второй вопрос.
Какая же это может быть зависимость от пути? Дерево - оно и в Африке дерево. Уж если я сижу на ветке, как бандерлог какой-нибудь, ствол и корень я как-нибудь найду. Тут есть три НО. Во-первых, дерево файлов Unix не совсем дерево; во-вторых, команда pwd — не бандерлог и имеет собственное разумение о том, что и как выдавать на экран; в-третьих, pwd часто существует в двух ипостасях - встроенная команда и выполняемый файл /bin/pwd.
Начнем, как это часто бывает, с конца. В чем разница в поведении встроенной команды и выполняемого файла /bin/pwd? Обычно встроенная команда pwd выдает на экран не полный путь к текущему каталогу, а значение некоторой собственной переменной (видимой снаружи, например, как PWD), которая, как мы увидим далее, не обязана с ним совпадать (есть зависимость поведения от типа командного интерпретатора!). /bin/pwd - честный малый и сделает попытку слезть с дерева и раскопать корень. Попробуем подсунуть командному интерпретатору какой-либо магнит под компас и посмотреть результат. Например, проанализируем выполнение следующих команд:
ln -s /a/b/c/d/e /baecd /bae
pwd
/bin/pwd
Если наш интерпретатор использует PWD-метод, то встроенная команда pwd выдаст на экран именно /bae, а вот /bin/pwd выдаст /a/b/c/d/e.
Такое поведение pwd иногда оказывается достаточно удобным, особенно при использовании автоматического монтирования удаленных систем по обращению к файлам - в этом случае в некотором стандартном месте создается набор ссылок, а нам в качестве каталога выдается что-либо лирическое типа /home/mydir. Иногда же, когда нужно точно определить местоположение файлов на дисках, оно раздражает (целых 5 клавиш нажать!).
Теперь вопрос «на засыпку». Что явится результатом команды cd ..?Вроде бы, логично предположить, что это должен быть каталог /a/b/c/d. Опять мимо. Можем очутиться и в корневом каталоге. Фокус заключается в том, что переход выполняется тоже с использованием собственной переменной; для выбора нового каталога отбрасывается последнее имя в /bae и остается /.
Такое поведение cd оказывается удобным в том случае, когда требуется, например, разделить какой-нибудь каталог (скажем, места не хватило), перебросить его часть на другой том и заменить его ссылкой. Часть командных файлов будет вполне корректно бродить по такому разрывному дереву. Впрочем, корректность эта зависит от необходимости запускать новые процессы. Например:
cd /bae
pwd
bash
pwd
В этом случае pwd нового интерпретатора в Linux выдаст полное имя. В других разновидностях Unix это может быть и не так.
А как поведет себя в этих условиях ls? Например:
ls /bae/..
ls - также достаточно прямолинейная команда. Вы обнаружите, что в каталоге /bae/.. (т.е. /a/b/c/d) лежит каталог e.
КГБ знает все...
Вернемся к монтированию удаленных систем. Предположим, что нам позволили смонтировать собственную мусорную кучу (/tmp). Заглядываем в смонтированный каталог /remtmp и с ужасом обнаруживаем там файл passwd, в точности совпадающий с вашим, быть может, еще и скрытым.
«О горе мне!» - восклицаете вы, ведь вы никому не давали возможности входить на ваш компьютер - и вот кто-то таки стащил файл. Более подробный анализ показывает, что passwd был создан на удаленной системе командой ln -s /etc/passwd passwd. Никто не крал ваш файл, вы просто наблюдали один из фокусов NFS. Похожий HTML-фокус под леденящим душу лозунгом (типа «КГБ знает все, даже то, что у вас на диске») вы можете, например, найти на страничке Сергея Богомолова (http://www.deol.ru/~bog/).
Вопрос в одном - кто должен выполнять подстановку имени, т.е. локальный или удаленный компьютер. Оказывается, это делает локальный компьютер. Поэтому, например, если ваша программа откроет файл passwd на удаленном компьютере, то вы не получите доступ к данным удаленного компьютера. Ссылки этого типа не позволяют получить доступ к данным за пределами смонтированного каталога. Аналогично ведет себя и Windows. Если, например, в «нашаренном» каталоге сервера вы обнаруживаете ярлык (shortcut), то, обращаясь к нему, вы попытаетесь получить доступ к локальному файлу.
Если в момент сбоя питания или другой фатальной ситуации Unix теряет файл, то при достаточном везении его можно отыскать в каталоге lost+found. Поскольку ОС в этом случае не знает корректного имени файла, то генерирует имя в соответствии с inode файла. В некоторых вариантах Unix это может быть и что-нибудь типа #00007. Если окажется, что данный файл - каталог, пользователь, конечно, немедленно наберет для проверки его содержимого команду:
cd #00007
и окажется в каталоге HOME. Мало кому удавалось не впасть в такой ситуации в ступор прежде, чем он понимал, как подло его обманули с именем файла.
Продолжение темы
Среди присланных ответов на мой вопрос о rm (см. первый номер «Открытых систем» за этот год) имеется следующий (А. Векшин):
a=`pwd`; cd ..; rm -rf $a; mkdir $a;cd $a
В ходе диалога с использованием исключительно парламентских выражений (вот как у нас ценят «журнальных гуру»), автору было показано, что после выполнения модернизации файловых утилит Linux, данное решение (если не считать таких «мелочей», как возможная неэквивалентность структуры дерева, прав доступа и т.п., что, впрочем, легко преодолевается) в принципе корректно обходит проблемы перечисленные в упомянутом номере журнала. Положа руку на сердце, нельзя не признать, что автор использует аналогичные методы. Однако, данный командный файл не обязан работать в любой ситуации. Пользуясь исключительно (есть и другие возможности) вышеприведенными фактами, попробуйте «скомпрометировать» данный командный файл. Вариантов может оказаться много. Обратите внимание, что это может произойти в результате достаточно стандартных операций администрирования.
Больше «инодов» хороших и разных
Как известно, для каждого носителя (секции или логического тома) в unix имеется специальная пронумерованная (проиндексированная) таблица описателей отдельных файлов - таблица inode. Номер каждого файла можно посмотреть командой ls -i. Легко можно обнаружить, что часть фалов имеет совпадающие номера, например, можно заметить, что sed, vi, ex имеют один номер. Что это означает? Это означает, что sed, vi и ex различные имена одного и того же файла. Иначе их называют физическими ссылками (в отличие от символьных). Только после того, как исчезнет последняя ссылка на файл, происходит реальное освобождение места. Количество ссылок на файл выдается, например, просто командой ls -l. Какие файлы имеют больше всего ссылок? Обычно каталоги. На каждый каталог ссылается (по «законному» имени) папочка, сам каталог (в виде .) и все подкаталоги (в виде ..).
Что хранится в каталоге? Фактически это просто таблица преобразования имени в число (возможно, с использованием хэширования и т.п.). Иногда каталог можно открыть как обычный файл (только на чтение) и посмотреть ее структуру.
Итак, мы видим, что обычно число файлов в файловой системе больше числа различных inode. А вот всегда ли это так? Тут явно не хватает определенности. Ясно, что, если говорить о количестве различных чисел, которые вы можете увидеть при помощи ls -i, то их не может быть больше, чем количество файлов в системе. А вот если же говорить непосредственно о занятых позициях в таблице описателей файлов, то все не так очевидно.
Что обычно находится в таблице описателя файла? Тип файла, код владельца, группы, количество ссылок, адреса блоков, различные времена и т.п. (иногда даже код отложенной операции для наката транзакции в случае сбоя). Обычно, речь идет о нескольких десятках или даже сотне-другой байт. Согласитесь, не так уж и мало по сравнению с размером небольшого файла, например символьной ссылки, которая только имя-то и хранит. Более того, иногда размера элемента таблицы хватает для того, чтобы хранить небольшой каталог. Иногда в Unix проделывается подобного рода оптимизация, т.е. если файл имеет один из предопределенных типов и размер файла таков, что его можно засунуть непосредственно в элемент таблицы, то это делается. Иногда Unix поддерживает возможность задания дополнительной («неклассической», если угодно) информации. Например, для файла задается целый список прав доступа (ACL - access control list). Его-то куда девать? Основная проблема в данном случае состоит в динамическом характере задаваемых параметров, мы не можем заранее определить требуемую длину списка. В этом-то случае и полезно для одного файла выделить несколько элементов в таблице описателей - каждому по потребностям.
Как видите, одному файлу может соответствовать несколько элементов таблицы и наоборот (если файлом называть любое имя).
Откуда вообще берется на диске таблица описателей. Один из применяемых алгоритмов для работы с ней предполагает, что таблица полностью создается в момент выполнения команды mkfs. Что из этого следует? Из этого следует, в частности, что вы не можете в данной файловой системе создать файлов больше некоторого числа, которое для вас подобрала система (если вы не задали его явно). Просмотреть это число можно командами типа df -t/df -i или bdf -i. Обычно, система по умолчанию использует некоторую эвристическую константу (что-либо типа 6 Кбайт в среднем на файл?) для того, чтобы вычислить требуемое количество inode в системе. Эта константа может называться «количество байт на inode» и иногда воспринимается как «количество байт необходимых для описания файла», на самом деле это не так (см. описание ключа -i для mke2fs в Linux - по умолчанию 4 Кбайт). В результате имеем следствие второе: если у вас преобладают маленькие файлы, то, может быть, вы не сможете использовать все пространство под файлы - при этом вам не хватит не пространства, а места под описатели. Что делать дальше? Единственный гарантированный метод предполагает перезапись тома на новое место (или на старое через ленту) с, предпочтительно, подкрученными параметрами mkfs.
Можно ли поступить иначе? Это зависит от того, можно ли увеличить пространство, которое занимает файловая система и произойдет ли при этом увеличение количества inode. Заметим, что обычно файловая система занимает какую-то часть диска (дисков), например, секцию или логический том. Соответственно нужно, например, иметь возможность изменения размера секции (тома) и далее необходима какая-то утилита, которая позволит использовать новое пространство в существующей файловой системе - автоматом это не происходит. Если вы работаете на системе, которая поддерживает работу с логическими томами, то, конечно, вы можете увеличить размер тома и первый вопрос снимается (если нет ограничений на расположение данного тома, что тоже возможно, например, в виде требования непрерывности тома участвующего в загрузке). С секциями сложнее, они бывали даже закодированы в включаемых файлах (самый допотопный вариант) с фиксацией в ядре системы, в таблице на диске и т.п. Естественно, в этом случае необходимо, чтобы место было непосредственно за текущей секцией и можно было внести изменения во все таблицы (выполнить «логическое» реформатирование диска; использовать альтернативный набор секций, если были пересекающиеся и т.п.). Далее необходимо изменить размер файловой системы с тем, чтобы воспользоваться всем пространством секции/тома. Управляющая информация о файловой системе может быть сосредоточена в начале или распределена по всему пространству (более современный вариант) секции/тома.
В первом случае увеличения количества inode ждать не приходится (хотя увеличение пространства иногда возможно - это зависит от способа хранения информации о свободных блоках, чаще их хранят в битовой карте, но если это список, то помогает комбинация fsdb/fsck - первый позволяет изменить число блоков, а fsck все «приводит в порядок», т.е. новое пространство собирает в список). Во втором случае требуется специальная программа, т.к. на вновь используемом пространстве должны появиться структуры описания. Но в этой ситуации вы не можете потребовать точного количества inode для нового кусочка (файловая система должна оставаться однородной) и, если новое пространство будет использоваться так же, как старое, то вы потеряете еще больше места зря.
Если же ваша файловая система применяет динамический метод выделения inode, то, конечно, повода плакать по этому поводу у вас не будет.
Нужны ли номера inode обычному пользователю? Есть ли «обычные» программы, которые как-то используют эти номера?
Рассмотрим простейший пример. Пусть у нас есть какие-то файлы a, b и c. Что будет, если мы попробуем команды:
ln b dcat a d c > b
cat обнаружит, что выполняется попытка переписать d сам в себя и заявит об этом; возможная диагностика разнообразна. Иногда говорят, а чего же это он тогда запортил файл b. Проблема в том, что файл b «портит» немедленно командный интерпретатор. Совесть cat почти чиста. Не все программы такие разумные как cat (или, например, cp). Например, Юрий Прилипко наблюдал следующие попытки выполнить одну и ту же команду несколько раз:
grep .... s* > sres.lstПосле первого же выполнения появляется файл sres.lst и далее тоже участвует в «процессе поиска». Если размер sres.lst превышает некоторые пределы (sres.lst для этого должен стоять в списке поиска достаточно далеко) процесс поиска становится неуправляемым, т.к. все, что есть в этом файле, с гарантией проходит через grep.
Как cat обнаруживает повтор, имени выходного файла он не знает (да мы и подменили его). Он анализирует именно inode-номер файла, его можно получить для любого открытого файла. Номер inode уникален в пределах одной смонтированной файловой системы. Соответственно проверив эти два факта, т.е. совпадение номеров файла и монтирования, можно заявить, что два открытых файла совпадают.
Есть ряд процедур, которые тоже пытаются использовать номер для inode и устройства, например, для генерации уникальных ключей доступа к ресурсам межпроцессного взаимодействия - такова функция ftok.
Рассказ об inode, конечно, не закончен и будет продолжен. Присылайте свои вопросы и пожелания по адресу oblakov@bigfoot.com