Глава 8. Файл как он есть

Введение в POSIX'ивизм
(C) Алексей Федорчук, 2005

Назад Содержание Вперед
Вниз

Глава 8. Файл как он есть

Все сказанное выше может показаться пока не совсем понятным. Однако надеюсь - эта глава внесет ясность в вопрос о китах POSIX'ивизма (а равно баранах и прочих кашалотах). Для чего нам только потребуется замкнуть разорванный в начале раздела круг, и поговорить о файлах. Тема эта очень многогранная, и будет продолжена в следующих главах.

Содержание

Как уже говорилось, все, что существует в POSIX-системе в статическом виде, являет собою файлы. Собственно, использование файловой системы как универсального интерфейса доступа ко всему, чему угодно - от устройств, физически подсоединенных к машине, до процессов, в системе протекающих, - и есть один из критериев Unix-подобия ОС (и ее соответствия стандартам POSIX). Так что пора рассмотреть вопрос о том,

Что такое файл

Помнится, на заре моего приобщения к компьютерам этот вопрос доставил мне немало пищи для размышлений. Встреченное где-то (не у Фигурнова ли?) определение файла как именованной области на диске для хранения данных казалось а) интуитивно непонятным, и б) не соответствующим собственным впечатлениям. И по мере освоения POSIX-систем (сначала Linux'а, а потом и BSD) оказалось, что интуиция меня не подвела. Ибо файл - это отнюдь не область, расположен он не всегда на диске и уж ни в коей мере не привязан к какому-то имени.

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

Предложенное определение может быть применено к любой операционной системе. Конкретно же в Unix'ах всякого рода (сиречь POSIX-совместимых ОСях) к этому следует прибавить, что файл состоит как бы из двух частей, не обязательно находящихся в одном месте (а в общем случае - как раз разобщенных).

Первая часть файла - так называемая область метаданных, именуемая по английски inode (index node или information node - мне встречались обе расшифровки). Нормального русского эквивалента для этого термина не придумано (выражения типа "информационный узел" вряд ли прояснять существо дела, а "индексный дескриптор" назвать русским выражением трудно), и потому далее он будет использоваться без перевода. В inode содержатся некоторые сведения о файле (именуемые атрибутами файла), как то:

  • идентификаторы файла и устройства, на котором он расположен;
  • тип файла;
  • счетчик ссылок;
  • его размер;
  • атрибуты принадлежности, доступа, режима и времени.

Все эти характеристики файла будут рассмотрены в следующих параграфах этого раздела.

Область данных файла заключает в себе собственно ту информацию, для хранения которой файл и предназначен. Она может быть самой различной - текстовые символы, двоичные коды и многое, многое другое. Однако характер этой информации для операционной системы POSIX-типа безразличен (чего нельзя сказать о пользовательских приложениях) - она воспринимает их просто как последовательность байтов.

Область данных не является непременной принадлежностью файла. Существуют файловые системы, которые умудряются разместить данные маленьких файлов в области метаданных, чем достигается как быстрота доступа к хранимой информации, так и экономия дискового пространства. Потому что, не смотря на приведенную выше оговорку, файлы в основном все же хранятся на дисковых устройствах.

Внимательный читатель, привыкший под влиянием DOS/Windows к трактовке файлов в качестве именованных документов, обратил внимание на то, что среди атрибутов файлов нет одного, казалось бы, самого важного, - имени файла. Как же различаются они друг от друга?

Цели идентификации файлов служат числа, которые, как это ни странно, так и называются - идентификаторы. Таковых - два: идентификатор устройства и собственно идентификатор файла. Со вторым все более-менее понятно - это просто порядковый номер (с некоторыми оговорками). И номер этот - уникален для файловой системы. Именно по нему ОС считывает информацию из конкретного файла - то есть из области его данных, поставленных в однозначное соответствие с файловым ID из inode.

А понятие идентификатора устройства требует некоторого пояснения. Когда речь шла об установке системы (глава 5), вскользь было упомянуто о том, что Linux или BSD могут быть установлены на несколько дисковых разделов. И каждый из этих разделов будет нести свою файловую систему, образующую отдельные ветви единого файлового древа.

Так вот, нумерация файлов в каждом таком разделе - независима от других, начинаясь с 2 (идентификатор "локального" корня). И потому в каждой ветви файловой системы могут быть (и неизбежно будут) файлы с одинаковыми идентификаторами. Чтобы отличать их друг от друга, и введен такой атрибут - идентификатор устройства.

Файлы существуют в том числе и для того, чтобы пользователь с ними работал - открывал, просматривал, редактировал, и т.д. А из предыдущей главы мы уже знаем, что все эти действия осуществляются процессами. То есть каждый процесс имеет некий набор открытых им файлов. Однако легко представить себе ситуацию, при которой два и более процессов открывают один и тот же файл. Как они различаются? - ведь inode открытого файла по определению один, а имя файла в контексте процесса также не фигурирует.

Для этой цели предназначены т.н. файловые дескрипторы, которые не следует путать с дескрипторами индексными (сиречь inodes). Файловые дескрипторы (в дальнейшем условимся называть их дескрипторами просто) - это также числовые идентификаторы, которые присваиваются файлам по мере открытия их данным процессом. Причем нумерация их для каждого процесса - отдельная. То есть файл с inode xxx, открытый процессом A, получит дескриптор M, а в процессе B ему же будет присвоен дескриптор N.

Однако одинаковые дескрипторы для одних и тех же файлов также не запрещаются. Примером чему - имеющие особое значение имеют дескрипторы 0, 1 и 2. В любом процессе они присваиваются фиксированным файлам устройств - стандартного ввода, стандартного вывода и стандартного потока ошибок (/dev/stdin, /dev/stdout, /dev/stderr, соответственно). К ним мы еще вернемся, а пока достаточно запомнить, что в случае настольной персоналки для ввода данных в процесс стандартно служит устройство под названием "клавиатура", а вывод процесса осуществляется на экран монитора (куда по умолчанию валятся и все сообщения об ошибках).

Классификация файлов

Старый пользователь DOS/Windows, не погрязший окончательно в метафоре файлов-документов, возникшей в MacOS (где, надо отдать ей должное, метафора эта вполне оправдана) и внедренной в Windows, начиная с версии 95, помнит о существовании типов файлов, отличающихся своими расширениями, - исполняемых (*.exe, *.com), пакетных (*.bat), текстовых (*.txt), изображений в различных форматах (*.tiff там, или *.gif), и так далее. И потому будет весьма удивлен, когда узнает, что в Unix'ах все они не типизированы никак. И действительно - как ОС может отличить их друг от друга, если имени файла (и, следовательно, расширения) в атрибутах его нет и в помине? В этом смысле говорят, что в Unix нет понятия типизации файла.

На самом деле средства для различения файлов с контентом разного рода существуют, но это - прерогатива приложений, а не самой ОС, и о них сейчас речи не будет.

Тем не менее, под понятие файла в POSIX-системах подпадают столь разнородные объекты, что они неизбежно требуют классификации. Другое дело, что классификация эта основана на совершенно других принципах. А именно, выделяются следующие типы файлов (и тип в данном случае - это атрибут, описанный в соответствующем поле inode каждого файла):

  • каталоги;
  • символические ссылки;
  • специальные файлы устройств;
  • именованные каналы и сокеты;
  • обычные, или регулярные, файлы.

Пользователю постоянно приходится иметь дело с обычными файлами и каталогами, довольно часто - с символическими ссылками, время от времени - с файлами устройств. А вот с каналами и сокетами он практически не сталкивается (по крайней мере, у меня, за все годы карьеры POSIX-пользователя, такой необходимости ни разу не возникло). Пропорционально этому мы и уделим внимание перечисленным типам файлов. Начнем мы, однако, не с обычных файлов, с коими пользователь общается наиболее тесно, а с каталогов - и сейчас же станет ясно, почему.

Каталоги

Итак, каталоги (по английски directory) - в некотором смысле они противопоставляются всем другим типам. Это - специальные файлы, объединяющие другие файлы (и подкаталоги, называемые также вложенными каталогами) в обособленные друг от друга группы (по крайней мере, так они видятся пользователю в специальных программах управления файлами - файловых менеджерах).

В русскоязычной литературе по DOS/Windows каталоги обычно именуются директориями, а в последнее время в обиход вошел термин "папка" (по английски folder). Однако для Unix-систем термин "каталог" предпочтительнее, и я сейчас постараюсь объяснить, почему.

Принятая в Windows (и идущая исторически от MacOS) метафора каталога как папки с документами способна создать впечатление (и подчас его создает), что каталог - это некое физическое вместилище включенных в него файлов (на память приходит "зиккурапия" классиков нашей фантастики). Но это - не так. Каталоги - суть не более чем списки имен входящих в них файлов и тех самых числовых идентификаторов, о которых речь шла в прошлом разделе (и по которым собственно данные и находятся системой). И потому метафора библиотечного каталога (или книжного оглавления) как нельзя лучше отражает физический смысл соответствующего понятия.

Роль каталогов трудно переоценить. Потому что имя файла в файловой системе Unix'ов существует только в составе каталога, и никак иначе. Собственно же содержимое файла ставится в соответствие имени через идентификатор его inode, подобно тому, как это осуществляется в базах данных. Механизм соотнесения имени файла, его метаданных и данных носит название жесткой ссылки (hardlink), чтобы отличить его от ссылок символических - файлов особого типа, о которых речь пойдет в следующем подпараграфе. Количество жестких ссылок и есть значение поля счетчика в inode файла. Очевидно, что возможный его минимум для большинства типов файлов - единица.

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

Во-вторых, на один inode с его данными может быть создано произвольное количество жестких ссылок (вспомним о соответствующем атрибуте - счетчике ссылок). То есть файл с одним и тем же физическим содержанием может выступать под рядом имен, и все они будут равноправны между собой. Или, напротив, под одним и тем же именем входить в состав разных каталогов.

Редактирование файла, вызванного под любым из его имен или из разных каталогов (например, текста в текстовом редакторе) будет приводить к изменению области данных файла, не зависящим от имени, и, следовательно, содержимое любого из таких файлов-двойников будет оставаться идентичным. Более того, любое из имен такого файла можно удалить (то есть изменить содержимое включающего его каталога), что не окажет никакого влияния на остальные имена файла (и, тем более, на его реальное содержимое).

Подчеркну, что два имени файла, связанных жесткой ссылкой с одним и тем же inode, не являются копией один другого, поскольку при копировании файла создаются новые области метаданных и данных, которые в дальнейшем будут существовать абсолютно независимо друг от друга - то есть изменение содержимого копии или ее атрибутов никак не затронет исходный файл.

В третьих, удаление файлов в Unix происходит совершенно иначе, чем в DOS/Windows. А именно, файл считается удаленным, когда уничтожены все имена, ссылающиеся на идентификатор данного inode (то есть файл исключен из файловой системы), и закрыта последняя программа, к нему обращающаяся (то есть завершен процесс, загрузивший данные файла в память, и уничтожен индексный дескриптор файла в этом процессе). В описании атрибутов файла это выражается в том, что счетчик ссылок его inode обнуляется. Разумеется, сами по себе данные, составляющие содержание файла, физически могут продолжать существовать на диске, но для системы они уже недоступны. А поскольку содержание файла оторвано от его имени, восстановление случайно удаленного файла по фрагменту имени (на чем основаны DOS-утилиты типа UNERASE и UNDELETE) оказывается невозможным.

Пока любой файл открыт, то есть существует ссылающийся на него процесс, он продолжает существовать, даже если имя его исключено из всех каталогов, и может быть записан, скопирован, переименован, и т.д. То есть открытый каким либо процессом дескриптор данного файла - гарантия его существования, по крайней мере до завершения процесса. Именно поэтому я ранее сказал, что файл не обязательно имеет имя: в случае удаления открытого файла из каталога он некоторое время существует как бы безымянным, для поддержания его бытия достаточно открытого дескриптора, ассоциированного с inode.

Однако вернемся к каталогам. Как мы только что увидели, любой файл доступен для системы в том только случае, если он включен, пардон за двойную тавтологию, в файловую систему. То есть имя его приписано к определенному каталогу. Но ведь каталог - это тоже файл, и он тоже должен быть приписан к некоему каталогу более высокого уровня, который носит название родительского. И этот родительский каталог (символически он обозначается как ..) - один из элементов любого, даже не содержащего как бы файлов, каталога. А второй его непременный элемент - это он сам, то есть текущий каталог (что символизируется так - .). То есть для каталога минимум возможных связей (значение поля счетчика в inode), в отличие от всех прочих типов файлов, равно 2.

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

Корневому каталогу файловой системы всегда присваивается идентификатор 2. Очевидно, что он не имеет родительского каталога - выше него в файловой иерархии не лежит ничего. Тем не менее, счетчик его ссылок также имеет два значения - ибо он сам выступает в качестве своего родителя. То есть ссылка на родительский каталог - есть ссылка на него же.

Отдельные ветви файловой системы, расположенные на самостоятельных дисковых разделах, имеют свои корневые каталоги, также имеющие в качестве идентификатора 2. Однако, поскольку они различаются идентификаторами устройств (то есть разделов), путницы между ними и "главным" корневым каталогом не происходит (подробнее об этом речь пойдет в главе о файловой иерархии).

Символические ссылки

Как уже было сказано, жесткая ссылка на файл может существовать только в той же файловой системе, в которой размещен его inode. Однако подчас возникает необходимость поместить имена одних и тех же файлов в каталогах, лежащих в разных файловых системах (и из главы о файловой иерархии станет ясно, зачем). Конечно, такие файлы можно просто скопировать. Однако это, помимо напрасной траты дискового пространства, влечет за собой и другие неудобства. В частности, нельзя гарантировать идентичности содержимого таких файлов-копий - а в ряде случаев именно идентичности от них и требуется.

Кроме требования нахождения в одной файловой системе, на жесткие ссылки накладывается еще одно ограничение: они могут быть созданы только на обычные или специальные файлы (например, файлы устройств), но не на каталоги. И потому не подходят для воспроизведения файловой иерархии, в чем подчас также возникает необходимость.

Для разрешения этих проблем был придуман особый тип файлов - символические ссылки (symlinks), часто называемые просто ссылками (links). Будем так поступать и мы. А для отличия от них жестких ссылок последние будут именоваться полность. Вообще-то, в русском языке для однозначности за жесткими ссылками хорошо бы закрепить термин "связь", но он почему-то не прижился.

В отличие от жесткой ссылки, представляющей собой просто еще одно имя для одного и того же набора метаданных и данных, символическая ссылка - это именно самостоятельный файл, со своим идентификатором и прочими атрибутами, записанными в области метаданных, и со своими данными. Другое дело, что последние предоставляют очень ограниченную информацию. А именно - просто указание пути к собственно файлу или каталогу, на который эта ссылка указывает. Причем файл этот может лежать не только на иной файловой системе (дисковом разделе), но даже находиться на иной машине в локальной или даже глобальной сети. Не возбраняются и символические ссылки на каталоги - в этом случае содержимое каталога-ссылки будет точно воспроизводить внутреннюю иерархию исходного каталога.

Собственно говоря, на локализацию файла, исходного для символической ссылки, не накладывается практически никаких ограничений. Потому что при обращении к файлу-симлинку (например, при вызове его для редактирования в текстовом редакторе) происходит отслеживание пути до исходного файла, который и загружается данной программой.

Очевидно, что если исходный файл по каким-либо причинам недоступен (например, лежит на удаленной машине, связь с которой в данный момент отсутствует), никакое обращение к нему окажется невозможным - последует просто сообщение об ошибке. Аналогично будет и при обращении к симлинку, исходный файл которого был удален - такие ссылки называются мертвыми.

Роль символических ссылок в Unix'ах очень велика. Файловые иерархии операционок этого класса складывались исторически, и, несмотря на ряд общих черт, со временем стали весьма различаться в отдельных клонах. В то же время мы помним, что все POSIX-совместимые системы должны уметь исполнять одни и те же программы, удовлетворяющие соответствующим стандартам. А программы эти могут искать требующиеся им для работы компоненты (в частности, системные библиотеки) в совершенно определенных каталогах. Которые в разных Unix-клонах вполне могут оказаться не только в разных местах, но и на разных файловых системах. И тут символические ссылки оказываются незаменимыми - достаточно продублировать ими требуемые ветви файловой системы в соответствующих местах, чтобы данное приложение спокойно находило все, что ему нужно.

Есть и другое применение символических ссылок. Некоторые программы требуют для своей работы не просто определенные компоненты, чаще всего - библиотеки, но и фиксированные их версии. В то же время установленная в данной системе версия может быть иной - более старой или более новой. В первом случае все ясно - программа, запрашивающая библиотеку имя_рек.5, с версией имя_рек.4, скорее всего, работать не будет (хотя бывают и исключения).

А что делать, если версия библиотеки - более новая? Ведь большинство разработчиков программ для POSIX-систем обычно свято придерживаются принципа обратной совместимости. То есть функциональность библиотеки имя_рек.6 будет, как правило, заведомо достаточной для работы программы, требующей версии имя_рек.5 (исключения и здесь бывают, но они не так уж часты). Однако просит-то наша программа именно версию имя_рек.5, и, не обнаружив ее, выдает сообщение об ошибке.

Конечно, теоретически правильно написанная программа должна требовать версии не ниже некоторой определенной, однако на практике такая ситуация встречается сплошь и рядом. И разрешается она часто достаточно легко - посредством механизма тех же символических ссылок. То есть обычно достаточно создать симлинк библиотеки имя_рек.5 на имя_рек.6, чтобы обмануть наше приложение и заставить его работать правильно.

Сказанным не исчерпывается сфера применения символических ссылок. В частности, они часто требуются для корректного доступа рядя программ к определенным файлам устройств. Если внимательно просмотреть каталог /dev любой POSIX-системы, там почти наверняка обнаружатся такие файлы, как cdrom, mouse, modem, audio и еще несколько им подобных. Так вот, это обычно не более, чем символические ссылки на файлы реальных устройств, созданные для единообразия доступа к ним, позволяющие абстрагироваться от конкретного конкретного его интерфейса (положения на IDE-контроллере, номера последовательного порта, и так далее), Впрочем, это уже тема следующего раздела, имя которому -

Файлы устройств

Файлы устройств соответствуют различным присутствующим в системе устройствам. Устройства эти могут быть реальными (жесткие диски, принтеры и т.д.), а могут - т.н. псевдоустройствами, с которыми не ассоциировано никакого "железа" (например, знаменитое устройство /dev/null, не содержащее ничего, или /dev/zero, содержимое которого - сплошные нули).

С другой стороны, выделяются файлы символьных устройств и устройств блочных. К первым возможен только последовательный (побитный, то есть посимвольный) доступ. Примером их являются последовательные и параллельные порты, а также терминалы, реальные и виртуальные. К блочным устройствам можно осуществлять произвольный доступ, обмен информацией с ними осуществляется блоками фиксированной (реже - переменной) длины. Блочные устройства представлены, в частности, жесткими дисками и другими накопителями.

Нужно заметить, что в BSD-системах (FreeBSD 5-й ветки, DragonFlyBSD) последнее время отмечается отказ от поддержки устройств блочного типа: диски и прочие накопители предстают в них как устройства символьные. Что, впрочем, не отменяет блочной организации лежащих на них файловых систем, которые будут предметом рассмотрения в следующей главе.

Для файлов устройств, кроме обычной идентификации, есть и собственная система опознавания: по номерам - основному, или старшему (major), определяющему класс устройств (например, 12 - старший номер устройств, относимых к классу виртуальных консолей; это - для DragonFlyBSD, в иных системах старший номер того же устройства будут иными), и дополнительному, или младшему (minor). Последний является просто порядковым номером данного устройства в своем классе (например, для первой виртуальной консоли он будет равен 0, для второй - 1, и так далее).

Файлы устройств могут быть фиксированными и динамическими. В первом случае они создаются (как правило, при инсталляции системы) в комплекте на все случаи жизни, и включают в себя почти все, что теоретически может быть подсоединено к машине. Например, на машине с обычным IDE-контроллером, имеющим два канала на два устройства каждое, можно обнаружить четыре файла, соответствующие жестким дискам, для каждого - по четыре файла, отвечающие теоретически возможным первичным разделам, немернянное количество файлов терминальных и псевдотерминальных устройств, и т.д. Хотя большая часть из этих файлов не соответствует никаким реально присутствующим устройствам.

В то же время некоторые на самом деле задействованные устройства могут не иметь ассоциированных с ними файлов. В этом случае пользователь вынужден пополнять их список, для чего предназначены специальная команда (mknode) и построенный на ее основе сценарий (/dev/MAKEDEV.

Динамическое создание файлов устройств применяется в тех системах, которые используют т.н. файловую систему устройств (devfs) или механизм udev. Оно происходит каждый раз заново в процессе старта машины на основе тестирования наличного оборудования. И тут уже файлы создаются только для тех устройств, которые присутствуют на самом деле (и, что немаловажно, поддерживаются текущей конфигурацией ядра данной операционки - в противном случае устройства все равно что нет).

Понятие файла устройств в ОС Unix-семейства позволяет пользователю (и программам) общаться с железом, не вдаваясь в подробности того, как оное устроено. В разделе о командах будет показано, что вывод программ может быть перенаправлен в произвольный файл. Или, напротив, содержимое файла может быть считано в качестве входных данных. А поскольку устройства - это такие же файлы, то понятие перенаправления распространяется и на них. То есть для распечатки некоего текста его достаточно перенаправить в файл печатающего устройства (например, /dev/lpr - принтер на параллельном порту).

Каналы и сокеты

Специальные виды файлов - именованные каналы (named pipe) и сокеты (socket), - предназначены для обмена данными между процессами. Они важны для разработчиков ПО, пользователь с ними, как правило, напрямую не общается. Однако знать о факте их существования не вредно.

Обычные файлы

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

Легко понять, что множество обычных файлов охватывает все то, что в DOS/Windows рассматривалось бы как самостоятельные типы - исполняемые бинарные файлы, файлы сценариев оболочки, обычные тексты, файлы изображений, файлы в форматах специальных программ (например, документы ворд-процессоров или электронных таблиц). Общее между ними одно: содержимое всех их может быть непосредственно просмотрено либо стандартными командами типа cat, less, more, либо программами, в которых они были созданы. Тогда как непосредственный просмотр содержимого файлов прочих типов, как правило, невозможен - доступ к ним можно получить только косвенными путями.

Однако под понятие регулярных файлов подпадают и весьма неожиданные вещи. Выше я упоминал, что в Unix'ах в качестве файлов предстают не только устройства (сущности статические), но даже протекающие в системе процессы. Для представления последних задействуется т.н. файловая система процессов - procfs. Так вот, как это ни странно, файлы процессов суть также обычные файлы, многие из которых можно легко просмотреть с помощью обычных средств доступа к файлам - командами типа cat, more, less.

К слову сказать, существование файлов процессов опровергает еще один момент из приведенного в начале параграфа определения файла - будто бы это обязательно область на диске. Файловая система процессов со всем своим содержимым никакого места на диске не занимает, являюсь лишь отображением происходящего в системе в виде, пригодном для восприятия универсальным интерфейсом файловой системы.

Еще раз об именах

Как можно заключить из изложенного выше, имя файла не несет в POSIX-системах того сакрального смысла, который вкладывался в него со времен DOS. Не имеется здесь и расширений имен (типа приснопамятной DOS-маски 8.3), предопределяющих отношение ОС к данным из конкретного файла.

На самом деле в Unix'ах на длину и формат имени файла накладывается весьма мало ограничений. Длина имени определяется форматом каталога, принятым в данной файловой системе. И ныне большинство их реализаций поддерживают имена длинной до 255 символов.

В именах файлов абсолютно запрещенными к использованию символами, с точки зрения ОС, являются лишь прямой слэш (/), зарезервированный как имя собственное корневого каталога и элемент пути к файлу (разделитель каталогов), и NULL, то есть отсутствие всякого символа. Все прочие символы алфавитные, цифровые и специальные символы теоретически могут входить в имена.

Важно помнить, что имена файлов в POSIX-системах чувствительны к регистру образующих их алфавитных символов. То есть README и readme всегда будут восприниматься как имена разных файлов.

Однако практически на имена файлов накладывают свои ограничения еще и некоторые прикладные программы. В первую очередь это - командные оболочки, которые целый ряд символов, не принадлежащих к числу алфавитно-цифровых, интерпретируют по-своему - как коды управления, регулярные выражения, и так далее. В главе о командах будет показано, что в принципе специальное значение символов можно отменить. Тем не менее, такие знаки, как *, !, #, @ и прочие из верхнего ряда клавиатуры, за исключением подчеркивания (_), не рекомендуется включать в имена файлов. К слову, сам символ отмены специального значения - обратный слэш (\), - также в именах лучше не использовать...

Зато точек имя Unix-файла может содержать сколько угодно - ведь мистического значения (отделения собственно имени от расширения) в этот символ не вкладывается. И потому здесь обычными являются имена типа archive.tar.gz или archive.tar.bz2 - файлы архивов, образованных утилитой tar и компрессированных программами gzip или bzip2, соответственно (в Unix'ах понятия архивации и компрессии суть вещи, обычно, разные).

Особую роль играет точка в начале имени файла - например, .cshrc. Файлы такого вида так и называются - dot-файлами. Имена их недоступны при просмотре содержимого каталогов без специальных средств (о чем речь пойдет в ближайшей интермедии). Обычно в качестве dot-файлов выступают конфигурационные файлы пользователей.

Поскольку одноименные файлы (с разным или одинаковым содержанием) вполне могут существовать в разных каталогах, имя файла само по себе не позволяет однозначно идентифицировать его положение в файловой системе. Отсюда возникает понятие пути к имени, по английски - pathname, что иногда переводится как полное, или составное, имя. Оно, помимо собственно именования файла, указывает всю цепочку подкаталогов, отделяющих его либо от корневого каталога (абсолютный путь к имени), либо от каталога текущего (относительный путь).

Абсолютный путь отсчитывается от "главного" корневого каталога файловой системы вообще, и имеет следующий вид:

/dir1/filename

где dir1 - подкаталог корневого каталога (первый /). Относительный путь отсчитывается от каталога, выступающего текущим для данного процесса. В предположении, что текущим является каталог /dir1, относительный путь к файлу /dir2/filename будет выглядеть так:

../dir2/filename

Элементы пути к имени (промежуточные подкаталоги) в любом случае разделяются символами прямого слэша - именно поэтому он и является абсолютно запрещенным к употреблению собственно в именах.

На полную длину пути также накладываются системные ограничения. Впрочем, они весьма мягкие - верхний лимит pathname лежит обычно в районе нескольких тысяч символов (в Linux, например, он составляет 4096 символов). Так что путь к имени файла может быть сколь угодно длинным - в разумных пределах, разумеется. А вполне возможная в Windows (точнее, в одной из ее файловых систем - vfat) ситуация, когда создается файл, доступ к которому в дальнейшем оказывается невозможным вследствие превышения лимита длины пути, в Unix практически исключена.

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

И еще о текущем каталоге. Для пользователя, заставшего времен DOS, представляется очевидным, что любой файл (например, исполняемый экзешник) ищется в первую очередь в текущем каталоге. В Unix'ах это не так: в большинстве систем поиск в текущем каталоге или не производится вообще, или осуществляется в последнюю очередь. И потому, если требуется вызвать исполняемый файл из текущего каталога, его положение должно быть указано в явном виде:

$ ./filename

Это имеет глубокое внутреннее обоснование - в том числе и с точки зрения безопасности, и к этому вопросу мы еще будем возвращаться.

Право на файл

В начале этого параграфа были вскользь упомянуты прочие атрибуты файла - принадлежности, доступа и режима. Это - очень важные понятия любой Unix-системы (как, впрочем, и любой иной системы, претендующей на звание всамделишней). И - одно из тех понятий, которые (знаю по собственному опыту) психологически трудны для понимания пользователя, пришедшего из мира DOS/Windows 3.X/9X/ME.

Впрочем, смысл атрибутов принадлежности интуитивно понятен: каждый файл в системе, а) имеет своего хозяина, б) приписан к какой-либо группе пользователей, и в) находится в неких отношениях со всеми прочими. В качестве хозяина файла обычно выступает его создатель. Точнее, хозяин процесса, этот файл создавшего. А еще точнее - пользователь, идентификатор которого был унаследован процессом, создавшим файл, в качестве эффективного UID. То есть если процесс этот по каким-либо причинам получил привилегии root-оператора, то и созданный им файл будет иметь своим хозяином его, а не пользователя, на самом деле процесс запустившего.

Далее, каждый файл приписывается по умолчанию к основной группе пользователя - хозяина файла, однако такое положение не является непременным. И именно отнесение файла к некоей группе широко используется для разграничения доступа, организации коллективной работы и прочих задач администрирования. Ну и все "прочие" также имеют некие права на файл - и не обязательно более узкие, чем члены его группы (или даже хозяин).

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

Право на чтение (read) файла - это возможность просмотреть его командой less (естественно, только для обычного файла, файлы устройств, сокеты или каналы просмотреть таким образом нельзя, а попытка просмотра символической ссылки вызовет ее файл-источник), открыть в текстовом редакторе или какой-либо прикладной программе. Атрибут изменения (write) также понятен - он дает право изменить содержимое файла, вплоть до полного удаления (содержимого, но не файла! - никакие атрибуты файла не имеют отношения к возможности его удаления). А атрибут исполнения (execute) означает возможность запустить откомпилированный бинарник или скрипт: именно присвоение этого атрибута волшебным образом превращает набранную в текстовом редакторе последовательность команд в сценарий оболочки.

Для каталога же атрибут чтения позволяет просмотреть командой ls (или каким бы то ни было иным способом) его содержимое. А по предыдущему параграфу мы помним, что все содержимое каталога - это просто список имен входящих в него файлов. И потому право на изменение каталога - это возможность этот самый список модифицировать. Например, удалить из него какое-либо имя, или создать новое (путем перемещения ли, копирования файла, или записи некоторых данных из программы). А поскольку больше имя файла нигде не фигурирует, лишившись его, файл становится недоступным для системы - то есть удаленным. При этом прошу обратить внимание: никаких особенных прав на сам файл не требуется, в общем случае можно удалить файл, не имея на него прав не только изменения, но даже чтения, достаточно обладать правом на изменение каталога. При этом, зная точно имя удаляемого файла и полный путь к нему, можно не иметь и права на чтение каталога, к которому файл приписан.

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

