Восстановление данных. Практическое руководство — страница 8 из 13

Восстановление данных под Linux/BSD

Главный недостаток UNIX-подобных систем — отсутствие нормальных драйверов под множество разнообразного оборудования, с которым Windows справляется без проблем. Несколько лет назад ситуация с драйверами под Linux и BSD была просто катастрофической. Поддерживалось лишь некоторое оборудование, и железо для UNIX-машин приходилось закупать отдельно. Тогда операционная система Linux еще не вышла из стадии "конструктора" для хакеров, a BSD в основном использовалась на серверах, все оборудование которых сводилось к сетевой карте и контроллеру SCSI. Поэтому основной массе пользователей жаловаться не приходилось.

На домашних и офисных компьютерах ситуация совсем иная. Тут и сканеры, и мультимедиа, и, конечно же, видеокарты, издавна славящиеся отсутствием общих стандартов. Производители железа в основном ориентируются на Windows и крайне неохотно вкладывают деньги в другие системы, поскольку они приносят одни убытки. Разработка и тестирование драйверов — удовольствие не из дешевых. При этом парк UNIX-машин не только весьма невелик (несколько процентов от общего числа, если не меньше), но и, к тому же, очень разобщен. Следовательно, рыночная доля каждой из многочисленных операционных систем, принадлежащих к этим линейкам, становится еще меньше. Прибрать соответствующий рыночный сегмент к рукам могут только очень крупные производители, такие как, например, nVIDIA, продающие миллионы видеокарт, среди которых и пара процентов становится вполне ощутимой величиной.

Часто приходится слышать о большой армии энтузиастов, дизассемблирующих драйверы Windows и переписывающих их под Linux или BSD. Но, к сожалению, этих энтузиастов не так уж и много. Разнотипного оборудования гораздо больше, тем более что сплошь и рядом приходится сталкиваться с ситуацией, когда на компьютере энтузиаста драйвер работает, а на компьютере пользователя — нет, хотя аппаратные конфигурации этих компьютеров практически идентичны. Драйвер мало написать, его еще нужно отладить, протестировать на большом количестве конфигураций и сопровождать, иначе это будет не драйвер, а просто игрушка. Все это требует затрат времени, поэтому если драйвер создается не для коммерческой выгоды, а для личных нужд, то его надежность и совместимость оставляют желать лучшего.

Составители дистрибутивов проделывают огромную работу, собирая различные драйверы и включая их в свой комплект. Качество тестирования таких драйверов очень невысоко, и даже если в списке поддерживаемого оборудования значится конкретное устройство, никаких гарантий того, что оно заработает, у нас нет. Может быть, подойдет драйвер от другой модели, а может быть, и ничего не подойдет вообще! А ведь без драйверов сидеть скверно.

Виртуальные машины

Прежде чем ставить Linux/BSD задумайтесь — а зачем вам, собственно, все это нужно? Если просто хотите протестировать альтернативную систему, освоить средства разработки или компилировать исходные тексты, то наилучшим выбором будет виртуальная машина, например, VMWare (рис. 8.1). Fedora Core на ней, конечно, будет работать очень медленно (например, на P-III 733 работать вообще невозможно), но Debian с KDE будет "пахать" вполне нормально. Хочешь — разрабатывай программы, хочешь — читай руководства (man pages). Еще и в игры типа Star Wars можно поиграть. Никаких драйверов сверх того, что есть в любом "правильном" дистрибутиве, для этого не потребуется. Большинство разработчиков именно так и поступают. Как ни крути, а любой уважающий себя UNIX-программист вынужден держать на компьютере десяток различных операционных систем, чтобы тестировать свои программы на совместимость. На "живом" компьютере переключения между ними происходят только путем перезагрузки, что не слишком удобно. Виртуальные машины при этом можно переключать в любом порядке и с любой скоростью (главное — это иметь как можно больше памяти!).

Рис. 8.1. Виртуальная машина Linux, работающая в среде Windows

Можно поступить и наоборот. Установить Linux/BSD как базовую систему, a Windows водрузить на виртуальную машину (рис. 8.2). Так как VMWare дает прямой доступ к портам COM, LPT и USB, то подключение сканера, принтера или цифровой камеры к вашей машине перестанет быть проблемой. С этим оборудованием будет работать Windows! Базовая машина UNIX в этом случае получает в свое распоряжение все системные ресурсы, и падения производительности уже не происходит, но появляются другие проблемы. Приложения Windows (например, игры) будут либо сильно тормозить, либо откажутся запускаться совсем, к тому же со всеми остальными типами устройств, например, интегрированной платой WLAN или видеокартой, Windows работать не сможет. А все потому, что VMWare представляет собой "черный ящик", отгороженный от базовой операционной системы толстой стеной эмулятора. Вот если бы существовала возможность предоставить виртуальной машине полный доступ ко всему физическому оборудованию, вот тогда бы... Готовьтесь! Именно такой способ мы и собираемся описать!

Рис. 8.2. Виртуальная машина Windows в среде Linux

Перенос драйверов Windows в Linux/BSD

Начнем с простого, но до сих пор никем не решенного вопроса. Разумеется, на самом-то деле вопрос этот, конечно, уже давно решен, но совсем не так, как следовало бы. Известно, что поддержка разделов NTFS в Linux/BSD представляет собой сплошную проблему. Драйверы, способные осуществлять запись на разделы NTFS, появились совсем недавно, да и то лишь затем, чтобы покрасоваться на выставках. Для реальной работы они непригодны, потому что работают не очень стабильно и страдают множеством ограничений. Сжатые файлы, транзакции и множество других возможностей все еще не поддерживаются. Кроме того, NTFS не стоит на месте и хоть и медленно, но совершенствуется. Можно ли, хотя бы теоретически, написать стопроцентно совместимый драйвер, корректно работающий со всеми новыми версиями NTFS без участия программиста? Этот вопрос совсем не так глуп, как кажется. Для чего программистам тратить силы и время на собственный драйвер, когда под рукой есть уже готовый — NTFS.SYS. Если заставить его заработать под Linux, то все проблемы решатся сами собой.

Несмотря на то, что на уровне ядра между Linux/BSD и Windows существует большое количество различий, но и кое-что общее между ними все-таки имеется. И Windows, и Linux, и BSD работают на процессорах семейства x86 в защищенном режиме, используют страничную организацию виртуальной памяти и, наконец, все они взаимодействуют с оборудованием в строго установленном порядке (через иерархию физических и виртуальных шин). Высокоуровневые драйверы, такие, например, как NTFS.SYS, вообще не работают с оборудованием напрямую и содержат минимум системно-зависимого кода. Почему же тогда драйвер от одной системы не работает в другой? А потому, что интерфейс между ОС и драйвером в каждом случае различен, а также потому, что драйвер использует библиотеку функций, экспортируемых системой, и эти функции у каждой системы свои.

Перенести драйвер Windows в Linux/BSD вполне реально! Для этого даже не потребуется его исходный код. Достаточно лишь написать тонкий и несложный "переходник" между драйвером и операционной системой, принимающий запросы и транслирующий их по всем правилами "этикета", а также перенести библиотеку функций, необходимых драйверу для работы. Конечно, для этого необходимо уметь программировать! Для простых пользователей такой рецепт совершенно не годится, но тут уж ничего не поделаешь. Тем не менее, перенести готовый драйвер намного проще, чем переписать его с нуля. Нам не потребуется проводить кропотливую работу по дизассемблированию оригинального кода, заменяющую собой поиск технической документации (которая либо совсем отсутствует, либо отдается только под подписку о неразглашении, зачастую запрещающую открытое распространение исходных текстов). Наконец, при выходе новых версий драйвера Windows процедура его переноса в Linux/BSD проста до тривиальности — достаточно скопировать новый файл поверх старого файла. Однако все это лишь сухая теория. Перейдем к деталям.

Модель ядра Windows NT и всех производных от нее операционных систем (включая Windows 2000, XP, 2003, Longhorn) достаточно проста (рис. 8.3). С "внешним" миром ядро связывает диспетчер системных сервисов, "подключенный" к NTDLL.DLL, которая находится уже за "скорлупой" ядра и исполняется в режиме пользователя. Диспетчер системных сервисов, реализованный в NTOSKRNL.EXE, опирается на вызываемые интерфейсы ядра, часть которых реализована в самом файле NTOSKRNL.EXE, а часть — во внешних драйверах, к числу которых, в частности, принадлежит диспетчер питания. Определенный класс драйверов, называемый драйверами устройств и файловой системы, находится в своеобразной "скорлупе" и взаимодействует с диспетчером системных вызовов через диспетчер ввода-вывода, реализованный опять-таки в NTOSKRNL.EXE!

Рис. 8.3. Архитектура систем из семейства Windows NT

Ядро, на котором, как на фундаменте, держатся все вышеупомянутые компоненты, представляет собой просто совокупность низкоуровневых функций, сосредоточенных в NTOSKRNL.EXE. Ниже находится только уровень аппаратных абстракций (Hardware Abstraction Level, HAL). Когда-то у Microsoft была идея разделить ядро на системно-зависимую и системно-независимую части, чтобы упростить перенос Windows на другие платформы. Однако уже во времена Windows NT 4 все перемешалось, и большая часть системно-зависимых функций попала в NTOSKRNL.EXE. На сегодняшний день ситуация такова, что HAL медленно, но неотвратимо умирает. В нем осталось небольшое количество действительно низкоуровневых функций, непосредственно взаимодействующих с оборудованием, например, с портами и с DMA. Но в ядре Linux/BSD есть свои функции для работы с DMA, так что тащить за собой HAL нам совершенно необязательно, тем более что драйверы взаимодействуют с DMA не напрямую, а через диспетчер Plug and Play, который находится в NTOSKRNL.EXE.

Что же касается портов ввода-вывода, то, например, дизассемблированный текст функции

READ_PORT_UCHAR
, читающий из данного порта беззнаковый байт, выглядит, как показано в листинге 8.1.

Листинг 8.1. Дизассемблированный листинг функции

READ_PORT_UCHAR
, выдернутой из HAL

.text:80015A2C

.text:80015А2С                 public READ_PORT_UCHAR

.text:80015A2C READ_PORT_UCHAR proc near

               ; CODE XREF: HalGetEnvironmentVariable+2C↑p

.text:80015A2C ; HalSetEnvironmentVariable+3D↑p

.text:80015A2C

.text:80015A2C arg_0           = dword ptr 4

.text:80015A2C

.text:80015A2C                 xor eax, eax

.text:80015A2E                 mov edx, [esp+arg_0]

.text:80015A32                 in al, dx

.text:80015A33                 retn 4

.text:80015A33 READ_PORT_UCHAR endp

Иначе говоря, если заставить NTOSKRNL.EXE работать в чужеродной среде Linux или BSD, мы получим возможность запускать любые драйверы Windows NT без какой-либо доработки их двоичного кода. Это не только упрощает задачу переноса, но и снимает проблему авторских прав. Любой обладатель лицензионной копии Windows (или другой программы) вправе вызывать готовый драйвер откуда угодно без каких бы то ни было разрешений и без выплаты дополнительного вознаграждения, но вот модифицировать двоичный код ему позволят едва ли.

