$ ch05-devnum /dev/null /* Символьное устройство */
char: major: 1, minor: 3
$ ch05-devnum /dev/hda2 /* Блочное устройство */
block: major: 3, minor: 2
К счастью, вывод согласуется с выводом ls
, давая нам уверенность[59], что мы в самом деле написали правильный код.
Воспроизведение вывода ls замечательно и хорошо, но действительно ли это полезно? Ответ — да. Любое приложение, работающее с иерархиями файлов, должно быть способно различать различные типы файлов. Подумайте об архиваторе, таком как tar
или cpio
. Было бы пагубно, если бы такая программа рассматривала файл дискового устройства как обычный файл, пытаясь прочесть его и сохранить его содержимое в архиве! Или подумайте о find
, которая может выполнять произвольные действия, основываясь на типе и других атрибутах файлов, с которыми она сталкивается, (find
является сложной программой; посмотрите
5.4.4.2. Возвращаясь к V7 cat
В разделе 4.4.4 «Пример: Unix cat» мы обещали вернуться к программе V7 cat
, чтобы посмотреть, как она использует системный вызов stat()
. Первая группа строк, использовавшая ее, была такой:
31 fstat(fileno(stdout), &statb);
32 statb.st_mode &= S_IFMT;
33 if (statb.st_mode != S_IFCHR && statb.st_mode != S_IFBLK) {
34 dev = statb.st_dev;
35 ino = statb.st_ino;
36 }
Этот код теперь должен иметь смысл. В строке 31 вызывается fstat()
для стандартного вывода, чтобы заполнить структуру statb
. Строка 32 отбрасывает всю информацию в statb.st_mode
за исключением типа файла, используя логическое AND с маской S_IFMT
. Строка 33 проверяет, что используемый для стандартного вывода файл не является файлом устройства. В таком случае программа сохраняет номера устройства и индекса в dev
и ino
. Эти значения затем проверяются для каждого входного файла в строках 50–56.
50 fstat(fileno(fi), &statb);
51 if (statb.st_dev == dev && statb.st_ino == ino) {
52 fprintf(stderr, "cat: input %s is output\n",
53 ffig ? "-" : *argv);
54 fclose(fi);
55 continue;
56 }
Если значения st_dev
и st_ino
входного файла совпадают с соответствующими значениями выходного файла, cat
выдает сообщение и продолжает со следующего файла, указанного в командной строке.
Проверка сделана безусловно, хотя dev
и ino
устанавливаются, лишь если вывод не является файлом устройства. Это срабатывает нормально из-за того, как эти переменные объявлены:
int dev, ino = -1;
Поскольку ino
инициализирован значением (-1), ни один действительный номер индекса не будет ему соответствовать[60]. То, что dev
не инициализирован так, является небрежным, но не представляет проблемы, поскольку тест в строке 51 требует, чтобы были равными значения как устройства, так и индекса. (Хороший компилятор выдаст предупреждение, что dev
используется без инициализации: 'gcc -Wall
' сделает это.)
Обратите также внимание, что ни один вызов fstat()
не проверяется на ошибки. Это также небрежность, хотя не такая большая, маловероятно, что fstat()
завершится неудачей с действительным дескриптором файла
Проверка того, что входной файл не равен выходному файлу, осуществляется лишь для файлов, не являющихся устройствами. Это дает возможность использовать cat
для копирования ввода из файлов устройств в самих себя, как в случае с терминалами:
$ tty /* Вывести имя устройства текущего терминала */
/dev/pts/3
$ cat /dev/pts/3 > /dev/pts/3 /* Копировать ввод от клавиатуры на экран */
this is a line of text /* Набираемое в строке */
this is a line of text /* cat это повторяет */
5.4.5. Работа с символическими ссылками
В общем, символические ссылки ведут себя подобно прямым ссылкам; файловые операции, такие, как open()
и stat()
, применяются к указываемому файлу вместо самой символической ссылки. Однако, бывают моменты, когда в самом деле необходимо работать с символической ссылкой вместо файла, на которую она указывает.