Атрибуты доступа сцеплены с атрибутами принадлежности. То есть для каждой категории обладателей файла (даже прочих - они тоже в определенной мере обладатели файла) может быть установлено свое (и теоретически любое) сочетание прав на доступ к нему. Так, юзер может иметь право на чтение, изменение и (если это оправдано по смыслу) исполнение файла или каталога (очевидно, что право исполнения TIFF-файла ни малейшего смысла не имеет), группа и прочие - иметь права чтения и, если нужно, исполнения: это схема, по которой обычно по умолчанию распределяются атрибуты вновь создаваемого файла.

Можно запретить любой доступ к своим файлам для прочих, оставив их только для группы особо избранных товарищей. А можно, напротив, запретить право чтения и исполнения для группы, сохранив их для прочих: в этом случае группа товарищей превращается в группу, скажем так, не-товарищей. Ведь права доступа проверяются при обращении к нему в строгом порядке: юзер -> группа -> прочие. И потому, если чтение файла запрещено для членов группы, к которой файл приписан, их попытки ознакомиться с ним будут отвергнуты, тогда как все прочие прочтут файл беспрепятственно...

Кроме атрибутов принадлежности и доступа, которые присваиваются файлу (или, напротив, отнимаются у него) в обязательном порядке, он может иметь и три дополнительных атрибута, называемых иногда атрибутами режима. Для названия их удобочитаемого русского перевода не существует. А на вражьей мове это атрибут SUID (Set User IDentificator), иногда именуемый битом "суидности" (не путать с суицидом), SGID (Set Group IDentificator) и sticky (что можно трактовать как атрибут "липкости" или "сохранности"). Первые два имеют смысл только для исполняемых обычных файлов, последний - в основном для каталогов.