Но мы ведь и не собираемся ничего модифицировать! Мы берем готовый NTOSKRNL.EXE. Работы предстоит не так уж и много. Достаточно просто спроецировать его по адресам, указанным в заголовке РЕ-файла (a NTOSKRNL.EXE это обычный РЕ-файл), и разобраться с таблицей экспорта, используемой драйверами. Короче говоря, мы должны реализовать свой собственный загрузчик РЕ и включить его в загружаемый модуль ядра или в само ядро. Чтобы не мучиться, можно взять готовый загрузчик Wine (Windows Emulator).

Взаимодействие NTOSKRNL.EXE с ядром Linux/BSD будет происходить через код, эмулирующий HAL. Этот код мы будем должны написать сами, однако ничего сложного в этом нет, и объем работы предстоит минимальный, поскольку HAL содержит совсем немного простых функций. Сложнее подружить диспетчер системных вызовов с внешним миром, т.е. с миром Linux/BSD. Основная проблема в том, что интерфейс диспетчера системных вызовов не документирован, и к тому же подвержен постоянным изменениям. В Windows 2000 он один, в Windows XP он уже другой, а потом Microsoft вновь внесет недокументированные изменения, и весь наш труд пойдет насмарку. Поэтому приходится хитрить и тащить за собой не только NTOSKRNL.EXE, но еще и NTDLL.DLL. Некоторые могут спросить: а зачем? Какое отношение NTDLL.DLL имеет к драйверам и ядру? Драйверы его не вызывают, да и сам NTDLL.DLL представляет собой всего лишь набор переходников к NTOSKRNL.EXE.

Дело в том, что по традиции интерфейс NTDLL.DLL худо-бедно документирован. Кроме того, он остается практически неизменным уже на протяжении многих лет, поэтому его смело можно брать за основу. После этого остается "всего лишь" связать NTDLL.DLL с миром Linux/BSD, т.е. написать транслятор запросов к драйверам. Это не так-то просто сделать, поскольку писать придется достаточно много, и работа отнимет не один день, и даже не одну неделю. С учетом отладки потребуется как минимум месяц. Но работа стоит того!

В результате в Linux/BSD наладится нормальная работа с NTFS и некоторыми другими драйверами ввода-вывода. С видеокартами, правда, все значительно сложнее, поскольку они, как и следует из рис. 8.3, взаимодействуют отнюдь не с диспетчером ввода-вывода (который находятся внутри NTOSKRNL.EXE), а с подсистемой Win32. В Windows 2000 она реализована в файле win2k.sys. Как обстоят дела в других системах — точно не знаю, да это и не важно. Драйвер win2k.sys — лишь малая часть того, что ему нужно для работы, и просто так перетащить его в Linux/BSD не получится. За ним неизбежно потянется все его окружение, и написать столько "оберток" будет практически нереально. То есть теоретически-то, конечно, реально, но сколько это потребует времени и сил? Переписать видеодрайвер гораздо проще, не говоря уже о том, что в этом случае он будет намного более производителен. Кстати говоря, компании nVIDIA (рис. 8.4) и ATI (рис. 8.5) в последнее время наладили выпуск драйверов Linux/BSD под наиболее популярные чипсеты, так что проблема снимается сама собой.

Рис. 8.4. Видеодрайверы для Linux x86 и x86_64 от nVIDIA

Рис. 8.5. Видеодрайверы для Linux x86, x86_64, IA64, FreeBSD x86 и Solaris x86/x64 от ATI

Пример реализации

Конкретные случаи переноса драйверов из мира Windows в Linux/BSD мне неизвестны, однако под MS-DOS такие случаи имеются. Речь идет о проекте Марка Руссиновича "NTFS for MS-DOS. Бесплатная версия (http://www.sysinternals.com/Utilities/NtfsDosProfessional.html) может только читать. Специальный мастер установки просит указать путь к системному каталогу Windows и создает две дискеты. Содержимое этих дискет показано в листингах 8.2 и 8.3.

Листинг 8.2. Содержимое первой дискеты NTFS for MS-DOS

30.10.2005 19:01   904 414 NTOSKRNL.gz

11.02.2002 09:39    89 472 ntfspro.exe

30.10.2005 19:00   314 665 NTFS.gz

30.10.2005 19:01     1 403 C_866.gz

  4 файлов       1 309 954 байт

   0 папок         146 944 байт свободно

Листинг 8.3. Содержимое второй дискеты NTFS for MS-DOS

30.10.2005 19:03   212 681 AUTOCHK.gz

30.10.2005 19:04   219 099 NTDLL.gz

30.10.2005 19:04     1 633 C_437.gz

30.10.2005 19:04     1 467 C_1252.gz

30.10.2005 19:04       746 L_INTL.gz

08.02.2002 10:45    56 748 ntfschk.exe

  6 файлов         492 374 байт

   0 папок         964 096 байт свободно

Начнем с первой дискеты. Она обычно бывает системной, поскольку NTFS для MS-DOS работает только из-под "черного экрана", однако для наглядности все системные файлы удалены. Здесь находится только один исполняемый файл ntfspro.exe, представляющий собой транслятор запросов, слинкованный с расширением защищенного режима WDOSX 0.96 DOS extender от Michael Tippach

NTFS.gz — это "родной" драйвер NTFS.SYS, извлеченный из системного каталога Windows, и для экономии места упакованный архиватором gzip. Для распаковки нам потребуется либо Linux, либо pkzip для Windows/MS-DOS. Сравнив его с оригинальным файлом драйвера, мы не найдем никаких изменений! NTOSKRNL.gz — это ядро системы (NTOSKRNL.EXE), точно таким же образом извлеченное и упакованное. Никаких изменений в нем нет.

На другой дискете находятся файлы NTDLL.gz (о происхождении которого догадаться нетрудно) и ntfschk.exe. Последний представляет собой полностью переписанный вариант штатной утилиты chkdsk.exe, поскольку, чтобы заставить консольное приложение заработать в MS-DOS, пришлось бы эмулировать еще множество функций, что в планы Руссиновича, очевидно, не входило.

Примечание

Тем не менее, легендарный хакер Юрий Харон все-таки создал расширитель, способный запускать Windows-приложения из-под голого DOS, без обращения к Windows вообще! Все умещается на одну дискетку. Сам расширитель можно скачать с http://www.doswin32.com:8080/index_en.html. Для некоммерческого применения он бесплатен.

Еще на дискетах содержатся файлы C_866.gz, AUTOCHK.gz, C_437.gz, C_1252.gz, L_INTL.gz, содержащие языковые страницы и прочую служебную мишуру, без которой можно в принципе и обойтись.

Ядро проекта NTFS для MS-DOS составляют три файла: NTOSKRNL.EXE, NTDLL.DLL и NTFS.SYS, которые помещаются в своеобразную "скорлупу" файла NTFSPRO.EXE, переводящего процессор в защищенный режим и транслирующего запросы MS-DOS на "язык", понятный NTFS.SYS, и наоборот. Как видите, это работает. Разумеется, Linux/BSD — это совсем не чистая MS-DOS. Ядро по-своему распределяет прерывания и другие системные ресурсы, поэтому при написании "скорлупы-оболочки" возникает множество технических проблем, но все они решаемы. Пример аналогичного решения можно найти в другом проекте Марка Руссиновича — NTFS для Windows 9x. Здесь также используется "скорлупа", создающая адекватное окружение для NTOSKRNL.EXE и транслятор запросов. Однако в данном случае эта "обертка" уже работает совсем не в голой MS-DOS, а в агрессивной Windows 9x, которая отличается от NT ничуть не меньше, чем Linux/BSD.

Так что, написать драйверную "скорлупу" для Linux/BSD вполне реально, и ничего фантастичного в этом нет. Ее достаточно создать лишь однажды, после чего в ней будет можно запускать различные драйверы. Почему бы нам, хакерам, не скооперироваться и не заняться этим? Например, создать новый проект на http://www.sourceforge.net, набрать группу и оттянуться по полной программе.

Восстановление удаленных файлов под файловыми системами ext2fs/ext3fs

Каждый из нас хотя бы однажды удалял ценный файл, а то и весь корневой каталог целиком! Резервной копии нет, времени на поиски утилит для восстановления — тоже. Как быть? К счастью, все необходимое уже включено в ваш любимый дистрибутив и всегда находится под рукой. Если говорить кратко, то это утилиты debugfs, lsdel, stat, cat, dump и undel. Если требуется чуть более подробная информация — читайте этот материал, рассказывающий о восстановлении данных на разделах ext2fs и, отчасти, на разделах ext3fs.

Информации по восстановлению данных под Linux практически нет. Такое впечатление, будто у линуксоидов данные никогда не исчезают. Исчезают, да еще и как! Ошибочное удаление файлов — это достаточно распространенное явление, наверное, даже более частное, чем в мире Microsoft. Под Windows большинство файловых операций осуществляется вручную с помощью Проводника или других интерактивных средств типа FAR или Windows Commander. Интерактивные среды есть и в Linux (KDE, GNOME, Midnight Commander), но большая часть фанатов Linux — поклонники командной строки. Командная же строка — это регулярные выражения и скрипты, т.е. автоматизированные средства управления — мощные, удобные, и, при неправильном использовании, разрушительные. Малейшая небрежность — и можете навсегда попрощаться со своими файлами!

Перефразируя Булгакова, можно сказать: мало того, что файл смертен, так он еще и внезапно смертен! Беда никогда не предупреждает о своем приходе, и администратору приходится быть постоянно начеку. Несколько секунд назад все было хорошо: цвела весна, винчестер оживленно стрекотал всеми своими головками, администратор отхлебывал кофе из черной кружки с надписью root, как вдруг сотни гигабайт ценнейших данных внезапно разлетелись на мелкие осколки. Все силы брошены на разгребание завалов и спасение всех, кого еще можно спасти.

Доступность исходных текстов драйвера файловой системы значительно упрощает исследование ее внутренней структуры, которая, кстати говоря, очень проста. Поэтому восстановление данных на разделах ext2fs/ext3fs — задача тривиальная. Файловые системы UFS и FFS, работающие под FreeBSD, устроены намного сложнее, к тому же достаточно скудно документированы. А ведь FreeBSD занимает далеко не последнее место в мире UNIX-совместимых операционных систем, и разрушения данных даже в масштабах небольшого городка происходят сплошь и рядом. К счастью, в подавляющем большинстве случаев информацию можно полностью восстановить.

Подготовка к восстановлению

В первую очередь, обязательно размонтируйте дисковый раздел, или, на худой конец, перемонтируйте его в режим "только на чтение". Лечение активных разделов зачастую только увеличивает масштабы разрушений. Если восстанавливаемые файлы находятся на основном системном разделе, у нас два пути — загрузиться с LiveCD или подключить восстанавливаемый жесткий диск на Linux-машину вторым.

Чтобы случайно что-нибудь не испортить, никогда не редактируйте диск напрямую. Работайте с его копией! Копию можно создать командой

cp /dev/sdb1 my_dump
, где
sdb1
— имя устройства, a
my_dump
— имя файла-дампа. Файл-дамп можно разместить на любом свободном разделе или скопировать на другую машину по сети. Все дисковые утилиты (lde, debugfs, fschk) не заметят подвоха и будут работать с ним как с "настоящим" разделом. При необходимости его даже можно смонтировать на файловую систему:
mount my_dump mount_point -о loop
, чтобы убедиться, что восстановление прошло успешно. Команда
cp my_dump /dev/sdb1
копирует восстановленный файл-дамп обратно в раздел, хотя делать это совсем необязательно. Проще (и безопаснее) копировать только восстанавливаемые файлы.

Восстановление удаленных файлов под ext2fs

Файловая система ext2fs все еще остается базовой файловой системой для многих клонов Linux, поэтому рассмотрим ее первой. Концепции, которые она исповедует, во многом схожи с NTFS, так что культурного шока при переходе с NTFS на ext2fs вы не испытаете. Подробное описание структуры хранения данных ищите в документе "Design and Implementation of the Second Extended Filesystem" (см. список рекомендованной литературы и ресурсов Интернета в данном разделе), а также книге Эндрю Таненбаума "Operating Systems: Design and Implementation", электронную версию которой можно раздобыть в e-Mule. Исходные тексты дисковых утилит (драйвер файловой системы, lde, debugfs) также не помешают.

Структура файловой системы

В начале диска расположен загрузочный сектор, который на незагрузочных разделах может быть пустым. За ним по смещению 1024 байта от начала первого сектора лежит суперблок (super-block), содержащий ключевую информацию о структуре файловой системы. В FAT и NTFS эта информация хранится непосредственно в загрузочном секторе. В первую очередь нас будет интересовать 32-разрядное поле

s_log_block_size
, расположенное по смещению
18h
байт от начала суперблока. Здесь хранится размер одного блока (block) или, в терминологии MS-DOS/Windows, кластера, выраженный в виде указателя позиции, на которую нужно сдвинуть число
200h
. В естественных единицах это будет звучать так:
block_size == 200h << s_log_block_size
(байт). То есть если
s_log_block_size
равен нулю, размер одного блока составляет
400h
байт или два стандартных сектора. Структура дискового тома, отформатированного под ext2fs, показана в листинге 8.4.

Листинг 8.4. Структура дискового тома, размеченного под ext2fs

смещение размер описание

-------- ------ --------

       0      1 boot record              ; Загрузочный сектор

         -- block group 0 --             ; Группа блоков 0

(1024 bytes)  1 superblock               ; Суперблок

       2      1 group descriptors        ; Дескриптор группы

       3      1 block bitmap             ; Карта свободных блоков

       4      1 inode bitmap             ; Карта свободных inode

       5    214 inode table              ; Массив inode

                                         ; (сведения о файлах)

     219   7974 data blocks              ; Блоки данных

                                         ; (файлы, каталоги)

         -- block group 1 --             ; Группа блоков 1

    8193      1 superblock backup        ; Копия суперблока

    8194      1 group descriptors backup ; Копия дескриптора группы

    8195      1 block bitmap             ; Карта свободных блоков

    8196      1 inode bitmap             ; Карта свободных inode

    8197    214 inode table              ; Массив inode

                                         ; (сведения о файлах)

    8408   7974 data blocks              ; Блоки данных

                                         ; (файлы, каталоги)

         -- block group 2 --             ; Группа блоков 2

   16385      1 block bitmap             ; Карта свободных блоков

   16386      1 inode bitmap             ; Карта свободных inode

   16387    214 inode table              ; Массив inode

                                         ; (сведения о файлах)

   16601   3879 data blocks              ; Блоки данных

                                         ; (файлы, каталоги)

