Книга: UNIX — универсальная среда программирования

7.3 Файловая система: индексные дескрипторы

7.3 Файловая система: индексные дескрипторы

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

Для начала разберемся в самом индексном дескрипторе. Часть индексного дескриптора описывается структурой stat, определенной в <sys/stat.h>:

struct stat /* структура, возвращаемая stat */
{
 dev_t st_dev;    /* устройство, содержащее файл */
 ino_t st_ino;    /* индекс */
 short st_mod;    /* биты режима */
 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; /* время последнего изменения
                     индексного дескриптора или файла */
}

Большинство полей поясняются примечаниями. Типы вроде dev_t и ino_t определены в <sys/types.h>, как отмечено выше. Поле st_mode содержит множество флагов, описывающих файл; для удобства определения флагов также являются частью файла <sys/stat.h>:

#define S_IFMT   0170000  /* тип файла */
#define  S_IFDIR 0040000  /* каталог */
#define  S_IFCHR 0020000  /* байт-ориентированный */
#define  S_IFBLK 0060000  /* блок-ориентированный */
#define  S_IFREG 0100000  /* регулярный */
#define S_SUID   0004000  /* установка идентификатора пользователя при
                             выполнении */
#define S_ISGID  0002000  /* установка идентификатора группы
                             при выполнении */
#define S_ISVTX  0001000  /* сохранить выгруженный текст даже после
                             использования */
#define S_IREAD  0000400  /* разрешение читать, владелец */
#define S_IWRITE 0000200  /* разрешение писать, владелец */
#define S_IEXEC  0000100  /* разрешение на выполнение/поиск, владелец */

Индексный дескриптор для файла доступен двум системным вызовам stat и fstat. При вызове stat параметром является имя файла, а результатом — информация из индексного дескриптора для этого файла (или — 1 при наличии ошибки). Fstat выполняет те же функции в отношении дескриптора открытого файла (не в отношении указателя на FILE). Иными словами,

char *name;
int fd;
struct stat stbuf;
stat(name, &stbuf);
fstat(fd, &stbuf);

заполняет структуру stbuf информацией из индексного дескриптора для имени файла или дескриптора файла fd.

Зная все это, мы можем приступить к написанию некоторой полезной программы. Начнем с версии checkmail — программы на Си, которая следит за содержимым вашего почтового ящика. Если файл увеличивается, checkmail выдает сообщение: "У вас есть корреспонденция" и включает звонок. (При уменьшении файла, видимо, из-за того, что вы успели прочитать и сбросить некоторую почтовую корреспонденцию, сообщение не требуется.) Для первого шага вы сделали вполне достаточно, а когда ваша программа заработает, вы станете знатоком.

/* checkmail: watch user's mailbox */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
char *progname;
char *maildir = "/usr/spool/mail"; /* system dependent */
main(argc, argv)
 int argc;
 char *argv[];
{
 struct stat buf;
 char *name, *getlogin();
 int lastsize = 0;
 progname = argv[0];
 if ((name = getlogin()) == NULL)
  error("can't get login name", (char*)0);
 if (chdir(maildir) == -1)
  error("can't cd to %s", maildir);
 for (;;) {
  if (stat(name, &buf) == -1) /* no mailbox */
  buf.st_size = 0;
  if (buf.st_size > lastsize)
  fprintf(stderr, "nYou have mail07n");
  lastsize = buf.st_size;
  sleep(60);
 }
}

Функция getlogin(3) возвращает ваше регистрационное имя или NULL, если это невозможно, checkmail переходит к почтовому каталогу с помощью системного вызова chdir, так что последующие вызовы stat не должны будут "добираться" до почтового каталога через все каталоги, начиная от корневого. Возможно, вы должны адаптировать maildir для своей системы. Мы написали checkmail так, чтобы она работала, даже если нет почтового ящика, поскольку большинство версий mail убирают почтовый ящик в том случае, когда он пуст.

Мы приводили эту программу в гл. 5 для иллюстрации циклов shell. Всякий раз при проверке почтового ящика она создает несколько процессов и загружает систему больше, чем хотелось бы. Версия на Си — единственный процесс, который выполняет stat для файла каждую минуту. Сколько времени требуется на то, чтобы checkmail постоянно выполнялась как фоновая задача? Как показали наши измерения, это время составляет меньше секунды в час, так что им вполне можно пренебречь.

sv: иллюстрация обработки ошибок

Следующей мы собираемся написать похожую на cp программу sv, которая будет копировать множество файлов в каталог, заменяя каждый файл лишь в том случае, если его нет в каталоге или он "старше" копируемого с тем же именем (имя sv означает "сохранять"). Суть действия программы состоит в том, что она не переписывает новую версию файла, sv использует больше информации из индексного дескриптора, чем checkmail. Вызов sv будет иметь такую конструкцию:

