(Отчасти, к разговору скорее о психологии, чем о Unix, приглашало и письмо Г. Махметова.)

Письмо содержало следующее описание: «... почему-то в моем домашнем каталоге появился файлик 0 — так и называется (ноль), — который я никакими средствами удалить не могу, хотя я его владелец, права позволяют. Средствами MC удаляю, выхожу из MC, снова вхожу — файлик тут как тут — 0. Удалял и средствами rm — вообще никакого эффекта». К делу оперативно подключился администратор и, проделав пару довольно злобных действий, починил систему.

Надо сказать, это достаточно типичная ситуация: обычно «исправить» проще, чем понять — сам неоднократно проделывал это. Если всякий раз анализировать ситуацию, то, конечно, надо сменить свою работу на работу тестировщика операционной системы. Кроме того, у меня нет полностью достоверной версии данной ситуации (поэтому я и не привожу имен).

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

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

Скажем, что делает, например, ls с точки зрения системы для того, чтобы выдать информацию о файле? Во-первых, передает имя для получения этого самого номера и далее читает inode для получения информации. Заметим — с точки зрения системы. Формально команда ls не обязана получать этот самый номер явно, и нет никаких вызовов, которые позволили бы явно работать с файлом по этому самому номеру. ls обходится простыми вызовами типа stat/fstat. Информации, которые выдают эти вызовы, оказывается достаточно для работы ls. Сочетание ls -i ... позволяет явно проверить этот самый номер inode. Корректная выдача на ls -l ... свидетельствует о том, что преобразование имени в номер inode, и чтение этого самого inode происходят корректно. Иногда возникает и обратный вопрос, т.е. как по номеру inode получить имя файла. Это можно проделать либо при помощи поиска, либо, если у вас достаточно прав, при помощи утилиты ncheck.

В нашем случае, конечно, было бы правильно проверить то, насколько конкретно уничтожался файл. А уничтожался ли он вообще? Для этого можно было бы использовать и номер inode или попробовать выполнить что-нибудь типа mv. Если во последнем случае появляется и новый файл со старым именем и изменяется имя старого, то, очевидно, это один из фокусов shell. Если файл со старым именем не появился, то разница в поведении с rm не очень ясна. Впрочем, в случае каких-либо нарушений в файловой системе вполне допустима некоторая «нетривиальность» поведения.

Тут вполне уместно поговорить о принципах программирования операционных систем и файловых систем в частности. Можно рассматривать два крайних варианта. Один из них предполагает, что любое нарушение внутренних структур ядра (проверяемое при помощи «охранных» заклятий) ведет, скорее всего, к фатальным последствиям. Поэтому система должна прекратить выполнять какую-либо дальнейшую работу; принцип «охранного программирования» говорит: лучше не работать, чем работать неправильно. Как правило, в результате обнаружения ошибки в работе системы изготавливается посмертный дамп памяти в некоем оговоренном месте - для того, чтобы разработчики системы помучались. Ясно, что абсолютно противоположный стиль поведения — ничего не проверять и просто «пахать» — будет приводить к сложно диагностируемым ошибкам. Более интересный вариант предполагает определенный уровень самопроверки системы, но, вместо паники, как в первом случае, предусматривает отказ на выполнение конкретной операции («файл отвалился») и допускает дальнейшую, возможно несколько ограниченную, работу. Отказ какого-либо компонента файловой системы приводит к невозможности выполнить часть операций с выдачей самой разнообразной диагностикой типа «permission denied» прямо в лицо администратору и т.п. Возможны и промежуточные ситуации, когда выбор варианта поведения дается на откуп администратору. Чаще всего это делается через параметры монтирования. Замечу, однако, что обычно любые ошибки в структурах файловой системы порождают какие-либо сообщения. Если сообщений нет, то, как правило, и проблем нет. Если же система «не грохается» по файловому поводу (паники исключены), но не выдает информации о файле, то это свидетельствует либо о проблемах безопасности, либо о повреждениях. О повреждениях поговорим позже, а пока зададимся вопросом, какие эффекты связаны с правами доступа? Для ответа на него необходимо вспомнить, что для полноценного доступа к каталогу необходимо иметь и права на чтение, и на выполнение. Если какие-то права отсутствуют, то либо отказывает поиск в каталоге, либо вообще нет возможности упоминать данный каталог в имени файла. В таком случае возможны достаточно забавные сообщения об ошибках, например, при отсутствии бита выполнения у каталога:

