Іноді буває потрібний відмінний тип взаємодії з файловою системою — здобуття інформації про самий файл, а не про його зміст. Команда переліку змісту каталогу, така як ls Юнікса, є прикладом того — вона виводить назви файлів каталогу, і, за бажанням, іншу інформацію, таку як розміри, дозволи тощо. Аналогічною є команда dir системи MS-DOS. Оскільки каталог в Юніксі, це також файл, ls повинна тільки прочитати його, щоб отримати назви файлів, що там знаходяться. Зате, щоб здобути іншу інформацію, як скажімо розмір, виникає потреба в системному викликові. На деяких операційних системах, навіть для того, щоб отримати назви файлів, необхідно звернутися до системного виклику; у випадку того самого MS-DOS, наприклад. Що нам потрібно, так це надати доступ до такої інформації у відносно системонезалежний спосіб, навіть якщо реалізація буде дуже системозалежною.
Ми проілюструємо дещо з цього шляхом написання програми під назвою fsize. fsize, буде спеціальною формою ls, яка виводитиме розміри всіх файлів, вказаних їй на командному рядкові. Якщо один з аргументів виявиться каталогом, fsize викличе себе рекурсивно стосовно цього каталогу. Коли не вказано жодних аргументів, вона оброблятиме поточний каталог.
Давайте розпочнемо з короткого огляду файлової системи Юнікса. Каталог — це файл, що містить список назв файлів і певну інформацію щодо їхнього розташування. «Розташування», це індекс з іншої таблиці під назвою «список індексних вузлів». Індексний вузол файла містить всю інформацію про файл за виключенням його назви. Записи в каталогах, звичайно, складаються з двох пунктів — назви файла та номера індексного вузла.
На жаль, формат і вміст каталогу не однаковий на різних версіях системи. Тож ми розіб'ємо завдання на дві частини, і намагатимемося ізолювати непортабельні частини. Зовнішній рівень означить структуру під назвою Dirent, і три функції: opendir, readdir і closedir, щоб забезпечити системонезалежним доступом до назви й індексного вузла файла. Ми складемоfsize саме із таким інтерфейсом. Після цього, ми покажемо як втілити таку функцію на системах, що використовують таку саму структуру каталогів, як Version 7 і System V UNIX; інші варіанти залишено як вправа читачеві.
Структура Dirent включає номер індексного вузла та назву файла. Максимальна довжина назви файла дорівнюватиме NAME_MAX, що являється системозалежним значенням. opendirповертає покажчик на структуру із назвою DIR, аналогічну FILE, використовувану readdir іclosedir. Ця інформація зберігатиметься у файлі dirent.h.
#define NAME_MAX 14 /* longest filename component; */
/* system-dependent */
typedef struct { /* portable directory entry */
long ino; /* inode number */
char name[NAME_MAX+1]; /* name + '\0' terminator */
} Dirent;
typedef struct { /* minimal DIR: no buffering, etc. */
int fd; /* file descriptor for the directory */
Dirent d; /* the directory entry */
} DIR;
DIR *opendir(char *dirname);
Dirent *readdir(DIR *dfd);
void closedir(DIR *dfd);
Системний виклик stat візьме назву файла та поверне всю інформацію індексного вузла, пов'язаного з цим файлом, або -1, якщо мала місце помилка. Тобто
char *name;
struct stat stbuf;
int stat(char *, struct stat *);
stat(name, &stbuf);
заповнить структуру stbuf інформацією індексного вузла для вказаної назви файла. Структура з описом значень, повернених stat знаходиться в <sys/stat.h>, і типово виглядає як наступне:
struct stat /* inode information returned by stat */
{
dev_t st_dev; /* device of inode */
ino_t st_ino; /* inode number */
short st_mode; /* mode bits */
short st_nlink; /* number of links to file */
short st_uid; /* owners user id */
short st_gid; /* owners group id */
dev_t st_rdev; /* for special files */
off_t st_size; /* file size in characters */
time_t st_atime; /* time last accessed */
time_t st_mtime; /* time last modified */
time_t st_ctime; /* time originally created */
};
Більшість цих значень пояснено в полі коментарів. Такі типи як dev_t та ino_t визначено в<sys/types.h>, який також потрібно включити.
Пункт st_mode містить набір прапорців з описом файла. Визначення цих прапорців так само знаходиться в <sys/types.h>; нас цікавить тільки частина, що відповідає типові файла:
#define S_IFMT 0160000 /* type of file: */
#define S_IFDIR 0040000 /* directory */
#define S_IFCHR 0020000 /* character special */
#define S_IFBLK 0060000 /* block special */
#define S_IFREG 0010000 /* regular */
/* ... */
Тепер ми готові до написання програми fsize. Якщо режим, отриманий за допомогою stat, вказує на те, що файл не є каталогом, тоді розмір знаходиться тут-таки, і можна вивести безпосередньо. Однак, якщо назва вказує на каталог, тоді нам доведеться обробити цей каталог, один файл за раз; він може, в свою чергу, містити інші каталоги, тож цей процес буде рекурсивним.
Функція 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 виводить розмір файла. Якщо ж файл являється каталогом, fsize спершу викличе dirwalk для обробки всіх файлів, що там знаходяться. Зверніть увагу на те, як використовуються прапорці S_IFMT і S_IFDIR для визначення того, чи являється файл каталогом. Дужки важливі, оскільки пріоритет & є нижчим за ==.
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: can't access %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: can't open %s\n", dir);
return;
}
while ((dp = readdir(dfd)) != NULL) {
if (strcmp(dp->name, ".") == 0
|| strcmp(dp->name, ".."))
continue; /* пропускаємо поточний і батьківський */
if (strlen(dir)+strlen(dp->name)+2 > sizeof(name))
fprintf(stderr, "dirwalk: name %s %s too long\n",
dir, dp->name);
else {
sprintf(name, "%s/%s", dir, dp->name);
(*fcn)(name);
}
}
closedir(dfd);
}
Кожний виклик readdir повертає покажчик на інформацію щодо наступного файла, або NULL, якщо не залишилося файлів. Кожний каталог завжди включає посилання на самого себе «.», і свій батьківський «..»; їх необхідно пропустити, інакше програма зациклиться.
До цього рівня код не залежить від того як сформовано каталоги. Наступним кроком буде представити мінімальні версії opendir, readdir і closedir для певної операційної системи. Наступні функції працюють з Version 7 та System V Юнікс-системами; вони використовують інформацію про каталоги з файла заголовка <sys/dir.h>, який має наступний вигляд:
#ifndef DIRSIZ
#define DIRSIZ 14
#endif
struct direct { /* запис каталогу */
ino_t d_ino; /* номер ідексного вузла */
char d_name[DIRSIZ]; /* довга назва без '\0' */
};
Деякі версії цих систем дозволяють набагато довші назви і складнішу структуру каталогів.
Тип ino_t утворений за допомогою typedef, і вказує індекс зі списку вузлів. На нашій системі це значення відповідає unsigned short, але це не є тією інформацією яку варто на стале включати у вашу програму; воно може відрізнятися на іншій системі, тож typedef краще. Повний набір типів систем ви знайдете в <sys/types.h>.
opendir відкриває каталог, перевіряє чи файл дійсно являється каталогом (цього разу за допомогою системного виклику fstat, подібного до stat, за винятком того, що він застовується до файлових дескрипторів), виділяє пам'ять під структуру каталогу, і занотовує інформацію:
int fstat(int fd, struct stat *);
/* opendir: відкриває каталог дла виклику readdir */
DIR *opendir(char *dirname)
{
int fd;
struct stat stbuf;
DIR *dp;
if ((fd = open(dirname, O_RDONLY, 0)) == -1
|| fstat(fd, &stbuf) == -1
|| (stbuf.st_mode & S_IFMT) != S_IFDIR
|| (dp = (DIR *) malloc(sizeof(DIR))) == NULL)
return NULL;
dp->fd = fd;
return dp;
}
closedir закриває файл каталогу і звільняє місце:
/* closedir: закриває каталог відкритий opendir */
void closedir(DIR * dp)
{
if (dp) {
close(dp->fd);
free(dp);
}
}
Нарешті, readdir користується read, щоб прочитати кожний запис в каталозі. Якщо каталоговий сегмент не використовується у даний момент (тому, що файл видалено), номер індексного вузла довнюватиме нулю, і ця позиція пропускається. У протилежному випаку, номер індексного вузла і назву буде занесено в статичну структуру, і покажчик на ці дані повернуто користувачеві. Кожний новий виклик перезаписує інформацію попереднього.
#include <sys/dir.h> /* локальна структура директорій */
/* readdir: читає записи директорій один за одним */
Dirent *readdir(DIR * dp)
{
struct direct dirbuf; /* локальна структура директорій */
static Dirent d; /* портабельна структура */
while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf))
== sizeof(dirbuf)) {
if (dirbuf.d_ino == 0) /* сегмент не використовується */
continue;
d.ino = dirbuf.d_ino;
strncpy(d.name, dirbuf.d_name, DIRSIZ);
d.name[DIRSIZ] = '\0'; /* забезпечує закінчення */
return &d;
}
return NULL;
}
Незважаючи на те, що програма fsize досить специалізована, вона ілюструє декілька важливих ідей. Спершу, що багато програм не являються «системними програмами»; вони просто користуються інформацією, утримуваної операційною системою. Для таких програм вирішальним є, щоб представлення інформації знаходилося тільки в стандартних заголовках, і щоб програми включали ці файли замість містити власні оголошення. Друге спостереження, що якщо постаратися, то можна створити інтерфейс до системозалежних об'єктів, який сам по собі буде віносно системонезалежним. Хорошим прикладом служать функції стандартної бібліотеки.
Вправа 8-5. Модифікуйте fsize, щоб вона виводила додаткову інформацію, що міститься у записі індексного вузла.