$ sv file1 file2 ... dir

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

/* sv: save new files */
#include <stdio.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <sys/stat.h>
char *progname;
main(argc, argv)
 int argc;
 char *argv[];
{
 int i;
 struct stat stbuf;
 char *dir = argv[argc-1];
 progname = argv[0];
 if (argc <= 2)
  error("Usage: %s files... dir", progname);
 if (stat(dir, &stbuf) == -1)
  error("can't access directory %s", dir);
 if ((stbuf.st_mode & S_IFMT) != S_IFDIR)
  error("%s is not a directory", dir);
 for (i = 1; i < argc-1; i++)
  sv(argv[i], dir);
 exit(0);
}

Значения времени, хранящиеся в индексных дескрипторах, исчисляются в секундах (за начало отсчета принято время 0:00 по Гринвичу, 1 января 1970 г.), так что более старые файлы имеют меньшие значения в поле st_mtime.

sv(file, dir) /* save file in dir */
 char *file, *dir;
{
 struct stat sti, sto;
 int fin, fout, n;
 char target[BUFSIZ], buf[BUFSIZ], *index();
 sprintf(target, "%s/%s", dir, file);
 if (index(file, '/') != NULL) /* strchr() in some systems */
  error("won't handle /'s in %s", file);
 if (stat(file, &sti) == -1)
  error("can't stat %s", file);
 if (stat(target, &sto) == -1) /* target not present */
  sto.st_mtime = 0; /* so make it look old */
 if (sti.st_mtime < sto.st_mtime) /* target is newer */
  fprintf(stderr, "%s: %s not copiedn", progname, file);
 else if ((fin = open(file, 0)) == -1)
  error("can't open file %s", file);
 else if ((fout = creat(target, sti.st_mode)) == -1)
  error("can't create %s", target);
 else
  while ((n = read(fin, buf, sizeof buf)) > 0)
   if (write(fout, buf, n) != n)
    error("error writing %s", target);
 close(fin);
 close(fout);
}

Мы заменили стандартные функции ввода-вывода функцией creat, так что sv может сохранять режим работы входного файла. (Заметьте, что index и strchr — разные имена одной и той же процедуры; посмотрите в справочном руководстве по string(3), какое имя использует ваша система.)

Хотя программа sv довольно специфична, в ней отражены некоторые важные идеи. Многие программы не являются системными, но тем не менее используют информацию, поддерживаемую операционной системой и доступную через системные вызовы. Для таких программ существенно, что представление информации хранится только в стандартных файлах макроопределений типа <stat.h> и <dir.h> и что в программы включены эти файлы вместо действительных описаний. Подобные программы с большей степенью вероятности переносимы с одной системы на другую.

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

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

Дело в том, что контроль ошибок весьма утомителен, но тем не менее важен. Из-за ограниченного объема книги и обширности излагаемого в ней материала мы не уделяли должного внимания этому вопросу. Но в настоящих, "производственных" программах не следует позволять себе игнорировать ошибки.

Упражнение 7.10

Модифицируйте checkmail так, чтобы идентифицировать посылающего сообщение: "У вас есть почта". Подсказка: sscanf, lseek.

Упражнение 7.11

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

Упражнение 7.12

Напишите программу watchfile, которая управляет файлом и печатает его с начала всякий раз, как он изменится. Когда бы вы ее использовали?

Упражнение 7.13

Программа sv очень "прямолинейна" при обработке ошибок. Модифицируйте ее так, чтобы она продолжала выполняться, даже если не удается обработать некоторый файл.

Упражнение 7.14

Сделайте sv рекурсивной: если один из исходных файлов — каталог, то этот каталог и все его файлы обрабатываются таким же образом. Сделайте рекурсивной cp. Подумайте, следует ли cp и sv объединить в одну программу, чтобы cp -v не создавала копию, если целевой файл новее файла-источника.

Упражнение 7.15

Напишите программу random.

$ random filename

должна выдавать одну строку, произвольно выбранную из файла. Если есть файл people, содержащий имена, random можно использовать в программе scapegoat ("козел отпущения"), полезной при случайном определении виновных:

$ cat scapegoat
echo "В этом виноват `random people`!"
$ scapegoat
В этом виноват Кен!
$

Убедитесь в том, что random хороша независимо от распределения длины строк.

Упражнение 7.16

Помимо прочего в индексном дескрипторе указаны адреса размещения блоков файла на диске. Рассмотрите файл <sys/into.h>, а затем напишите программу icat, которая должна читать файлы, описываемые номером записи каталога и устройством диска. (Она, конечно, будет работать только в том случае, если требуемый диск открыт на чтение.) При каких обстоятельствах icat полезна?

Оглавление книги


Генерация: 1.422. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз