Типичная файловая система в GNU/Linux (и в других Unix-подобных ОС) содержит объекты трех типов.
- Блок данных. Это адресуемый на диске фрагмент файла. Зная, где находятся все блоки данных файла и, получив к ним доступ, мы, тем самым, получаем доступ к содержимому файла. Изменяя блоки данных, мы меняем содержимое файла.
- Индексный дескриптор (inode). Этот объект хранит метаданные файла. В нем в том числе содержится информация о правах на файл и месте расположения на диске блоков данных файла. Но в индексном дескрипторе не содержится имени файла.
- Директория или каталог. Директория — это особый вид файла, который содержит сопоставление между именами файлов, которые «хранятся в этой директории», и индексными дескрипторами, соответствующими этим файлам. Конечно, как и у каждого файла, у директории есть свой индексный дескриптор, иначе нельзя было бы узнать, «внутри» какой директории «лежит» данная директория.
Хотя директории, по-существу, сами являются специализированными файлами, формат представления данных в них различается для разных файловых систем. Поэтому для получения списка файлов, находящихся в директории, программе необходим способ, не зависящий от типа файловой системы. Для получения списка файлов в таком универсальном формате существует системный вызов getdents
с номером 141:
int getdents(unsigned int fd, struct dirent *dirp, unsigned int count);
В качестве параметров этому системному вызову передаются:
unsigned int fd
— файловый дескриптор данной директории (полученный, например, системным вызовомopen
);struct dirent *dirp
— адрес буфера в памяти, куда будет записана информация о содержимом текущего каталога в виде следующих друг за другом структур;unsigned int count
— размер буфера, в который должна быть записана информация.
Возвращаемое этим системным вызовом значение — неотрицательное целое число, равное количеству фактически прочитанных байт.
Системный вызов записывает информацию о файле в виде структурной переменной типа dirent
. Эта структурная переменная содержит информацию, не зависящую от типа файловой системы. Структура содержит следующие поля:
struct dirent {
long d_ino; /* номер inode */
off_t d_off; /* смещение следующего элемента dirent в каталоге*/
unsigned short d_reclen; /* длина данного dirent */
char d_name []; /* имя файла в ASCIIZ-формате */
}
d_ino
это inode файла, d_off
— смещение в каталоге в реальной файловой системе (смысл значения d_off
в некоторых файловых системах оказывается не вполне тривиальным, поэтому данное поле не рекомендуется к использованию напрямую в прикладных программах). Массив d_name
содержит строку с именем файла, и имеет разную длину для разных файлов. Поэтому структурные переменные типа dirent
имеют разный размер, и размер данной конкретной структуры хранится в поле d_reclen
. Эта длина определяется как число байт между текущим элементом и следующим, причем следующий элемент всегда будет выравнен по границе значения типа long
.
Хотя в языке ассемблера нет специального типа данных, соответствующего структурам языка Си, к полям структурной переменной можно получить доступ, зная их размер. На языке ассемблера для этого к базовому адресу структурной переменной добавляется смещение, по которому находится нужное поле от начала структурной переменной.
В Си компилятор руководствуется следующими правилами при расположении полей внутри структурной переменной:
- Вся структура должна быть выровнена в памяти так, как выровнен её элемент с наибольшим выравниванием.
- Каждый элемент находится по наименьшему следующему адресу с подходящим выравниванием. Если необходимо, для этого между полями структуры добавляется нужное число байт-заполнителей. Размер структуры должен быть кратен её выравниванию. Если необходимо, для этого в конец структуры включается нужное число байт-заполнителей.
[ht] [pic:l101]
В нашем случае наибольшее по размеру поле — d_ino
. Это число размером 8 байт (для 32-битных систем). В результате, все смещения полей структурной переменной dirent
окажутся кратны 8 байтам.
Примечание: можно воспользоваться альтернативным способом определения смещений полей в структуре. Для этого нужно написать простую программу на Си, выводящую на экран соответствующие размеры, выполнить ее и посмотреть результаты эксперимента. Этот же способ можно использовать, чтобы выяснить размер неизвестного типа данных: например узнать размер типа off_t
можно, выполнив в Си-программе инструкцию printf("%d", sizeof(off_t))
.
В структуре dirent
для нас представляет интерес поле d_name
, содержащее имя файла, и поле d_reclen
(беззнаковое целое размером 2 байта в 32-битной системе). Поскольку значение d_reclen
— это размер текущей структурной переменной, оно равно числу байт между полем текущей структурной переменной и одноименным полем следующей структурной переменной в буфере.
Составить алгоритм программы, выполняющей побайтный «переворот» содержимого всех файлов, лежащих в текущей директории.
- файл длины не более 60 Kb;
- информация записывается в новый файл, в конце имени которого содержится знак тильда:
~
; любые промежуточные и дополнительные файлы отсутствуют; - каждая файловая операция контролируется на ошибку с выдачей на терминал соответствующего сообщения, типа «Ошибка открытия файла», «Ошибка записи» и т.д.;
- применить буферизацию с большими (десятки килобайт) буферами.
Примерная последовательность действий может быть следующей:
- получить системным вызовом
open
файловый дескриптор данной директории; - получить список файлов, находящихся в директории с помощью системного вызова
getdents
(с номером 141 в 32-битной системе); - Из структурной переменной типа
dirent
получить имя файла; открыть его для чтения, используя системный вызовopen
(с номером 5 в 32-битной системе). Эта операция проверяет правильность имени файла и его наличие на диске. Если файл отсутствует, то операция возвращает в регистреeax
отрицательное значение. Не забывайте проверять это; - определить длину файла. Для определения длины файла можно использовать, например, способ, описанный в работе 8 (с помощью системного вызова
lseek
получить значение позиции в файле, соответствующей смещению от конца файла); - создать новый файл с символом
~
в конце имени; - читать из исходного файла блок размером 60 Kb в буфер;
- выполнить побайтный «переворот» буфера;
- записать 60 Kb из буфера в новый файл (с текущей позиции, т. е. без перемещения файлового указателя);
- проверить, прочитан ли весь исходный файл, и если нет то продолжить с пункта 6, иначе приступить к перевороту следующего файла в директории и вернуться к пункту 3.