Атрибут "суидности" обеспечивает механизм, благодаря которому пользователю удается изменить свой пароль без помощи администратора. А именно: файл, которому он присвоен, при запуске на выполнение (именно потому он имеет смысл только для файлов с атрибутом исполнения) порождает процесс, наследующий эффективный UID не юзера, его запустившего, а хозяина файла, правами доступа которого и определяются привилегии процесса. А поскольку хозяином подавляющего большинства файлов за пределами домашних каталогов является root, именно его-то права процессу обычно и достаются. Смысл атрибута SGID аналогичен, только тут процессом наследуется не эффективный UID пользователя, а эффективный GID группы, к которой приписан помеченный файл.

Атрибут sticky присваивается обычно каталогам, и влечет за собой невозможность удаления из него файла кем бы то ни было, за исключением владельца файла - ведь в обычном случае для этого достаточно иметь права доступа не к файлу, а к каталогу. Установка его целесообразна для каталогов, хранящих всякого рода временные данные (типа /tmp и некоторых подкаталогов ветви /var), права доступа к которым по умолчанию (и по смыслу) допускают их модификацию всеми пользователями. Однако, если право на запись временных файлов юзер вряд ли сможет использовать во вред, то удаление из таких каталогов чужих файлов вряд ли будет приветствоваться их хозяином. И именно для предотвращения такой ситуации предназначен атрибут sticky.