[oblakov@test /]$ ls /tmp/xxx
ooo
[oblakov@test /]$ ls /tmp/xxx/ooo
ls: /tmp/xxx/ooo: Permission denied

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

Когда надо запускать fsck? Обычно имеется специальный файл типа /etc/checklist или /etc/fstab, который используется для задания списка файловых систем, проверяемых при загрузке. Чаще всего файловые системы находятся в нормальном состоянии, и в проверке нет необходимости. Поэтому fsck при загрузке запускается с такими ключами, которые позволяют избежать лишних проверок. Что служит указателем того, что нет необходимости в проверке? Для этого существует специальный флаг в суперблоке файловой системы. Слегка утрируя, можно считать, что данный флаг может принимать два значения: файловая система смонтирована, файловая система не смонтирована. Соответственно, если при выполнении команды mount обнаруживается флаг «система смонтирована», то, очевидно, что для данной файловой системы не была выполнена команда umount и, возможно, она находится в некорректном состоянии. Именно наличие подобного флага, в частности, проверяется при старте системы для определения необходимости выполнения проверки соответствующей файловой системы.

Как именно происходит коррекция файловой системы? Используются несколько методов. Два основных предполагают либо полную проверку взаимного соответствия различных файловых структур, либо поддержку механизма транзакций в стиле баз данных. Во втором случае ведется журнал всех изменений метаданных (описание структуры самой файловой системы) и любые реальные изменения происходят только после появления записей в журнале. Такой подход позволяет гарантировать, что объем изменений, которые надо проверить/выполнить в файловой системе, ограничен набором записей в журнале. В отличие от баз данных, для которых стандартной операцией после сбоя является откат открытой транзакции, стандартная операция в файловой системе с журнализацией — накат. Если есть серьезный сбой (появление плохих блоков), то проверки журнала может оказаться недостаточно, и в этом случае необходимо запустить полную проверку.

Перед описанием полной проверки стоит напомнить, что для работы файловой системы необходимо поддерживать несколько структур: суперблок (хранилище общей и статистической информации о структуре системы, типа размера блока, фрагмента, общий объем и текущий свободный объем), таблица описателей inode, таблицы свободного места, сами файлы и, в особенности, каталоги. Собственно возможность появление ошибок связана именно с большим количеством структур, которые необходимо обновить в результате даже достаточно простой операции. Соответственно полная проверка должна пройти по всем структурам и привести их в соответствие. Это многостадийный процесс (эдак 6-7 этапов), который обычно начинается с проверки таблицы inode и определения списка занятых/свободных блоков, далее появляется возможность проверки дерева каталогов и т.п. Иногда (очень редко и только для смонтированной системы, а fsck запускается обычно для несмонтированной системы, исключением является корневая система) fsck может сообщить, что в результате его деятельности появилось рассогласование между данными файловой системы в памяти и на диске, и необходимо выполнить перезагрузку без выполнения сброса буферов. Для этой цели предусмотрены специальные ключи для команды перезагрузки.

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

Кроме представленных двух случаев есть ряд любопытных идей относительно уменьшения времени стартовой проверки. Это, например, аналог checkpoint с временным сбросом флага монтирования (все ведь корректно), «мягкие обновления» [1] и т.п.

fsck является основным инструментом коррекции систем. Однако, как уже отмечалось, он имеет право не работать в сложных ситуациях. Если обнаруживается массовое желание удаления файлов (обычно масштаб бедствия можно понять, использовав fsck с ключом -n или -N, который запрещает внесение изменений), то в случае, когда ценность данных достаточно высока, можно применять и более «ядреные» методы. Один из них предполагает «форсированное» монтирование поврежденных систем (ключи типа -f плюс -r для того, чтобы монтирование было только на чтение). При таком способе монтирования вы можете бродить по файловой системе и пытаться спасти данные, которые, возможно, fsck попросту бы «снес». Правда, надо понимать, что если вы попытаетесь зайти в очень покореженный каталог или открыть файл с «бэд»-блоками (с точки зрения fsck к числу таковых называются не плохие блоки на носителе, а ссылки в файле на блоки за пределами файловой системы или куда-либо в управляющую информацию и т.п.), то, в зависимости от принципов программирования файловой системы ваша система, возможно, запаникует, и вы еще что-либо сломаете. Поэтому подобного рода фокусы делаются только в однопользовательском режиме с минимальным количеством смонтированных каталогов. При должной осторожности это может оказаться самым эффективным средством спасения данных.

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

