Язык программирования Си. Издание 3-е, исправленное — страница 49 из 69

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

Для начала вспомним структуру файловой системы в UNIXe. Каталог - это файл, содержащий список имен файлов и некоторую информацию о том, где они расположены. "Место расположения" - это индекс, обеспечивающий доступ в другую таблицу, называемую "списком узлов inode". Для каждого файла имеется свой inode, где собрана вся информация о файле, за исключением его имени. Каждый элемент каталога состоит из двух частей: из имени файла и номера узла inode.

К сожалению, формат и точное содержимое каталога не одинаковы в разных версиях системы. Поэтому, чтобы переносимую компоненту отделить от непереносимой, разобьем нашу задачу на две. Внешний уровень определяет структуру, названную Dirent, и три подпрограммы opendir, readdir и closedir: в результате обеспечивается системно-независимый доступ к имени и номеру узла inode каждого элемента каталога. Мы будем писать программу fsize, рассчитывая на такой интерфейс, а затем покажем, как реализовать указанные функции для систем, использующих ту же структуру каталога, что и Version 7 и System V UNIX. Другие варианты оставим для упражнений.

Структура Dirent содержит номер узла inode и имя. Максимальная длина имени файла равна NAME_MAX - это значение системно-зависимо. Функция opendir возвращает указатель на структуру, названную DIR (по аналогии с FILE), которая используется функциями readdir и closedir. Эта информация сосредоточена в заголовочном файле dirent.h.

#define NAME_MAX 14 /* максимальная длина имени файла */ 

 /* системно-зависимая величина */ 


typedef struct { /* универс. структура элемента каталога: */

 long ino; /* номер inode */

 char name[NAME_MAX+1]; /* имя + завершающий '\0' */

} Dirent;


typedef struct { /* минимальный DIR: без буферизации и т.д. */

 int fd; /* файловый дескриптор каталога */

 Dirent d; /* элемент каталога */

} DIR; 


DIR *opendir(char *dirname); 

Dirent *readdir(DIR *dfd); 

void closedir(DIR *dfd);

Системный вызов stat получает имя файла и возвращает полную о нем информацию, содержащуюся в узле inode, или -1 в случае ошибки. Так,

char *name; 

struct stat stbuf; 

int stat(char *, struct stat *); 


stat(name, &stbuf);

заполняет структуру stbuf информацией из узла inode о файле с именем name. Структура, описывающая возвращаемое функцией stat значение находится в ‹sys/stat.h› и выглядит примерно так:

struct stat /* информация из inode, возвращаемая stat */ 

{

 dev_t st_dev; /* устройство */

 ino_t st_ino; /* номер inode */

 short st_mode; /* режимные биты */

 short st_nlink; /* число связей с файлом */

 short st_uid; /* имя пользователя-собственника */

 short st_gid; /* имя группы собственника */

 dev_t st_rdev; /* для специальных файлов */

 off_t st_size; /* размер файла в символах */

 time_t st_atime; /* время последнего использования */

 time_t st_mtime; /* время последней модификации */

 time_t st_ctime; /* время последнего изменения inode */

};

Большинство этих значений объясняется в комментариях. Типы, подобные dev_t и ino_t, определены в файле ‹sys/types.h›, который тоже нужно включить посредством #include.

Элемент st_mode содержит набор флажков, составляющих дополнительную информацию о файле. Определения флажков также содержатся в ‹sys/stat.h› нам потребуется только та его часть, которая имеет дело с типом файла

#define S_IFMT  0160000 /* тип файла */

#define S_IFDIR 0040000 /* каталог */ 

#define S_IFCHR 0020000 /* символьно-ориентированный */

#define S_IFBLK 0060000 /* блочно-ориентированный */

#define S_IFREG 0100000 /* обычный */

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

Программа main просматривает параметры командной строки, передавая каждый аргумент функции fsize.

#include ‹stdio.h› 

#include ‹string.h› 

#include "syscalls.h" 

#include ‹fcntl.h› /* флажки чтения и записи */ 

#include ‹sys/types.h› /* определения типов */ 

#include ‹sys/stat.h› /* структура, возвращаемая stat */ 

#include "dirent.h" 


void fsize(char *); 


/* печатает размер файлов */ 

main(int argc, char **argv) {

 if (argc == 1) /* по умолчанию берется текущий каталог */

  fsize(".");

 else

  while (--argc › 0)

   fsize(*++argv);

 return 0;

}

Функция fsize печатает размер файла. Однако, если файл - каталог, она сначала вызывает dirwalk, чтобы обработать все его файлы. Обратите внимание на то, как используются имена флажков S_IFMT и S_IFDIR из ‹sys/stat.h› при проверке, является ли файл каталогом. Здесь нужны скобки, поскольку приоритет оператора & ниже приоритета оператора ==.

int stat(char *, struct stat *); 

void dirwalk(char *, void (*fcn)(char *)); 


/* fsize: печатает размер файла "name" */ 

void fsize(char *name) 

{

 struct stat stbuf;


 if (stat(name, &stbuf) == -1) {

  fprintf(stderr, "fsize: нет доступа к %s\n", name);

  return;

 }

 if ((stbuf.st_mode & S_IFMT) == S_IFDIR)

  dirwalk(name, fsize);

 printf("%8ld%s\n", stbuf.st_size, name);

}

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

#define MAX_PATH 1024 


/* dirwalk: применяет fcn ко всем файлам из dir */ 

void dirwalk(char *dir, void (*fcn)(char *)) 

{

 char name[MAX_PATH];

 Dirent *dp;

 DIR *dfd;


 if ((dfd = opendir(dir)) == NULL) {

  fprintf(stderr, "dirwalk: не могу открыть %s\n", dir);

  return;

 }

 while ((dp = readdir(dfd)) != NULL) {

  if (strcmp(dp-›name, ".") == 0 || strcmp(dp-›name, "…") == 0)

   continue; /* пропустить себя и родителя */

  if (strlen(dir)+strlen(dp-›name) + 2 › sizeof(name))