Сведения обо всех атрибутах файла можно получить посредством команды ls в "длинном" формате (с опцией -l). Или - от какого-либо файлового менеджера. Хотя, на мой взгляд, команда ls ничуть не менее информативна и выразительна. Что я и попытаюсь продемонстрировать в ближайшей интермедии.

В обычном выводе команды ls -l имена пользователя и группы находятся в третьем и четвертом полях, а атрибуты доступа объединены в первом. Он имеет вид

-rwxrwxrwx

что расшифровывается следующим образом. Первая позиция последовательности - тип файла (символ дефиса, -, в примере означает, что мы имеем дело с обычным файлом, для каталога там был бы символ d - от directory, для символьного устройства - символ c, для блочного - символ b). Следующие три символа определяют атрибуты доступа для хозяина файла: r - чтение, w - изменение, x - исполнение. Две последние тройки символов - суть то же самое, но для группы и прочих, соответственно. То есть в данном примере фигурирует исполняемый файл, открытый на чтение, запись и запуск для всех. Если же у кого-либо какое-то право отнято - в соответствующей позиции мы увидим символ дефиса. Так, атрибуты нового текстового файла по описанной выше умолчальной схеме будут выглядеть как

-rw-r--r--

Если файлу приписан атрибут суидности, в тройке владельца место символа x займет символ s, а при наличии атрибута SGID то же s окажется на месте x в тройке группы. Атрибут "липкости" маркируется символом t в последней позиции (вместо x для "прочих"). То есть вывод команды