Вслед за суперблоком идут дескрипторы групп (group descriptors) и карты свободного пространства (block bitmap/inode bitmaps которые не имеют большого практического значения для наших целей. Что же касается таблицы inode, расположенной за ними, то она заслуживает более подробного рассмотрения. В ext2fs (как и многих других файловых системах из мира UNIX), так называемый индексный дескриптор (inode) играет ту же самую роль, что и файловая запись в NTFS. Здесь сосредоточена вся информация о файле: тип файла (обычный файл, каталог, символьная ссылка и т.д.), его логический и физический размер, схема размещения на диске, время создания, модификации, последнего доступа или удаления, права доступа, а также ссылки на файл. Формат представления inode описан в листинге 8.5.

Листинг 8.5. Формат представления inode

смещение размер описание

-------- ------ --------

       0      2 i_mode        ; Формат представления

       2      2 i_uid         ; Uid пользователя

       4      4 i_size        ; Размер файла в байтах

       8      4 i_atime       ; Время последнего доступа к файлу

      12      4 i_ctime       ; Время создания файла

      16      4 i_mtime       ; Время модификации файла

      20      4 i_dtime       ; Время удаления файла

      24      2 i_gid         ; Gid группы

      26      2 i_links_count ; Количество ссылок на файл (0 — файл удален)

      28      4 i_blocks      ; Количество блоков, принадлежащих файлу

      32      4 i_flags       ; Различные флаги

      36      4 i_osdl        ; Значение, зависящее от ОС

      40 12 x 4 i_block       ; Ссылки на первые 12 блоков файла

      88      4 i_iblock      ; 1х INDIRECT BLOCK

      92      4 i_2iblock     ; 2x INDIRECT BLOCK

      96      4 i_3iblock     ; 3x INDIRECT BLOCK

     100      4 i_generation  ; Поколение файла (используется NFS)

     104      4 i_file_acl    ; Внешние атрибуты

     108      4 i_dir_acl     ; higher size

     112      4 i_faddr       ; Положение последнего фрагмента

     116     12 i_osd2        ; Структура, зависящая от ОС

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

DIRECT BLOCKS
непосредственно в теле inode. Каждый элемент массива представляет собой 32-битный номер блока. При среднем значении
BLOCK_SIZE
, равном 4 Кбайт, непосредственные блоки могут адресовать до
4×12 == 48
Кбайт данных. Если файл превышает этот размер, создаются один или несколько блоков косвенной адресации (
INDIRECT BLOCK
). Первый блок косвенной адресации (
1x INDIRECT BLOCK
или просто
INDIRECT BLOCK
) хранит ссылки на другие непосредственные блоки. Адрес этого блока хранится в поле
i_indirect_block
в inode. Как легко вычислить, он адресует порядка
BLOCK_SIZE/sizeof(DWORD) * BLOCK_SIZE == 4096/4 *4 
Мбайт данных. Если этого окажется недостаточно, создается косвенный блок двойной косвенной адресации (
2х INDIRECT BLOCK
или
DOUBLE INDIRECT BLOCK
), хранящий указатели на косвенные блоки, что позволяет адресовать (
BLOCK_SIZE/sizeof(DWORD))**2* BLOCK_SIZE =4096/4 ** 4096 == 4
Гбайт данных. Если же и этого все равно недостаточно, создается блок тройной косвенной адресации (
3х INDIRECT BLOCK
или
TRIPLE INDIRECT BLOCK
), содержащий ссылки на блоки двойной косвенной адресации. Иерархия непосредственных блоков и блоков косвенной адресации показана на рис. 8.6 (блок тройной косвенной адресации не показан).

Рис. 8.6. Порядок размещения файла на диске — иерархия непосредственных и косвенных блоков (блок косвенной адресации третьего порядка не показан)

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

Имя файла в inode не хранится. Ищите его внутри каталогов, представляющих собой массив записей, формат которого представлен в листинге 8.6.

Листинг 8.6. Формат представления массива каталогов

смещение размер описание

-------- ------ --------

       0      4 inode ; Ссылка на inode

       4      2 rec_len ; Длина данной записи

       6      1 name_len ; Длина имени файла

       7      1 file_type ; Тип файла

       8    ... name ; Имя файла

При удалении файла операционная система находит соответствующую запись в каталоге, обнуляет поле

inode
и увеличивает размер предшествующей записи (поле
rec_len
) на величину удаляемой записи. Таким образом, предшествующая запись "поглощает" удаленную. Хотя имя файла в течение некоторого времени остается нетронутым, ссылка на соответствующий ему индексный дескриптор (inode) оказывается уничтоженной. Это создает проблему, так как теперь придется разбираться, какому файлу принадлежит обнаруженное имя.

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

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

Техника восстановления удаленных файлов

В ext2fs полное восстановление файлов невозможно, даже если эти файлы были только что удалены. В этом отношении данная файловая система проигрывает как FAT, так и NTFS. Как минимум, теряется имя файла. Точнее говоря, теряется связь имен файлов с их содержимым. При удалении небольшого количества хорошо известных файлов эта проблема остается решаемой. Однако ситуация серьезно осложняется, если вы удалили несколько служебных подкаталогов, в которых никогда ранее не заглядывали.

Достаточно часто индексные дескрипторы назначаются в том же порядке, в котором создаются записи в таблице каталогов. Благодаря наличию расширений имен файлов (.c, .gz, .mpg, и т.д.) количество возможных комбинаций соответствий обычно оказывается сравнительно небольшим. Тем не менее, восстановить удаленный корневой каталог в автоматическом режиме никому не удастся (а вот NTFS с этим справляется без труда).

В целом, стратегия восстановления выглядит приблизительно так: сканируем таблицу индексных дескрипторов (inode table) и отбираем все записи, чье поле

i_links_count
равно нулю. Сортируем их по дате удаления, чтобы файлы, удаленные последними, оказались в верхних позициях списка. Как вариант, если вы помните примерное время удаления файла, можно просто наложить фильтр. Если соответствующие индексные дескрипторы еще не затерты вновь создаваемыми файлами, извлекаем список прямых/косвенных блоков и записываем их в дамп, корректируя его размер с учетом "логического" размера файла, за который отвечает поле
i_size
.

Восстановление удаленных файлов с помощью редактора Lde

Откройте редактируемый раздел или его файловую копию с помощью команд

lde my_dump
или
lde /dev/sdb1
. Редактор автоматически определяет тип файловой системы (в данном случае — ext2fs) и предлагает нажать любую клавишу для продолжения. Lde автоматически переключается в режим отображения суперблока и предлагает нажать клавишу для перехода в режим inode или клавишу <В> — для перехода в блочный режим (block-mode). Нажмите клавишу , и вы окажетесь в первом индексном дескрипторе, описывающем корневой каталог. Нажатие клавиши перемещает нас к следующему inode, а нажатие клавиши , — соответственно, к предыдущему. Пролистываем список индексных дескрипторов вниз, обращая внимание на поле
LINKS
. У удаленных файлов это поле равно нулю, и тогда поле
DELETION TIME
содержит время последнего удаления. Обнаружив подходящий inode по запомненному времени удаления, перемещаем курсор к первому блоку в списке
DIRECT BLOCKS
(где он должен находиться по умолчанию) и нажимаем клавишу . В появившемся меню выбираем пункт Block mode, viewing block under cursor (или сразу нажимаем клавиатурную комбинацию +). Редактор перемещается на первый блок удаленного файла. Просматривая его содержимое в шестнадцатеричном режиме, пытаемся определить, тот ли это файл, который требуется восстановить. Если вы нашли именно тот файл, который намеревались восстановить, нажмите для его восстановления клавиатурную комбинацию +, затем — клавишу , и введите имя файла, в который требуется восстановить дамп. Чтобы вернуться к просмотру следующего inode, нажмите клавишу .

Можно восстанавливать файлы и по их содержимому. Например, предположим, что нам известно, что удаленный файл содержит строку

hello, world
. Нажимаем , затем — клавишу (Search all block). Этим мы заставляем редактор искать ссылки на все блоки, в том числе и удаленные. Как вариант, можно запустить редактор с ключом
--all
. Но, так или иначе, затем мы нажимаем клавишу <В>, и, когда редактор перейдет в блочный режим, нажимаем клавишу и вводим искомую строку в ASCII. Находим нужный блок. Прокручивая его вверх и вниз, убеждаемся, что он действительно принадлежит тому самому файлу. Если это так, нажимаем +, давая редактору указание просматривать все индексные дескрипторы, содержащие ссылку на этот блок. Номер текущего найденного inode отображается в нижней части экрана.

Внимание!

Номер текущего найденного inode отображается именно в нижней части экрана. В верхней части отображается номер последнего просмотренного inode в режиме

inode
.

Переходим в режим inode нажатием клавиши , нажимаем клавишу <#> и вводим номер inode, который требуется просмотреть. Если дата удаления более или менее соответствует действительности, нажимаем +/ для сброса файла на диск. Если нет — возвращаемся в блочный режим и продолжаем поиск.

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

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

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

Восстановление с помощью отладчика файловой системы debugfs

Загружаем в отладчик редактируемый раздел или его копию. Сделать это можно с помощью команд

debugfs /dev/sdb1
или
debugfs my_dump
соответственно. Если мы планируем осуществлять запись на диск, необходимо указать ключ
-w
:
debugfs -w my_dump
или
debugfs -w /dev/sdb1
.

Чтобы просмотреть список удаленных файлов, даем команду

lsdel
(или
lsdel t_sec
, где
t_sec
 — количество секунд, истекших с момента удаления файла). На экране появится список удаленных файлов (разумеется, без имен). Файлы, удаленные более
t_sec
секунд назад (если эта опция задана), в данный список не попадут.

Команда

cat <N>
выводит содержимое текстового файла на терминал. Здесь
<N>
— номер inode, заключенный в угловые скобки. При выводе двоичных файлов разобрать смысл отображаемой на экране информации практически невозможно. Такие файлы должны сбрасываться в дамп командой
dump <N>new_filе_name
, где
new_file_name
— новое имя файла (с путем), под которым он будет записан в файловую систему, из-под которой был запущен отладчик debugfs. Файловая система восстанавливаемого раздела при этом остается неприкосновенной. Иными словами, команда должна даваться без ключа
--w
.

При желании можно восстановить файл непосредственно на самой восстанавливаемой файловой системе (что особенно удобно при восстановлении больших файлов). В этом нам поможет команда

undel <N>undel_file_name
, где
undel_file_name
— имя, которое будет присвоено файлу после восстановления.

Внимание!

Выполняя такую операцию, помните, что команда

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

Команда

stat <N>
отображает содержимое inode в удобочитаемом виде, а команда
mi <N>
позволяет редактировать их по своему усмотрению. Для ручного восстановления файла (откровенно говоря, этого не пожелаешь и врагу) мы должны установить счетчик ссылок (
link count
) на единицу, а время удаления (
deletion time
), наоборот, сбросить в ноль. Затем отдать команду
seti <N>
, помечающую данный inode как используемый, и для каждого из блоков файла выполнить команду
setb X
, где
X
— номер блока.

Совет

Перечень блоков, занимаемых файлом, можно просмотреть с помощью команды

stat
. В отличие от lde, она отображает не только непосредственные, но и косвенные блоки, что несравненно удобнее.

Остается только дать файлу имя, что осуществляется путем создания ссылки на каталог (

directory link
). Сделать это можно с помощью команды
ln <N>undel_file_name
, где
undel_file_name
— имя, которое будет дано файлу после восстановления (при необходимости имя восстанавливаемого файла указывается с полным путем).

Внимание!

Создание ссылок на каталог необратимо затирает оригинальные имена удаленных файлов.

После присвоения имени восстановленному файлу полезно дать команду

dirty
, чтобы файловая система была автоматически проверена при следующей загрузке. Как вариант, можно выйти из отладчика и вручную запустить команду
fsck
с ключом
-f
, форсирующим проверку.

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

stat dir_nam
e и запомнить номер inode, возвращенный этой командой.

Затем следует дать отладчику команду

dump <INODE>dir_file
, где
INODE
— номер сообщенного индексного дескриптора, a
dir_file
— имя файла "родной" файловой системы, в которую будет записан дамп. Полученный дамп следует загрузить в шестнадцатеричный редактор и просмотреть его содержимое в "сыром" виде. Все имена будут там. При желании можно написать утилиту-фильтр, выводящую только удаленные имена. На Perl это не замет и десяти минут.

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

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

dump_unused
, выводящая на терминал все неиспользуемые блоки. Имеет смысл перенаправить вывод в файл, запустить hexedit и покопаться в этой куче хлама — это, по крайней мере, проще, чем искать по всему диску. На дисках, заполненных более, чем на три четверти, этот трюк сокращает массу времени.

Таким образом, можно сделать вывод о том, что отладчик debugfs в значительной мере автоматизирует восстановление удаленных файлов, однако восстановить файл с разрушенным inode он не способен, и без lde в данном случае не обойтись.

Восстановление удаленных файлов с помощью утилиты R-Studio

Утилита R-Studio for NTFS, вопреки своему названию, поддерживает не только NTFS, но и файловые системы ext2fs/ext3fs (рис. 8.7). С точки зрения обычных пользователей, это — хорошее средство для автоматического восстановления удаленных файлов. Утилита предоставляет интуитивно-понятный интерфейс, проста в управлении и в благоприятных ситуациях позволяет восстанавливать удаленные файлы несколькими щелчками мыши. К недостаткам R-Studio for NTFS можно отнести:

□ отсутствие гарантий на восстановление файлов;

□ невозможность восстановления имен удаленных файлов;

□ отсутствие поддержки ручного восстановления;

□ невозможность восстановления файлов с разрушенным inode, несмотря на то, что под ext2fs этого можно добиться достаточно простым путем.

Рис. 8.7. Утилита R-Studio for NTFS восстанавливает удаленные файлы на разделе ext2fs. К сожалению, имена удаленных файлов не восстанавливаются

Обобщая, можно сказать, для профессионального использования R-Studio катастрофически не подходит.

Восстановление удаленных файлов на разделах ext3fs

Файловая система ext3fs фактически представляет собой аналог ext2fs, но с поддержкой протоколирования (в терминологии NTFS — транзакций). В отличие от ext2fs, она намного бережнее относится к массиву каталогов. Так, при удалении файла ссылка на inode уже не уничтожается, что упрощает автоматическое восстановление оригинальных имен. Тем не менее, поводов для радости у нас нет никаких, поскольку в ext3fs перед удалением файла список принадлежащих ему блоков тщательно вычищается. В результате этого восстановление становится практически невозможным (рис. 8.8). Нефрагментированные файлы с более или менее осмысленным содержимым (например, исходные тексты программ) еще можно собрать по частям, но и времени на это потребуется немало. К счастью, блоки косвенной адресации не очищаются, а это значит, что мы теряем лишь первые

12 * BLOCK_SIZE
байт каждого файла. На типичном разделе объемом около 10 Гбайт значение
BLOCK_SIZE
обычно равно 4 или 8 килобайтам, т.е., реальные потери составляют менее 100 Кбайт. По современным понятиям, это сущие пустяки! Конечно, без этих 100 Кбайт большинство файлов просто не запустятся, однако найти на диске двенадцать недостающих блоков — вполне реальная задача. Если повезет, они окажутся расположенными в одном или двух непрерывных отрезках, но такое везение не гарантируется. Тем не менее, непрерывные отрезки из 6–12 блоков достаточно часто встречаются даже на сильно фрагментированных разделах.

Рис. 8.8. Утилита R-Studio восстанавливает удаленные файлы на разделе ext3fs Имена файлов восстановлены, но нет самих файлов как таковых (их длина равна нулю, так как список блоков прямой адресации затерт)

Как мы будем действовать? Необходимо найти хотя бы один блок, гарантированно принадлежащий файлу и при этом расположенный за границей в 100 Кбайт от его начала. Это может быть текстовая строка, информация об авторских правах разработчика или любая другая характерная информация! Короче говоря, нам нужен номер блока. Пусть для определенности он будет равен

0x1234
. Записываем его в обратном порядке так, чтобы младший байт располагался по меньшему адресу, и выполняем поиск
34h 12h 00h 00h
— именно это число будет присутствовать в косвенном блоке. Отличить косвенный блок от всех остальных блоков (например, блоков, принадлежащих файлам данных) очень легко — он представляет собой массив 32-битных номеров блоков, более или менее монотонно возрастающих. Блоки с двойной и тройной косвенной адресацией отыскиваются по аналогичной схеме.

Проблема состоит в том, что одни и те же блоки в разное время могли принадлежать различным файлам, а это значит, что они могли принадлежать и различным косвенным блокам. Как разобраться, какой из найденных блоков является искомым? Да очень просто! Если хотя бы одна из ссылок косвенного блока указывает на уже занятый блок, данный косвенный блок принадлежит давно удаленному файлу и, следовательно, не представляет для нас интереса.

По правде говоря, debugfs обеспечивает лишь ограниченную поддержку ext3fs. В частности, команда

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

Рекомендуемые источники

"Design and Implementation of the Second Extended File system" — подробное описание файловой системы ext2fs от разработчиков данного проекта (на английском языке). Это руководство доступно по адресу: http://e2fsprogs.sourceforge.net/ext2intro.html.

"Linux Ext2fs Undeletion mini-HOWTO" — краткая, но доходчивая инструкция по восстановлению удаленных файлов на разделах ext2fs (на английском языке). Данная инструкция доступа по адресу: http://www.praeclarus.demon.co.uk/tech/e2-undel/howto.txt.

"Ext2fs Undeletion of Directory Structures mini-HOWTO" — краткое руководство по восстановлению удаленных каталогов на разделах ext2fs (на английском языке): http://www.faqs.org/docs/Linux-mini/Ext2fs-Undeletion-Dir-Struct.html.

"HOWTO-undelete" — еще одно руководство по восстановлению удаленных файлов на разделах ext2fs с помощью редактора lde (на английском языке): http://lde.sourceforge.net/UNERASE.txt.

Восстановление удаленных файлов на разделах UFS

Файловая система UNIX (UNIX File System, UFS) — это основная файловая система для систем BSD, устанавливаемая по умолчанию. Многие коммерческие варианты UNIX также используют либо UFS в чистом виде, либо одну из файловых систем, созданных на ее основе и очень на нее похожих. В отличие от ext2fs, хорошо документированной и изученной вдоль и поперек, UFS в доступной литературе описана крайне поверхностно. Единственным источником информации становятся исходные тексты, в которых не так-то просто разобраться! Существует множество утилит, восстанавливающих уничтоженные данные (или, во всяком случае, пытающихся это делать), но на практике все они оказываются неработоспособными. Это, в общем-то, и неудивительно, поскольку автоматическое восстановление удаленных файлов под UFS невозможно в принципе. Тем не менее, файлы, удаленные с разделов UFS, вполне возможно восстановить вручную, если, конечно, знать как это делается.

Исторический обзор

UFS ведет свою историю от файловой системы s5. Это — самая первая файловая система, написанная для UNIX в далеком 1974 году. Файловая система s5 была крайне простой и неповоротливой (по некоторым данным, ее производительность составляла от 2 до 5 процентов от "сырой" производительности "голого" диска). Тем не менее, понятия суперблока (super-block), файловых записей (inodes) и блоков данных (blocks) в ней уже существовали.

В процессе работы над дистрибутивом 4.2 BSD, вышедшим в 1983 году, оригинальная файловая система была несколько усовершенствована. Были добавлены длинные имена файлов и каталогов, символические ссылки и т.д. Так родилась UFS.

В 4.3 BSD, увидевшей свет уже в следующем году, улучшения носили намного более радикальный, можно даже сказать — революционный — характер. Появились концепции фрагментов (fragments) и групп цилиндров (cylinder groups). Быстродействие файловой системы существенно возросло, что и позволило разработчикам назвать ее быстрой файловой системой (Fast File System, FFS).

Все последующие версии линейки 4.x BSD прошли под знаменем FFS, но в 5.x BSD файловая система вновь изменилась. Для поддержки дисков большого объема ширину всех адресных полей пришлось удвоить: 32-битная нумерация фрагментов уступила место 64-битной. Были внесены и другие, менее существенные усовершенствования.

Таким образом, на практике мы имеем дело с тремя различными файловыми системами, не совместимыми друг с другом на уровне базовых структур данных. Тем не менее, некоторые источники склонны рассматривать FFS как надстройку над UFS. Например, документ "Little UFS2 FAQ" (http://sixshooter.v6.thrupoint.net/jeroen/faq.html) утверждает, что UFS и UFS2 определяют раскладку данных на диске, причем FFS реализована как надстройка над UFS/UFS2 и отвечает за структуру каталогов и оптимизацию операций доступа к диску. Однако если заглянуть в исходные тексты файловой системы, можно обнаружить два подкаталога — /ufs и /ffs. В /ffs находится определение суперблока (базовой структуры, отвечающей за раскладку данных), а в /ufs — определения inode и структуры каталогов, что опровергает данный тезис, с точки зрения которого все должно быть с точностью "до наоборот".

Чтобы не увязнуть в болоте терминологических тонкостей, под UFS мы будем понимать основную файловую систему 4.5 BSD, а под UFS2 — основную файловую систему 5.x BSD.

Структура UFS

В целом, UFS очень похожа на ext2fs — те же inode, блоки данных, файлы, каталоги. Тем не менее, есть между этими файловыми системами и различия. В ext2fs имеется только одна группа индексных дескрипторов (inodes), и только одна группа блоков данных для всего раздела. В отличие от ext2fs, UFS делит раздел на несколько зон одинакового размера, называемых группами цилиндров. Каждая зона имеет свою группу индексных дескрипторов и свою группу блоков данных, независимых ото всех остальных зон. Иначе говоря, индексные дескрипторы описывают блоки данных той и только той зоны, к которой они принадлежат. Это повышает быстродействие файловой системы, так как головка жесткого диска совершает более короткие перемещения. Кроме того, такая организация упрощает процедуру восстановления при значительном разрушении данных, поскольку, как показывает практика, обычно гибнет только первая группа индексных дескрипторов. Случаи, когда гибнут все группы, встречаются крайне редко. Предполагаю, что для того, чтобы умышленно этого добиться, диск потребуется положить под гидравлический пресс.

Диаграмма, осуществляющая сравнение файловых систем s5 и UFS, представлена на рис. 8.9. В UFS каждый блок разбит на несколько фрагментов фиксированного размера, предотвращающих потерю свободного пространства в хвостах файлов. В результате этого использование блоков большого размера уже не кажется расточительной идеей, напротив, это увеличивает производительность и уменьшает фрагментацию. Если файл использует более одного фрагмента в двух несмежных блоках, он автоматически перемещается на новое место, в наименее фрагментированный регион свободного пространства. Таким образом, фрагментация в UFS очень мала или же совсем отсутствует, что существенно облегчает восстановление удаленных файлов и разрушенных данных.

Рис. 8.9. Структура файловых систем s5/ext2fs (а) и UFS (б)

Адресация ведется либо по физическим смещениям, измеряемым в байтах и отсчитываемым от начала группы цилиндров (реже — от начала раздела UFS), либо в номерах фрагментов, отсчитываемых от тех же самых точек. Допустим, размер блока составляет 16 Кбайт, разбитых на 8 фрагментов. Тогда 69-й сектор будет иметь смещение

512 * 69 == 35328
байт или
1024 * (16/8)/512 * 69 == 276
фрагментов.

В начале раздела расположен загрузочный сектор, затем следует суперблок, за которым находится одна или несколько групп цилиндров (рис. 8.10). Для перестраховки копия суперблока дублируется в каждой группе. Загрузочный сектор не дублируется, но по соображениям унификации и единообразия под него просто выделяется место, таким образом, относительная адресация блоков в каждой группе остается неизменной.

Рис. 8.10. Последовательно расположенные группы цилиндров

В UFS суперблок располагается по смещению 8192 байт от начала раздела, что соответствует 16-му сектору. В UFS2 он "переехал" на 65536 байт (128 секторов) от начала, освобождая место для дисковой метки и первичного загрузчика операционной системы, а для действительно больших (в исходных текстах они обозначены как "piggy") систем предусмотрена возможность перемещения суперблока по адресу 262144 байт (целых 512 секторов).

Среди прочей информации суперблок содержит:

cblkno
— смещение первой группы блока цилиндров, измеряемое во фрагментах, отсчитываемых от начала раздела;

fs_iblkno
— смещение первого inode в первой группе цилиндров (фрагменты от начала раздела);

fs_dblkno
— смещение первого блока данных в первой группе цилиндров (фрагменты от начала раздела);

fs_ncg
— количество групп цилиндров;

fs_bsize
— размер одного блока в байтах;

fs_fsize
— размер одного фрагмента в байтах;

fs_frag
— количество фрагментов в блоке;

fs_fpg
— размер каждой группы цилиндров, выраженный в блоках (также может быть найден через
fs_cgsize
).

Для перевода смещений, выраженных во фрагментах, в номера секторов, служит следующая формула:

sec_n(fragment_offset) == fragment_offset* (fs_bsize/fs_frag/512)
или ее более короткая разновидность:
sec_n(fragment_offset) == fragment_offset*fs_fsize /512
.

Структура суперблока определена в файле /src/ufs/ffs/fs.h и в упрощенном виде выглядит, как показано в листинге 8.7.

Листинг 8.7. Формат суперблока (второстепенные поля опущены)

struct fs {

/* 0x00 */ int32_t fs_firstfield;    /* Связный список файловых систем */

/* 0x04 */ int32_t fs_unused_1;      /* для внутренних суперблоков */

/* 0x08 */ ufs_daddr_t fs_sblkno;

 /* Адрес суперблока в файловой системе (фс) */

/* 0x0C */ ufs_daddr_t fs_cblkno;    /* Смещение блока цилиндров в фс */

/* 0x10 */ ufs_daddr_t fs_iblkno;    /* Смещение блоков inode в фс */

/* 0x14 */ ufs_daddr_t fs_dblkno;    /* Смещение 1-го блока данных после

                                        группы цил. */

/* 0x18 */ int32_t fs_cgoffset;      /* Смещение группы цилиндров */

/* 0x1C */ int32_t fs_cgmask;        /* Используется в calc mod fs_ntrak */

/* 0x20 */ time_t  fs_time;          /* Время последней записи */

/* 0x24 */ int32_t fs_size;          /* Количество блоков в фс */

/* 0x28 */ int32_t fs_dsize;         /* Количество блоков данных в фс */

/* 0х2С */ int32_t fs_nog;           /* Количество групп цилиндров */

/* 0x30 */ int32_t fs_bsize;         /* Размер базовых блоков в фс */

/* 0x34 */ int32_t fs_fsize;         /* Размер фрагментов блоков в фс */

/* 0x38 */ int32_t fs_frag;          /* Количество фрагментов в блоке в фс */


/* Параметры конфигурации */

/* 0x3C */ int32_t fs_minfree;       /* Мин. процент свободных блоков */

/* 0x40 */ int32_t fs_rotdelay;      /* Мин. задержка (мс) для оптимального

                                        след. блока */

/* 0x44 */ int32_t fs_rps;           /* Обороты диска в минуту */


/* Размеры, определяемое кол-вом гц и их размерами */

/* 0x98 */ ufs_daddr_t fs_csaddr;     /* Адрес блока информации гц */

/* 0х9С */ int32_t fs_cssize;         /* Размер блока информации гц */

/* 0xA0 */ int32_t fs_cgsize;         /* Размер группы цилиндров */


/* Поля, которые могут быть вычислены на основании остальных */

/* 0хВ4 */ int32_t fs_cpg;             /* Кол-во цилиндров в группе */

/* 0xB8 */ int32_t fs_ipg;             /* Кол-во Inode на группу */

/* 0xBC */ int32_t fs_fpg;             /* Кол-во блоков в группе * fs_frag */


/* Поля, очищаемые при монтировании */

/* 0xD0 */ int8_t fs_fmod;             /* Флаг модификации суперблока */

/* 0xD1 */ int8_t fs_clean;            /* Флаг "чистой" (clean) фс */

/* 0xD2 */ int8_t fs_ronly;            /* Флаг защиты от записи */

/* 0xD3 */ int8_t fs_flags;            /* См. поле fs_ flags */

/* 0xD4 */ u_char fs_fsmnt[MAXMNTLEN]; /* Путь монтирования фс */

};

За концом суперблока, на некотором отдалении от него, находится первая группа цилиндров. В начале каждой группы расположена служебная структура

cg
, представляющая собой описатель группы цилиндров и содержащая магическую последовательность
55h 02h 09h
, по которой все уцелевшие группы можно найти даже при полностью испорченном суперблоке. Штатным образом стартовые адреса всех последующих групп вычисляются путем умножения номера группы на ее размер, содержащийся в поле
fs_cgsize
.

Другие важные параметры:

cg_cgx
— порядковый номер группы, отсчитываемый от нуля;

cg_old_niblk
— количество inode в данной группе;

cg_ndblk
— количество блоков данных в данной группе;

csum
— количество свободных inode и блоков данных в данной группе;

cg_iusedoff
— смещение карты занятых inode, отсчитываемое от начала данной группы (в байтах);

cg_freeoff
— смещение карты свободного пространства (байты от начала группы).

Структура

cg
определена в файле /src/ufs/ffs/fs.h и выглядит следующим образом — листинг 8.8.

Листинг 8.8. Структура описателя группы цилиндров

#define СG_MAGIC 0x090255

#define MAXFRAG 8


struct cg {

/* 0x00 */ int32_t cg_firstfield;     /* Связный список групп цилиндров */

/* 0x04 */ int32_t cg_magic;          /* Магическая последовательность */

/* 0x08 */ int32_t cg_old_time;       /* Время последней записи */

/* 0x0C */ int32_t cg_cgx;            /* Мы находимся в гц номер cgx */

/* 0x10 */ int16_t cg_old_ncyl;       /* Кол-во цилиндров в этой гц */

/* 0x12 */ int16_t cg_old_niblk;      /* Кол-во блоков inode в этой гц */

/* 0x14 */ int32_t cg_ndblk;          /* Кол-во блоков данных в этой гц */

/* 0x18 */ struct csum cg_cs;         /* Краткое описание цилиндра */

/* 0x28 */ int32_t cg_rotor;          /* Положение посл. исп. блока */

/* 0x2C */ int32_t cg_frotor;         /* Положение посл. исп. фрагмента */

/* 0x30 */ int32_t cg_irotor;         /* Положение посл. исп. inode */

/* 0x34 */ int32_t cg_frsum[MAXFRAG]; /* Счетчик доступных фрагментов */

/* 0x54 */ int32_t cg_old_btotoff;    /* (int32) блоков на цилиндр */

/* 0x58 */ int32_t cg_old_boff;       /* (u_int16) своб. позиций блоков */

/* 0x5C */ int32_t cg_iusedoff;       /* (u_int8) карта исп. inode */

/* 0x60 */ int32_t сg_freeoff;        /* (u_int8) карта своб. блоков */

/* 0x64 */ int32_t cg_nextfreeoff;    /* (u_int8) след. своб. блок */

/* 0x68 */ int32_t cg_clustersumoff;  /* (u_int32) счетчик своб. кластеров */

/* 0x6C */ int32_t cg_clusteroff;     /* (u_int8) карта своб. кластеров */

/* 0x70 */ int32_t cg_nclusterblks;   /* Кол-во кластеров в этой гц */

/* 0x74 */ int32_t cg_niblk;          /* Кол-во блоков inode в этой гц */

/* 0x78 */ int32_t cg_initediblk;     /* Посл. инициализированный inode */

/* 0х7С */ int32_t cg_sparecon32[3];  /* Зарезервировано */

/* 0x00 */ ufs_time_t cg_time;        /* Время последней записи */

/* 0x00 */ int64_t cg_sparecon64[3];  /* Зарезервировано */

/* 0x00 */ u_int8_t cg_space[1];      /* Место для карт гц */

/* реально больше */

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

За картами следует массив inode, смещение которого содержится в поле

cg_iusedoff
(адрес первой группы inode продублирован в суперблоке). По сути, в UFS структура inode ничем не отличается от ext2fs, только расположение полей другое. К тому же, имеется только один блок косвенной адресации вместо трех, но это уже детали, не имеющие большого практического значения. Рассмотрим назначение фундаментальных полей, к числу которых принадлежат:

di_nlink
— количество ссылок на файл (0 означает "удален");

di_size
— размер файла в байтах;

di_atime
/
di_atimensec
— время последнего доступа к файлу;

di_mtime
/
di_mtimensec
— время последней модификации;

di_ctime
/
di_ctimensec
— время последнего изменения inode;

di_db
— адреса первых 12 блоков данных файла, отсчитываемые во фрагментах от начала группы цилиндров;

di_ib
— адрес блоков косвенной адресации (фрагменты от начала группы).

Сама структура inode определена в файле /src/ufs/ufs/dinode.h. Для UFS1 эта структура выглядит, как показано в листинге 8.9 и на рис. 8.11.

Рис. 8.11. Схематичное изображение inode

Листинг 8.9. Структура inode в UFS1

struct dinode {

/* 0x00 */ uint16_t di_mode;          /*   0: IFMT, права доступа; */

                                      /*      см. ниже */

/* 0x02 */ int16_t  di_nlink;         /*   2: Счетчик ссылок */

/* 0x04 */ union {

            uint16_t oldids[2];       /*   4: Ffs: старые ID */

                                      /*      пользователя и группы */

            int32_t  inumber;         /*   4: Lfs: номер inode */

           } di_u;

/* 0x08 */ u_int64_t   di_size;       /*   8: Счетчик байтов файла */

/* 0x10 */ int32_t     di_atime;      /*  16: Время последнего доступа */

/* 0x14 */ int32_t     di_atimensec;  /*  20: Время последнего доступа */

/* 0x18 */ int32_t     di_mtime;      /*  24: Время последней */

                                      /*      модификации */

/* 0x1C */ int32_t     di_mtimensec;  /*  28: Время последней */

                                      /*      модификации */

/* 0x20 */ int32_t     di_ctime;      /*  32: Время последнего */

                                      /*      изменения inode */

/* 0x24 */ int32_t     di_ctimensec;  /*  36: Время последнего */

                                      /*      изменения inode */

/* 0x28 */ ufs_daddr_t di_db[NDADDR]; /*  40: Непоср. дисковые блоки */

/* 0x58 */ ufs_daddr_t di_ib[NIADDR]; /*  88: Косв. дисковые блоки */

/* 0x64 */ u_int32_t   di_flags;      /* 100: Флаги статуса (chflags) */

/* 0x68 */ int32_t     di_blocks;     /* 104: Факт, занятые блоки */

/* 0x6C */ int32_t     di_gen;        /* 108: Номер генерации */

/* 0x70 */ u_int32_t   di_uid;        /* 112: Владелец файла */

/* 0x74 */ u_int32_t   di_gid;        /* 116: Группа файла */

/* 0x78 */ int32_t     di_spare[2];   /* 120: Зарезервировано */

};

В UFS2 формат inode был существенно изменен — появилось множество новых полей, удвоилась ширина адресных полей (листинг 8.10). Что это обозначает для нас в практическом плане? Смещения всех полей изменились, только и всего, а общий принцип работы с индексными дескрипторами остался прежним.

Листинг 8.10. Структура inode в USF2

struct ufs2_dinode {

/* 0x00 */ u_int16_t    di_mode;         /*   0: IFNT, права доступа; */

                                         /*      см. ниже */

/* 0x02 */ int16_t      di_nlink;        /*   2: Счетчик ссылок */

/* 0x04 */ u_int32_t    di_uid;          /*   4: Владелец файла */

/* 0x08 */ u_int32_t    di_gid;          /*   8: Группа файла */

/* 0x0C */ u_int32_t    di_blksize;      /*  12: Размер блока Inode */

/* 0x10 */ u_int64_t    di_size;         /*  16: Счетчик байтов файла */

/* 0x18 */ u_int64_t    di_blocks;       /*  24: Практически занятые байты */

/* 0x20 */ ufs_time_t   di_atime;        /*  32: Время последнего доступа */

/* 0x28 */ ufs_time_t   di_mtime;        /*  40: Время последней */

                                         /*      модификации */

/* 0x30 */ ufs_time_t   di_ctime;        /*  48: Время последнего */

                                         /*      изменения inode */

/* 0x38 */ ufs_time_t   di_birthtime;    /*  56: Время создания Inode */

/* 0x40 */ int32_t      di_mtimensec;    /*  64: Время последней */

                                         /*      модификации */

/* 0x44 */ int32_t      di_atimensec;    /*  68: Время последнего доступа */

/* 0x48 */ int32_t      di_ctimensec;    /*  72: Время последнего доступа */

/* 0x4C */ int32_t      di_birthnsec;    /*  76: Время создания Inode */

/* 0x50 */ int32_t      di_gen;          /*  80: Номер генерации */

/* 0x54 */ u_int32_t    di_kernflags;    /*  84: Флаги ядра */

/* 0x58 */ u_int32_t    di_flags;        /*  88: Флаги статуса (chflags) */

/* 0x5C */ int32_t      di_extsize;      /*  92: Блок внешних атрибутов */

/* 0x60 */ ufs2_daddr_t di_extb[NXADDR]; /*  96: Блок внешних атрибутов */

/* 0x70 */ ufs2_daddr_t di_db[NDADDR];   /* 112: Непоср. дисковые блоки */

/* 0xD0 */ ufs2_daddr_t di_ib[NIADDR];   /* 208: Косв. дисковые блоки */

/* 0xE8 */ int64_t      di_spare[3];     /* 232: Зарезервировано */

};

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

DIRBLKSIZ
, в структурах типа
direct
, выровненных по 4-х байтной границе.

Рис. 8.12. Хранение имен файлов и каталогов

Структура

direct
определена в файле /src/ufs/ufs/dir.h (листинг 8.11) и содержит: номер inode, описывающий данный файл, тип файла, его имя, а также длину самой структуры
direct
, используемую для нахождения следующей структуры этого типа в блоке.

Листинг 8.11. Структура

direct
, отвечающая за хранение имен файлов и каталогов

struct direct {

/* 0x00 */ u_int32_t d_ino;                 /* Номер inode данной записи */

/* 0x04 */ u_int16_t d_reclen;              /* Длина данной записи */

/* 0x06 */ u_int8_t  d_type;                /* Тип файла, см. ниже */

/* 0x07 */ u_int8_t  d_namlen;              /* Длина строки в d_name */

/* 0x08 */ char      d_name[MAXNAMLEN + 1]; /* Имя с длиной <= MAXNAMLEN */

};

На этом описание файловой системы UFS можно считать законченным. Для ручного восстановления данных приведенной информации вполне достаточно.

На развалинах империи

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

□ В суперблоке обновляется поле

fs_time
(время последнего доступа к разделу).

□ В суперблоке обновляется структура

fs_cstotal
(количество свободных inode и блоков данных в разделе).

□ В группе цилиндров обновляются карты занятых inode и блоков данных. Inode и все блоки данных удаляемого файла помечаются как освобожденные.

□ В inode родительского каталога обновляются поля времени последнего доступа и времени последней модификации.

□ В inode родительского каталога обновляется поле времени последнего изменения inode.

□ В inode удаляемого файла обнуляются поля

di_mode
(IFMT, права доступа),
di_nlink
(количество ссылок на файл) и
di_size
(размер файла).

□ В inode удаляемого файла затираются нулями поля

di_db
(массив указателей на 12 первых блоков файла) и
di_ib
(указатель на блок косвенной адресации).

□ В inode удаляемого файла обновляются поля времени последней модификации и последнего изменения inode, время последнего доступа при этом остается неизменным.

□ В inode удаляемого файла обновляется поле

di_spare
. В исходных текстах оно помечено как зарезервированное, но просмотр дампа показывает, что это не так. Судя по всему, здесь хранится нечто вроде последовательности обновления (update sequence), используемой для контроля целостности inode. Однако это только мое предположение.

□ В каталоге удаленного файла размер предшествующей структуры

direct
увеличивается на значение
d_reclen
, в результате чего она как бы "поглощает" имя удаляемого файла. Однако физического затирания имени не происходит. Во всяком случае оно затирается не сразу, а лишь в тот момент, когда в этом возникнет реальная необходимость.

Средства восстановления файлов

Обнаружив, что один или несколько файлов были непреднамеренного удалены, немедленно демонтируйте раздел и запустите дисковый редактор, работающий на секторном уровне. Например, можно воспользоваться вариантом уже описанного редактора lde, переписанным для BSD. К сожалению, в моей системе (4.5 BSD) он работает крайне нестабильно. Так, например, он не отображает основные структуры данных в удобочитаемом виде, хотя поддержка UFS в нем заявлена. При наличии достаточного количества свободного дискового пространства можно скопировать раздел в файл и натравить на него любой hex-редактор (например, BIEW). Как вариант, можно открыть непосредственно само устройство раздела (например,

/dev/ad0s1a
). Наконец, можно загрузить компьютер с загрузочного CD с Windows РЕ и воспользоваться любым Windows-редактором — от Microsoft Disk Probe до Runtime DiskExplorer. Можно загрузиться даже с загрузочной дискеты MS-DOS и запустить Norton Disk Editor (правда, Norton Disk Editor не поддерживает ни диски большого объема, ни устройства SCSI). Наконец, можно запустить KNOPPIX или любой дистрибутив Live Linux, ориентированный на восстановление данных. Правда, в большинстве "реанимационных" дистрибутивов, в частности, Frenzy 0.3, никакого дискового редактора вообще нет.

В общем, выбор средств восстановления достаточно широк.

Техника восстановления удаленных файлов

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

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

А что делать, если файл фрагментирован? Первые 13 блоков (именно блоков, а не фрагментов!) придется собирать руками. В идеальном случае это будет один непрерывный регион. Хуже, если первый фрагмент расположен в "чужом" блоке (т.е. блоке, частично занятом другим файлом), а оставшиеся 12 блоков находятся в одном или нескольких регионах. На практике, однако, достаточно трудно представить себе ситуацию, в которой первые 13 блоков были бы сильно фрагментированы, ведь UFS поддерживает фоновую дефрагментацию. Такое может произойти только при масштабной перегруппировке большого количества файлов, что в реальной жизни практически никогда не встречается. Поэтому будем считать, что 13-й блок файла найден. В массив непосредственной адресации он уже не помещается (там содержатся только 12 блоков), и ссылка на него, как и на все последующие блоки файла, должна содержаться в блоках косвенной адресации. Как вы помните, блоки косвенной адресации при удалении файла помечаются как свободные, но не затираются сразу же. Затирание происходит только по мере реальной необходимости, и это существенно упрощает нашу задачу.

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

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

Внимание!

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

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

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

Оптимизация производительности файловой системы

В отличие от Windows, Linux поддерживает целый спектр файловых систем различного типа и назначения: minix, ext2fs, ext3fs, ReiserFS, XFS, JFS, UFS, FFS… Какую файловую систему выбрать? Как правильно ее настроить? Стандартный выбор, предлагаемый составителями дистрибутива по умолчанию, не всегда оптимален. Как правило, быстродействие системы можно значительно улучшить.

Жесткий диск — надежное и быстрое устройство. Но процессор еще быстрее! И дисковая подсистема, несмотря на все усилия инженеров, по- прежнему остается слабейшим звеном, сдерживающим быстродействие всего компьютера в целом. А ведь объемы обрабатываемых данных все растут и растут.

Большинство материнских плат, выпущенных после 2000 года, несут на своем борту интегрированный RAID-контроллер, поддерживающий режимы RAID-0 ("stripe" mode — режим чередования, при котором данные пишутся на несколько жестких дисков сразу) и RAID-1 ("mirror" mode — зеркальный режим, при котором жесткие диски дублируют друг друга). Режим чередования значительно повышает производительность — два диска работают приблизительно в 1,5 раза быстрее, а четыре — примерно в 3,5 раза быстрее, чем один.

Обладатели ядра с версией 2.4 или более современной могут использовать программные реализации RAID (software RAID), практически не уступающие по скорости аппаратным реализациям. Стоит, правда, отметить, что программные RAID несколько повышают нагрузку на процессор. Более древние ядра (кстати говоря, уже практически вышедшие из употребления), скорее всего, потребуют установки дополнительного программного обеспечения. Более подробную информацию по данному вопросу можно найти здесь: http://www.tldp.org/HOWTO/Software-RAID-HOWTO.html.

Большинство руководств настоятельно рекомендуют подключать программные RAID к различным IDE-каналам, т.е. разводить диски по "своим" шлейфам. Проблема в том, что типичная материнская плата имеет всего два IDE- канала. При этом, помимо жестких дисков требуется еще, как минимум, один оптический привод! Для достижения наивысшей скорости приходится приобретать материнскую плату, оснащенную несколькими каналами IDE. Что поделаешь — оптимизация требует жертв! В частности, плата EPOX 4PCA3+ содержит целых 6 каналов IDE, жаль лишь, что отнюдь не всем она по карману. На практике совмещать два жестких диска на одном шлейфе вполне возможно. Это совсем не страшно. Они могут работать почти параллельно (падение скорости составит около 15%). Современные накопители освобождают шину на время выполнения медленных операций, но шина все-таки одна, а накопителей два, вот им и приходится за нее конкурировать. А вот жесткий диск с оптическим приводом на одном шлейфе лучше не совмещать, так как в некоторых случаях скорость падает в разы. Чтобы выйти из этого положения, попробуйте отключить у оптического привода режим DMA, возможно, это поможет винчестеру заработать быстрее. Ряд примеров, иллюстрирующих производительность различных вариантов реализации программных RAID, приведены на рис. 8.13–8.15.

Рис. 8.13. Программный RAID (один диск — один канал)

Рис. 8.14. Программный RAID (два диска — два канала)

Рис. 8.15. Программный RAID (четыре диска — четыре канала)

Дисковый массив, состоящий из 12 винчестеров, подключенных к EPOX 4PCA3+, работает со сверхзвуковой скоростью, но и шумит точно так же, как и сверхзвуковой истребитель. При этом приходится покупать мощный блок питания на 350 Ватт и ставить специальные фильтры на разветвитель, чтобы подавлять помехи, к которым жесткие диски весьма чувствительны. Но выигрыш в скорости стоит того, особенно, если компьютер используется для занятий видеомонтажом или обработки изображений полиграфического качества. Но с такими потребностями лучше сразу обратится к дискам SCSI. Мы же остановимся на IDE как на самом демократичном и дешевом интерфейсе.

Настройка производительности с помощью утилиты hdparm

Для достижения наивысшей производительности каждый жесткий диск, установленный в систему, должен быть настроен в соответствии со своим предназначением. Стандартные настройки, принимаемые ядром по умолчанию, ориентированы на абстрактного среднестатистического пользователя и редко совпадают с конкретными требованиями. Учет преобладающего типа запросов к дисковой подсистеме значительно повышает быстродействие (в некоторых случаях — чуть ли не на порядок), хотя это оружие работает и в обратном направлении. Бестолковая настройка сваливает производительность в глубокую яму, из которой, впрочем, всегда можно выбраться, восстановив настройки по умолчанию.

Всем этим ведает консольная утилита hdparm (рис. 8.16), входящая в комплект штатной поставки большинства (если не всех) дистрибутивов Linux и требующая для своей работы полномочий администратора (root). Если вдруг этой утилиты в составе вашего дистрибутива не окажется, взять ее можно здесь: http://metalab.unc.edu/pub/Linux/system/hardware/hdparm-3.6.tar.gz. Формат ее вызова следующий:

hdparm опция1 опция2 ... опцияN /dеv/жесткий_диск

Рис. 8.16. Интерактивная оболочка утилиты hdparm

Жестким дискам с интерфейсом IDE обычно присваиваются имена

hda
(первый жесткий диск),
hdb
(второй жесткий диск),
hdc
и т.д. Диски SCSI, соответственно, именуются
sda
,
sdb
,
sdc
, вот только hdparm с ними, увы, не работает. Строго говоря, hdparm настраивает параметры не одного лишь жесткого диска, но и его контроллера и, отчасти, драйвера. Рассмотрим несколько практических примеров.

Ключ

устанавливает количество секторов опережающего чтения (read-ahead), которые будут автоматически прочитаны контроллером в надежде на то, что они все-таки пригодятся пользователю. По умолчанию ядро читает 8 секторов (4 Кбайт). При последовательном чтении больших слабо фрагментированных файлов это значение рекомендуется увеличить в несколько раз, а при хаотичном доступе, работе с мелкими или сильно фрагментированными файлами — уменьшить до 1–2 секторов. Ключ
-P
задействует механизм аппаратной предвыборки (hardware prefetching), сообщая приводу, сколько секторов ему необходимо прочитать. Грубо говоря, эта опция производит почти тот же самый эффект, что и
, только намного круче. Тем не менее, следует иметь в виду, что не все приводы поддерживают аппаратную предвыборку.

Ключ

-m
указывает количество секторов, обрабатываемых приводом за одну операцию обмена (так называемый multiple sector I/O или block mode). В зависимости от конструктивных особенностей жесткого диска он может обрабатывать от 2 до 64 (и больше) секторов за операцию. Конкретное значение можно узнать с помощью ключа
-i
(оно находится в графе
MaxMultSect
). В целом, скорость обработки данных прямо пропорциональна количеству секторов, однако некоторые приводы (например, WD Caviar) при больших значениях
-m
начинают жутко тормозить. Выяснить практическое положение дел помогает ключ
-t
, измеряющий пропускную способность дисковой подсистемы в режиме чтения.

Внимание!

Запредельные значения

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

Ключ

-M
отвечает за настройку шумовых характеристик накопителя (Automatic Acoustic Management, AAM). Значение 128 соответствует наиболее тихому режиму, 254 — наиболее быстрому. Промежуточные значения в общем случае не определены (некоторые накопители их поддерживают, некоторые нет). Следует сказать, что значение 128 не только уменьшает шум, но и способствует меньшому износу накопителя, однако падение производительности может быть очень и очень значительным, поэтому трудно посоветовать, какое именно значение следует выбрать.

Ключ

-c
управляет режимом передачи данных. Параметр 0 — 16-битная передача, 1 — 32-битная передача, 3 — 32-битная передача со специальным синхросигналом. По умолчанию ядро использует параметр 3 (возможно, не для всех ядер), как наиболее надежный, но и менее производительный, чем 1. Большинство современных чипсетов вполне нормально работают с параметром 1, так что излишняя осторожность тут ни к чему.

Ключ

-d1
активирует, a -d0 дезактивирует режим DMA, значительно повышающий производительность и радикально снижающий нагрузку на процессор. Однако на практике так бывает далеко не всегда. Устройства IDE, висящие на одной шине, могут конфликтовать между собой, и тогда хотя бы одно из них должно быть принудительно переведено в режим PIO. Выяснить, как обстоят дела в каждом конкретном случае, помогает ключ
-T
, измеряющий скорость передачи данных. Ключ
-d1
обычно используется совместно с ключом
-Xnnn
, форсирующим конкретный режим PIO или DMA. Режиму PIO
n
соответствует значение (
n + 8
), т.е.
-X9
задает PIO1, a
-X12
— PIO4. Режиму DMAn соответствует значение (
n+32
), например,
-X34
для DMA2, a Ultra DMA — (
n+64
), например,
-Х69
для UDMA5, который обеспечивает наивысшую производительность, но поддерживается не всеми жесткими дисками и чипсетами. Узнать список поддерживаемых режимов можно с помощью ключа
-i
. По умолчанию ядро выбирает не слишком агрессивные режимы передачи данных, оставляя солидный запас производительности за спиной.

Внимание!

Переход на высшие режимы UDMA чреват разрушением всего дискового тома, поэтому обязательно зарезервируйте его содержимое перед началом экспериментов!

Для сохранения установок необходимо дать команду

hdparm -k 1 /dev/hdx
, в противном случае они будут утеряны при первом же сбросе контроллера IDE или перезапуске машины.

Выбор файловой системы

Существует два типа файловых систем — журналируемые (journaling) и нет. К первым относятся ext3fs, ReiserFS, XFS, а последним — minix, ext2fs и UFS. Журналирумые файловые системы намного легче переносят зависание системы и отключение питания во время интенсивных дисковых операций, автоматически возвращая файловую систему в стабильное состояние. Однако от других типов разрушений (отказ контроллера, дефекты поверхности, вирусное нашествие) журналирование уже никак не спасает, а вот производительность падает изрядно.

Для домашних компьютеров и большинства рабочих станций журналирование не нужно, и надежности файловой системы ext2fs вполне достаточно, особенно если компьютер оборудован источником бесперебойного питания. В ответственных случаях используйте ext3fs или ReiserFS. По тестам ReiserFS в среднем вдвое, а на операциях записи — в 35 раз быстрее, чем ext3fs, что особенно хорошо заметно на мелких файлах. В реальности же часто все бывает наоборот. Высокая латентность ReiserFS (промежуток между подачей запроса и получением ответа) вкупе с агрессивной загрузкой процессора приводят к заметному отставанию от ext3fs, что особенно хорошо заметно на мелких файлах (да-да, на тех самых, на которых нам обещали выигрыш!). Подробнее об этом можно прочитать здесь: http://kerneltrap.org/node/view/3466.

Журналирование можно значительно ускорить, если разместить журнал на отдельном носителе. Такой журнал называется внешним (external). Подключить его можно командной строкой следующего вида:

tune2fs -J device=external_journal
(где
external_journal
— имя раздела соответствующего устройства), причем внешний журнал должен быть предварительно создан командой
mke2fs -О journal_dev external_journal
. Команда
tune2fs -J size=journal_size
управляет размером журнала. Чем меньше размер журнала, тем ниже производительность. Предельно допустимый размер составляет 102 400 блоков или ~25 Мбайт (точное значение зависит от размера блока, о котором мы еще поговорим).

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

mount /dev/hdx /data -о data=writeback
. Естественно, надежность файловой системы при этом снижается. При желании можно журналировать все данные (команда
mount /dev/hdx /data -о data= journal
), после чего никакие зависания или отказы питания нам будут не страшны, правда о производительности придется забыть.

При создании новой файловой системы важно выбрать правильный размер блока (в терминологии MS-DOS/Windows — кластера). На ext2fs и ext3fs это осуществляется командой

mke2fs -b block-size
, на XFS —
mkfs.xfs -b size=block-size
и
newfs -b block-size
— на UFS. Чем больше блок, тем ниже фрагментация, но и выше дисковые потери за счет грануляции дискового пространства. Некоторые файловые системы (например, UFS) поддерживают фрагменты (fragments) — порции данных внутри блоков, позволяющие задействовать свободное пространство в "хвостах" блоков, благодаря чему использование блоков большого размера уже не приводит ни к каким потерям. Файловая система ReiserFS, в отличие от остальных, не нарезает диск на ломтики фиксированного размера, а динамически выделяет требуемый блок данных, забивая диск файлами под завязку. В среднем это на 6% увеличивает доступный объем, однако приводит к чрезмерной фрагментации, "съедающей" всю производительность. Рекомендуется использовать максимально доступный размер блока (4 Кбайт для ext2fs и ext3fs, 16 Кбайт для UFS и 64 Кбайт для XFS, файловые системы ReiserFS и JFS не поддерживают этой опции) и задействовать максимальное количество фрагментов на блок (в UFS — 8).

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

mke2fs -о dir_index
, а в ReiserFS —
mkreiserfs -h HASH
, где
HASH
— один из следующих типов хэш-таблицы:
r5
,
rupasov
или
tea
. По умолчанию выбирается тип
r5
, который наилучшим образом подходит для большинства файловых операций. Тем не менее, некоторые приложения (например, Squid Web Proxy-сервер) настоятельно рекомендуют использовать
rupasov
-хэш, в противном случае за быстродействие никто не ручается. С другой стороны,
r5
и
rupasov
очень медленно работают с каталогами, содержащими по несколько миллионов файлов, и здесь лучше подходит
tea
, а на каталогах из нескольких десятков файлов все три алгоритма хеширования проигрывают стандартному нехешируемому
plain
-алгоритму. К сожалению, опция хеширования носит глобальный характер — нельзя одни каталоги хешировать, а другие — нет.

Файловая система XFS — единственная из всех, которая позволяет задавать размер inode вручную. Обычно в inode хранятся служебные данные файла (атрибуты, порядок размещения блоков на диске), но если файл целиком помещается в inode, то система сохраняет его именно там! Дополнительное дисковое пространство уже не выделяется, что избавляет головку винчестера от лишних перемещений, в результате чего время доступа к файлу существенно сокращается. Точно так же поступают ReiserFS, NTFS и некоторые другие файловые системы, однако размер inode они менять не в состоянии, а жаль! Если мы планируем работать с большим количеством мелких файлов, размер inode желательно увеличить, что положительно скажется как на производительности, так и на доступном дисковом пространстве. При работе с большими файлами размер inode лучше, наоборот, сократить, в противном случае потери дискового пространства будут довольно значительными. Выбор предпочтительного размера inode осуществляется командой следующего вида:

mkfs.xfs -i size=value
. Минимальный размер составляет 512 байт, максимальный — 2048.

Внимание!

Windows предоставляет минимум рычагов управления для настройки дисковой подсистемы, и угробить свои данные под ее управлением довольно затруднительно. Linux же позволяет "крутить" и настраивать вся и все! Как следствие — малейшая оплошность приводит к катастрофическим разрушениям. И винить в этом некого — нечего было браться за штурвал, не выучив руководство, как правило, написанное на английском языке. Но даже хорошо написанное руководство не поможет определить, какие именно режимы поддерживаются вашим оборудованием, а какие — нет. Вполне может оказаться и так, что у вас кабель перекручен или разъем барахлит, а на высокосортных режимах это сразу же скажется! Настройка дисковой подсистемы на максимальную производительность — это огромный риск! Никогда не экспериментируйте, не зарезервировав всех данных!

Фрагментация

В процессе работы с диском его фрагментация неизбежно увеличивается. Больше всего от этого страдают ext2fs/ext3fs и ReiserFS. На UFS и XFS за счет поддержи блоков большого размера падение производительности уже не так заметно. Утверждение, что файловые системы Linux якобы не подвержены фрагментации — нелепый миф, который легко опровергнет любой опытный пользователь.

При последовательной записи на диск нескольких файлов система их размещает один за другим, так что первый файл "упирается" во второй. Свободного места для дальнейшего роста уже нет (короткий "хвост" в конце блока не считается), и система вынуждена выделять блоки где-то за концом следующего файла. Если же их там нет, свободные блоки ищутся в начале диска. В результате этого файл оказывается "размазанным" по поверхности диска. Рассмотрим еще один сценарий. Представьте себе, что вы записали пять файлов по 100 блоков каждый, а затем удалили первый, третий и пятый файлы. Таким образом, вы освободите 300 блоков в трех фрагментах. При записи 300-блочного файла система сначала попытается отыскать непрерывный участок свободного пространства подходящего размера, но если его не окажется, будет вынуждена "размазывать" файл по поверхности. Чтобы исправить ситуацию, необходимо собрать все свободные блоки, объединив их в один непрерывный фрагмент, т.е. дефрагментировать раздел.

С моей личной точки зрения, из бесплатных дефрагментаторов лучшим является стандартный defrag, входящий в штатный комплект поставки большинства дистрибутивов Linux. Если же в вашем дистрибутиве его нет, исходные тексты дефргаментатора можно скачать по следующему адресу: ftp://metalab.une.edu/pub/Linux/system/filesystems/defrag-0.70.tar.gz.

Фирма OO-Software, наряду с одноименным дефрагментатором для Windows NT, выпустила замечательный консольный дефрагмантатор для Linux, в настоящее время находящийся в стадии бета-тестирования и распространяющийся на бесплатной основе. Так что качайте его, пока дают, а скачать его можно отсюда: http://www.oo-software.com/cgi-bin/download-e.pl?product=OODLXBIN.

Регулярная дефрагментация — это хороший способ противостоять растущему падению производительности файловой системы.

Обновлять или не обновлять

Некоторые приложения, в частности, уже упомянутый Squid Web Proxy-сервер, требуют особой настройки файловой системы. Для увеличения быстродействия рекомендуется отключить обновления времени последнего доступа к файлу с помощью команды

mount -о noatim
e. Наибольший прирост производительности наблюдается на UFS, которая, в отличие от подавляющего большинства остальных файловых систем, не откладывает обновление inode в долгий ящик (lazy write), а делает это сразу же после его изменения (write through). На ext3fs в силу ее журналирующей природы, обновление
atime
вносит столь незначительный вклад в общее быстродействие, что никакой разницы просто нет.

Проблема "хвостов"

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

mount -о notail
отключает "паковку" хвостов и коротких файлов. Повторное монтирование с настройками по умолчанию вновь активизирует эту опцию, но при этому уже "упакованные"/"распакованные" хвосты останутся на своем месте вплоть до модификации "своего" файла.

Рис. 8.17. Производительность файловой системы ReiserFS на операциях записи в зависимости от объема свободного пространства (паковка хвостов включена)

Рис. 8.18. Производительность файловой системы ReiserFS на операциях записи в зависимости от объема свободного пространства (паковка хвостов выключена)

Внимание!

Помните, что

mke2fs
— это деструктивная команда, разрушающая всю файловую систему целиком! Грубо говоря, это format.com под Linux.

Полезные ссылки

"The Software-RAID HOWTO" — руководство по созданию программных RAID'ов под Linux (на английском языке): http://www.tldp.org/HOWTO/Software-RAID-HOWTO.html.

"Тонкая настройка IDE дисков в Linux с помощью hdparm" — отличная статья на русском языке. Доступна здесь: http://www.opennet.ru/base/sys/htparm_tune.txt.html.

"JFS for Linux" — домашняя страничка проекта JFS. Содержит исходные тексты, документацию, технологию и т.д. (на английском языке): http://jfs.sourceforge.net/.

"ReiserFS" — домашняя страничка проекта ReiserFS (на английском языке): http://www.namesys.com.

"Работа с дисками и файловыми системами в FreeBSD" — отличный faq на русском языке: http://www3.opennet.ru/base/sys/freebsd_fs_mount.txt.html.

"Understanding Filesystem Performance for Data Mining Applications" — сравнение производительности различных файловых систем под Linux с советами по их "тонкой" настройке (на английском языке): http://www.cs.rpi.edu/~szymansk/papers/hpdm03.pdf.

"Linux Filesystem Performance Comparison for OLTP" — еще одна статья по сравнению производительности файловых систем под Linux (на английском языке): http://otn.oracle.com/tech/linux/pdf/Linux-FS-Performance-Comparison.pdf.

"Journaling file systems" — журналируемые файловые системы и все, что с ними связано (на английском языке): http://awlinux1.alphaworks.ibm.com/developerworks/linux390/perf/tuning_res_journaling.shtml.

"Linux: Low Latency and Filesystems" — обсуждение преимуществ и недостатков ReiserFS (на английском языке): http://kerneltrap.org/node/view/3466.

"Ext3 or Reiserfs? Hans reiser says red hat's move is understandable" — еще одно сравнение ext3fs и ReiserFS (на английском языке) http://www.linuxplanet.com/linuxplanet/reports/3726/1/.

"Optimizing Linux filesystems" — отличная статья про оптимизацию файловых систем под Linux (на английском языке): http://www.newsforge.com/article.pl?sid=03/10/07/1943256.

"Journaling-Filesystem Fragmentation Project" — исследовательская работа по фрагментации файловых систем и ее влиянию на производительность (на английском языке): http://www.informatik.uni-frankfurt.de/~loizides/reiserfs/agesystem.html.

"HDD REPAIR FORUMS" — форум по тестированию жестких дисков и восстановлению данных (на русском языке): http://mhddsoftware.com/forum/.

"Filesystem defragmenter for Linux filesystems" — исходные тексты стандартного дефрагментатора: ftp://metalab.unc.edu/pub/Linux/system/filesystems/defrag-0.70.tar.gz.

"О&О Defrag Linux BETA - 1.0.4761" — бета-версия хорошего коммерческого дефрагментатора: http://www.oo-software.com/cgi-bin/download/download-e.pl?product=OODLXBIN.

Часть III