Однако попробуем запустить fsdb, например, в Solaris:

# fsdb /dev/dsk/c0t1d0s6
fsdb of /dev/dsk/c0t1d0s6 (Read only)
 — last mounted on /data
fs_clean is currently set to FSSTABLE
fs_state consistent (fs_clean CAN be trusted)

Попытаемся получить информацию о каком-нибудь inode (номер 2 используется для корня файловой системы на носителе, для других же Unix-систем следующие две команды будут короче - 2i, см. описание в man fsdb_тип_файловой_системы; к сожалению, даже в рамках одной системы интерфейс файловых систем разного типа может отличаться):

/dev/dsk/c0t1d0s6 > 2:inode
/dev/dsk/c0t1d0s6 > ?i
i#: 2              md: d—-rwxr-xr-x 
 uid: 0             gid: 0
ln: 8              bs: 2              
sz : 200
db#0: 360
    accessed: Wed Nov  8 19:38:39 2000
    modified: Thu Jul  6 11:12:55 2000
    created : Thu Jul  6 11:12:55 2000

Последние три строки комментария не требуют, i# - номер inode, md одновременно выдает и тип файла и права доступа к нему, uid/gid - соответственно пользователя и группу файла, ln - количество ссылок, sz - размер файла, db#0 - прямой указатель на блок. Любой из этих параметров можно с легкостью поменять соответствующей операцией, например:

/dev/dsk/c0t1d0s6 > 0:db=358
i#: 2              md: d—-rwxr-xr-x
  uid: 0             gid: 0
ln: 8              bs: 2              
sz : 200
db#0: 358
   accessed: Wed Nov  8 19:38:39 2000
   modified: Thu Jul  6 11:12:55 2000
   created : Thu Jul  6 11:12:55 2000

Вах, мы поменяли номер блока. Ой, что будет... (Тут я, конечно, передергиваю, систему с правами read only так не прописать, но добавить соответствующий ключ не проблема.)

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

Общение с утилитами этого класса не сахар. Поэтому к ним прибегают только в том случае, когда данные очень ценные. Иногда можно наблюдать совершенно фантастические вещи. Например, однажды мне удалось наблюдать в корневом каталоге и /usr, и /usr/lib (lib, заметим, не в /usr, а именно в корне и именно в форме /usr/lib). Достаточно часто доводилось видеть превращение обычного файла в каталог. Как такое возможно, ведь для этого необходимо изменение в поле типа md? Как правило, «фантастические» события в файловой системе не связаны с проблемой диска. Если ваша система не имеет защиты от сбоев памяти, и они происходят, то... возможно все. В одной организации мне говорили, что при возникновении «проблем» в файловой системе (естественно, речь не идет о банальном выключении на ходу и т.п., то есть имеются в виду ПРОБЛЕМЫ, а не проблемы) в 80% случаев была виновата память.

Теперь поговорим о некоторых других ситуациях.

Одной из самых типичных проблем с файловыми системами является появление плохих блоков на носителе. Что делать? Тут необходимо понимать природу этих блоков. SCSI диски имеют достаточный запас прочности, если возникает какая-либо проблема, то диск самостоятельно предпринимает ряд действий для избавления от плохих блоков. Для этого каждый блок окружен дополнительной информацией. Искажение нескольких бит в блоке, как правило, не приводит к печальным последствиям. Диск, обнаружив подобную проблему, восстанавливает корректное содержимое и, понимая, что данный блок может испортиться окончательно, выбирает новое место для данных, соответственно помечая данный блок как дефектный. При этом пользователь не замечает ничего — логический адрес блока не меняется. Отлично, а как же тогда возникают дефектные блоки вообще, если все так хорошо? Если к данному блоку долго не обращались, то он вполне может «протухнуть» настолько, что его не восстановит никакая сила. В этом случае диск не имеет права подсунуть пользователю произвольную информацию — пользователь должен получить сообщение о некорректном блоке. Поэтому мы и видим сообщение об ошибке. Что будет в том случае, если прописать данный блок чем-либо? Тут-то диск вспомнит о своих обязанностях и будет использовать новое место для данных. Другими словами, для того чтобы избавится от плохого блока, может оказаться достаточно обычного dd с параметром seek (пока мне попался только один старый «подлый» диск, который не пожелал восстанавливаться). Желательно, однако, прописывать плохой блок нулями. Особенно это важно, если надо «вернуть» блок в таблицу inode или в каталог. Лучше уж иметь ноль в таких местах, чем что-либо более содержательное. Можно, конечно, воспользоваться и форматированием, но этот метод может оказаться достаточно обременительным, поскольку, судя по некоторым документам, низкоуровневое форматирование может потребовать 18 часов для современных дисков — вместо часика-другого для старых. Да и спасение данных на ленту или оптику перед форматированием может потребовать времени.