ls -l /usr/bin/passwd

будет выглядеть так:

-rwsr-xr-x

а прочие варианты предлагается домыслить самостоятельно.

Выше при описании прав доступа использована т.н. символьная нотация, простая и мнемонически понятная (r - от read, o - от other, и т.д.). Однако наряду с ней существует (и активно используется) нотация цифровая, где права доступа обозначаются числами типа 644. Поначалу она кажется загадочной. Однако при ближайшем рассмотрении - ничего подобного. Первая цифра соответствует правам хозяина файла, вторая - правам членов группы, третья - правам разных там прочих, как и при символьной нотации. А сама цифра представляет собой простую арифметическую сумму прав, также выраженных численно - только в двоичной системе счисления, трансформированной в восьмеричную для компактности. А именно: наличию любого права соответствует двоичная единица, отсутствию - двоичный ноль. То есть символьной форме rwxrwxrwx будет соответствовать двоичная 111 111 111, что в восьмеричном пересчете и даст "число юзверя" - 777 (полный доступ для всех атрибутов принадлежности). Образуется она из суммы прав чтения (восьмеричное 4), изменения (восьмеричное 2) и исполнения (восьмеричная 1) в трех позициях. Из чего можно догадаться, что число 000, напротив, означает отсутствие прав на любые действия у кого бы то ни было.

