Skip to content

Latest commit

 

History

History
71 lines (46 loc) · 11.7 KB

lab10files.textile

File metadata and controls

71 lines (46 loc) · 11.7 KB

Лабораторная работа №9. Работа с директориями в ОС GNU/Linux

Краткие теоретические сведения

Получение информации о содержимом текущего каталога

Типичная файловая система в 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;
  • информация записывается в новый файл, в конце имени которого содержится знак тильда: ~; любые промежуточные и дополнительные файлы отсутствуют;
  • каждая файловая операция контролируется на ошибку с выдачей на терминал соответствующего сообщения, типа «Ошибка открытия файла», «Ошибка записи» и т.д.;
  • применить буферизацию с большими (десятки килобайт) буферами.

Примерная последовательность действий может быть следующей:

  1. получить системным вызовом open файловый дескриптор данной директории;
  2. получить список файлов, находящихся в директории с помощью системного вызова getdents (с номером 141 в 32-битной системе);
  3. Из структурной переменной типа dirent получить имя файла; открыть его для чтения, используя системный вызов open (с номером 5 в 32-битной системе). Эта операция проверяет правильность имени файла и его наличие на диске. Если файл отсутствует, то операция возвращает в регистре eax отрицательное значение. Не забывайте проверять это;
  4. определить длину файла. Для определения длины файла можно использовать, например, способ, описанный в работе 8 (с помощью системного вызова lseek получить значение позиции в файле, соответствующей смещению от конца файла);
  5. создать новый файл с символом ~ в конце имени;
  6. читать из исходного файла блок размером 60 Kb в буфер;
  7. выполнить побайтный «переворот» буфера;
  8. записать 60 Kb из буфера в новый файл (с текущей позиции, т. е. без перемещения файлового указателя);
  9. проверить, прочитан ли весь исходный файл, и если нет то продолжить с пункта 6, иначе приступить к перевороту следующего файла в директории и вернуться к пункту 3.