Еще одна потенциальная проблема связана с кабелями. К сожалению, крепление кабелей к внешним устройствам бывает достаточно ненадежным даже при наличии винтиков. Кабель может отлететь. Такой исход еще надо признать удачным. Худший из наблюдавшихся вариантов заключался в медленном (в течение минуты) отваливании разъема в результате проноса мимо тяжелого оборудования. Это сопровождалось нарастающим объемом диагностики на экране, свидетельствовавшей о предположительном появлении шума на линии (часть операций, вероятно искаженных, выполнялась) и выходом из строя многих файловых систем как на внешнем, так и на внутренних дисках.

Один, два... куча

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

Система, когда выполняет системный вызов, обязана скопировать к себе аргументы (в противном случае было бы неприятной неожиданностью обнаружить, что другая ниточка управления в программе меняет их по ходу выполнения вызова). Это автоматически приводит к желанию ограничить аппетиты конкретного системного вызова и задать явное ограничение на размер аргумента. Типичное ограничение, например, на размер имени файла в вызове 1-4 Кбайт (не путать с ограничением на размер имени файла в каталоге: что-то в диапазоне 14-256 символов). Означает ли это, что мы не можем иметь файлы с более длинными именами? Ясно, что не обозначает. Одного механизма монтирования достаточно для того, чтобы понять, что любые ограничения в рамках одной файловой системы не должны быть абсолютными ограничениями для системы в целом. Однако, как быть с такими файлами? Конечно, вы не сможете использовать явное указание файла с вложенностью в несколько сот каталогов. Например, в команде cp вас явно обругают. Однако, для файла вполне можно воспользоваться адресацией относительно текущего каталога после выдачи нужного (одного может не хватить) числа cd. В частности, Linux (ограничение на длину имени в вызове 4 Кбайт) может легко создать в общей с Windows (например, FAT) файловой системе файл, не перевариваемый Windows.

Еще один пример связан с выполнением подстановки имен. В общем случае команда ls * не должна работать корректно. Shell обязан передать новому процессу (ls) все аргументы. Однако, аргументы эти, очевидно, должны как-то копироваться системой, и без ограничения на общую длину аргументов и переменных окружения не обойтись. Ясно, что взяв достаточно большой (по количеству файлов) каталог, мы можем превысить данный лимит (обычно в пределах 5-200 Кбайт, чаще 10-20 Кбайт) и, соответственно, ls не сможет получить все требуемые аргументы.

Вернемся к нашему письму. Что это я все страсти-мордасти расказываю? Скорее всего, в том случае ничего этого не было. Самый простой способ создать подобный вечный файл (например, при использовании bash) такой:

export PROMPT_COMMAND=»>0»

Данный вариант выполняет все требования письма. mc позволяет его удалить, кстати, он сам меняет данную переменную и, соответственно, порожденный процесс ls ничего не находит, однако, после выхода из него... Другие действия ничего с файлом сделать, как кажется, не позволяют:

[oblakov@test oblakov]$ ls -i 0
  37962 0
[oblakov@test oblakov]$ rm 0
[oblakov@test oblakov]$ ls -i 0
  37962 0

Заметим, что даже описатель inode оказался тем же (ясно, что нам повезло, и везение это разрушается, например, при помощи mv, но результат эффектен).

Сомнительно, что автор письма поставил именно такую команду к себе в инициализационный файл, однако есть ряд других возможностей, добиться похожего результата с переменными ENV, HISTFILE и т.п. Увы, точный вариант канул в Лету.

Пишите о наблюдавшихся вами «потусторонних» явлениях в работе операционных систем по электронной почте по адресу: oblakov@bigfoot.com.

Литература

[1] Маршал Кирк Маккьюзик, Грегори Р. Ганджер, «Мягкие обновления», Открытые системы, № 7-8, 2000