Арифметические вычисления того, какие числа соответствуют каким соотношениям прав, я предоставляю читателю (слаб стал в устном счете с тех пор, как бросил преферанс). Скажу только, что "умолчальная" атрибутика новорожденного файла rw-r--r-- в численной нотации будет выглядеть как 644.

В численной нотации определяются обычно и права, даруемые файлу при рождении. Для этого существует команда umask, и ее аргумент в численной форме показывает, какие права из их суммы должны быть отняты (на этот раз - именно отняты) от совокупности исходных прав у файла. То есть для получения стандартных (в большинстве систем) прав 644 этот аргумент должен быть равен 022.

Догадались, почему в первой позиции стандартной совокупности прав фигурирует 6, хотя в аргументе команды umask видим 0? Правильно, потому, что ни один создаваемый непосредственно пользователем файл не рождается как исполняемый: этот атрибут должен быть присвоен ему принудительно. А от рождения право на исполнение обретают только двоичные файлы, создаваемые компилятором (например, gcc).

О времени и о файле

В заключение - несколько слов о временных атрибутах файла, которые, как станет ясным из главы про управление пакетами, в некоторых случаях оказываются очень важными. Их - опять же три: время доступа (atime - от Access Time), время модификации (mtime - от Modification Time) и время изменения (ctime - от Change Time). Первый атрибут фиксирует время последнего обращения к файлу - любого, например, просмотра его командой less. Атрибут этот на практике используется редко, а пересчет времени доступа для изобилия файлов на древе Unix-системы отъедает ресурсы. И потому часто он (пересчет, конечно, а не атрибут) отменяется, о чем будет разговор в главе о иерархии файлов и монтировании файловых систем.

Атрибут модификации, mtime, устанавливает время последнего изменения файла, вернее - изменения области его данных (например, правки текста в редакторе). А атрибут изменения, ctime, (по русски лучше для определенности добавлять - изменения статуса) фиксирует время изменения метаданных файла - например, прав доступа, владельца, группы и т.д.

Характерно, что ни один из временных атрибутов файла не отражает непосредственно времени его создания. В качестве такового можно рассматривать атрибут ctime - но только в том случае, если в дальнейшем статус файла не изменялся (например, если файлу текста скрипта не был присвоен атрибут исполнения). В противном случае определить истинное время создания файла невозможно.

[ опубликовано 01/08/2005 ]

Алексей Федорчук - Глава 8. Файл как он есть   Версия для печати