Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 18 (всего у книги 55 страниц)
isatty()
Модель стандартного ввода, стандартного вывода и стандартной ошибки Linux/Unix препятствует специальной трактовке устройств ввода и вывода. Программам обычно не нужно знать или беспокоиться о том, направляется ли их вывод на терминал, в файл, канал, физическое устройство или что-то еще.
Однако иногда бывают моменты, когда программе действительно нужно знать, с какого рода файлом связан файловый дескриптор. Семейство вызовов stat()
часто предоставляет достаточно сведений обычный файл, каталог, устройство и т.д. Хотя иногда даже этого недостаточно, и для интерактивных программ, в частности, вам может потребоваться знать, не представляет ли дескриптор файла tty
.
tty (сокращение для Teletype, одного из ранних производителей компьютерных терминалов) является любым устройством, представляющим терминал, т.е. нечто, что человек мог бы использовать для взаимодействия с компьютером. Это может быть либо аппаратное устройство, такое, как клавиатура и монитор персонального компьютера, или старинный терминал видеодисплея, соединенный с компьютером через последовательный порт или модем, или программный псевдотерминал, такой, который используется в оконных системах и при сетевых регистрациях.
Различить можно с помощью isatty()
.
#include
int isacty(int desc);
Эта функция возвращает 1, если дескриптор файла desc
представляет терминал, в противном случае 0. В соответствии с POSIX isatty()
может установить errno
для указания ошибки; поэтому до вызова isatty()
следует установить errno в 0, а затем проверить ее значение, если был возвращен 0. (Справочная страница GNU/Linux isatty(3) не упоминает об использовании errno
.) Стандарт POSIX также указывает, что просто возврат isatty()
1 не означает, что на другом конце дескриптора файла находится человек!
Одним местом, где используется isatty()
, является современная версия ls
, в которой имена файлов по умолчанию печатаются в столбцы, если терминалом является стандартный вывод, а если нет, они печатаются по одной на строчку.
1. Mastering Algorithms With C by Kyle Loudon. O'Reilly & Associates, Sebastopol, California, USA, 1999. ISBN: 1-56592-453-3.
Эта книга предоставляет практическое, утилитарное введение в алгоритмы и структуры данных с использованием С, освещая среди прочих вещей таблицы хэшей, деревья, сортировку и поиск.
2. The Art of Computer Programming Volume 3. Sorting and Searching, 2nd edition, by Donald E. Knuth Addison-Wesley, Reading Massachusetts, USA, 1998. ISBN: 0-201-89685-0.[71]71
Русский перевод Дональд E. Кнут. Искусство программирования Том 3. Сортировка и поиск (2-е издание). Москва • Санкт-Петербург • Киев, Вильямс, 2000 – Примеч. науч. ред.
[Закрыть]
На эту книгу обычно ссылаются как на последнее слово в сортировке и поиске. Примите во внимание, что она значительно более сжата и труднее для чтения, чем книга Loudon'a.
3. Проект GTK+[72]72
http://www/gtk.org
– Примеч. автора.
[Закрыть] состоит из нескольких совместно работающих библиотек GTK+ является лежащим в основе инструментарием, используемым проектом GNU GNOME.[73]73
http://www.gnome.org
– Примеч. автора.
[Закрыть] В основе иерархии библиотек располагается Glib, библиотека фундаментальных типов, структур данных и функций для работы с ними. Glib включает возможности для всех основных операций, которые мы до сих пор рассмотрели в данной книге, и многое другое, включая связанные списки и хэш-таблицы. Для просмотра онлайн-документов начните с веб-сайта проекта документации GTK+[74]74
http://www/gtk.org/rdp
– Примеч. автора.
[Закрыть], щелкните на ссылке «Загрузить» (Download) и идите дальше по онлайн-версии.
• Время внутренне хранится в виде значений time_t
, представляющих «секунды с начала Эпохи». Эпоха для систем GNU/Linux и Unix начинается с полночи 1 января 1970 г. по UTC. Текущее время получается от системы с помощью системного вызова time()
, а difftime()
возвращает разницу в секундах между двумя значениями time_t
.
• Структура struct tm
представляет «разложенное время», которое является значительно более удобным представлением даты и времени. gmtime()
и localtime()
преобразуют значения time_t
в значения struct tm
, a mktime()
действует в обратном направлении.
• asctime()
и ctime()
осуществляют упрошенное форматирование значений времени, возвращая указатель на static
строку символов фиксированного размера и формата, strftime()
предусматривает гораздо более гибкое форматирование, включая значения на основе местных настроек.
• Сведения о часовом поясе доступны через вызов tzset()
. Поскольку стандартные процедуры действуют так, как если бы они автоматически вызывали tzset()
, необходимость в непосредственном вызове этой функции возникает редко.
• Стандартной процедурой для сортировки массивов является qsort()
. Используя предоставленную пользователем функцию сравнения и принимая параметры числа элементов массива и их размера, qsort()
может сортировать любые виды данных. Это обеспечивает значительную гибкость.
• scandir()
читает в массив struct dirent
каталог целиком. Для выбора того, какие элементы включить в массив и для обеспечения упорядочения элементов в массиве могут использоваться предоставленные пользователем функции alphasort()
является стандартной функцией для сортировки элементов каталога по имени; scandir()
передает функцию сортировки прямо через qsort()
.
• Функция bsearch()
работает подобно qsort()
. Она осуществляет быстрый бинарный поиск. Используйте ее, если цена линейного поиска перевешивает цену сортировки ваших данных. (Дополнительный API для поиска коллекций данных описан в разделе 14.4 «Расширенный поиск с помощью двоичных деревьев».)
• Базы данных пользователей и групп могут храниться в файлах на локальном диске или могут быть доступны через сеть. Стандартный API намеренно скрывает это различие. Каждая база данных обеспечивает как линейный просмотр всей базы данных, так и непосредственные запросы имени или ID пользователя/группы.
• Наконец, для тех случаев, когда недостаточно простого stat()
, isatty()
может вам сообщить, представляет ли открытый файл устройство терминала.
1. Напишите простую версию команды date
, которая принимает в командной строке строку формата и использует ее для форматирования и вывода текущего времени.
2. Когда файл старше шести месяцев, 'ls -l
' использует для печати времени изменения более простой формат. GNU версия файла ls.c
использует следующее вычисление:
3043 /* Время считается недавним, если оно в пределах последних 6
3044 месяцев. В Григорианском годе 365.2425 * 24 * 60 * 60 ==
3045 31556952 секунд в среднем. Запишите это значение как
3046 целую константу для избежания трудностей с плавающей точкой.*/
3047 six_months_ago = current_time – 31556952 / 2;
Сравните это с нашим примером вычисления шести прошлых месяцев. Каковы преимущества и недостатки каждого из методов?
3. Напишите простую версию команды touch
, которая изменяет время модификации файла, имя которого указано в командной строке, на текущее время
4. Добавьте к вашей команде touch
опцию, которая принимает в командной строке значения даты и времени и использует их в качестве нового времени модификации файлов, указанных в командной строке.
5. Добавьте к своей версии touch
еще одну опцию, которая принимает имя файла и использует время модификации данного файла как новое время модификации файла, указанного в командной строке.
6. Усовершенствуйте ch06-sortemp.c
так, чтобы она сортировала отдельный массив указателей, указывающих на массив сотрудников.
7. Добавьте к ch06-sortdir.c
опции для сортировки по номеру индекса, времени модификации, времени доступа и размеру. Добавьте «обратную опцию», так, чтобы основанная на времени сортировка первым помещала самый недавний файл, а по другим критериям (размеру, индексу) помещала вначале наибольшее значение.
8. Напишите простую версию команды chown
. Она должна использоваться так:
chown пользователь[:группа] файлы ...
Здесь пользователь и группа являются именами пользователя и группы, представляющими новых пользователя и группу для указанных файлов. Группа необязательна; если она присутствует, она отделяется от пользователя двоеточием. Чтобы протестировать свою версию на системе GNU/Linux, вы должны зарегистрироваться в качестве root
. Делайте это осторожно!
9. Усовершенствуйте свою chown
, чтобы допустить использование числовых значений пользователя или группы наряду с их именами.
10. Напишите функции для копирования структур пользователя и группы, включая указываемые данные. Для выделения памяти используйте при необходимости malloc()
.
11. Напишите специализированную библиотеку поиска пользователей, которая считывает в динамически выделяемый массив всю базу данных пользователей. Предусмотрите быстрый поиск пользователей как по ID, так и по именам. Гарантируйте обработку случая, при котором запрошенный пользователь не найден.
12. Сделайте то же самое для базы данных групп.
13. Напишите программу stat
, которая печатает содержимое struct stat
для каждого файла, указанного в командной строке. Она должна выводить все значения в формате, удобном для восприятия человеком: значения time_t
в виде дат и времени, значения uid_t
и gid_t
в виде соответствующих имен (если они доступны), а также содержимое символических ссылок. Выведите поле st_mode
таким же образом, как вывела бы ls
.
Сравните свою программу с программой stat
GNU Coreutils как по их выводу, так и по исходному коду.
Глава 7
Соединяя все вместе: ls
Команда V7 ls
хорошо связывает воедино все, что мы до сих пор видели. Она использует почти все API, которые мы рассмотрели, затрагивая многие аспекты программирования Unix: выделение памяти, вспомогательные данные файлов, времена и даты, имена пользователей, чтение каталогов и сортировку.
ls
По сравнению с современными версиями ls
, V7 ls
принимает лишь небольшое количество опций, а значение некоторых из них для V7 отличается от значения для современной ls
. Эти опции следующие:
-а
Выводит все элементы каталога. Без нее '.
' и '..
' не выводятся. Довольно интересно, V7 игнорирует лишь '.
' и '..
', тогда как с V1 по V6 игнорируется любой файл, имя которого начинается с точки. Это последнее является также поведением по умолчанию и для современных версий ls
.
-n
Вместо времени модификации файла использует для -t
или -l
время изменения индекса.
-d
Для аргументов каталогов выводит сведения о самом каталоге, а не о его содержимом.
-f
«Заставляет» читать каждый элемент как каталог и печатать найденное в каждом слоте имя. Эта опция отключает -l
, -r
, -s
, -t
и включает -а
. (Эта опция, очевидно, существует для отладки и исправления файловой системы.)
-g
Для 'ls -l
' использует вместо имени пользователя имя группы.
-i
Выводит в первом столбце номер индекса вместе с именем файла или длинным листингом.
-l
Осуществляет привычный вывод в длинном формате. Обратите, однако, внимание, что V7 'ls -l
' выводила лишь имя владельца, а не имена владельца и группы вместе.
-r
Изменяет порядок сортировки, будь то по алфавиту для имен файлов или по времени.
-s
Выводит размер файла в 512-байтовых блоках. Справочная страница V7 ls(1) утверждает, что вспомогательные блоки (indirect blocks) – блоки, используемые файловой системой для обнаружения блоков больших файлов – также учитываются при вычислении, но, как мы увидим, это утверждение было неверным.
-t
Сортирует вывод вместо имени по времени модификации, сначала более ранние.
-u
С опциями -t
и/или -l
использует время доступа вместо времени модификации. Наибольшие различия между V7 ls
и современной ls
затрагивают опцию -а
и опцию -l
. Современные системы опускают все файлы с точками, если не указана -а
, и они включают в длинный листинг -l
имена и владельца, и группы. На современных системах -g
означает вывод лишь имени группы, а -o
означает вывод лишь имени владельца. Стоит заметить, что у GNU ls
свыше 50 опций!
ls
Файл /usr/src/cmd/ls.c
в дистрибутиве V7 содержит код. Весь он занимает 425 строк.
1 /*
2 * перечисляет файлы или каталоги
3 */
4
5 #include
6 #include
7 #include
8 #include
9
10 #define NFILES 1024
11 FILE *pwdf, *dirf;
12 char stdbuf[BUFSIZ];
13
14 struct lbuf { /* Собирает необходимые сведения */
15 union {
16 char lname[15];
17 char *namep;
18 } ln;
19 char ltype;
20 short lnum;
21 short lflags;
22 short lnl;
23 short luid;
24 short lgid;
25 long lsize;
26 long lmtime;
27 };
28
29 int aflg, dflg, lflg, sflg, tflg, uflg, lflg, fflg, gflg, cflg;
30 int rflg = 1;
31 long year; /* Глобальные переменные: инициализируются 0 */
32 int flags;
33 int lastuid = -1;
34 char tbuf[16];
35 long tblocks;
36 int statreq;
37 struct lbuf *flist[NFILES];
38 struct lbuf **lastp = flist;
39 struct lbuf **firstp = flist;
40 char *dotp = ".";
41
42 char *makename(); /* char *makename(char *dir, char *file); */
43 struct lbuf *gstat(); /* struct lbuf *gstat(char *file, int argfl); */
44 char *ctime(); /* char *ctime(time_t *t); */
45 long nblock(); /* long nblock(long size); */
46
47 #define ISARG 0100000
Программа начинается с включения файлов (строки 5–8) и объявлений переменных. struct lbuf
(строки 14–27) инкапсулирует части struct stat
, которые интересны ls
. Позже мы увидим, как эта структура заполняется.
Переменные aflg
, dflg
и т.д. (строки 29 и 30) все указывают на наличие соответствующей опции. Такой стиль именования переменных типичен для кода V7. Переменные flist
, lastp
и firstp
(строки 37–39) представляют файлы, о которых ls
выводит сведения. Обратите внимание, что flist является массивом фиксированного размера, которая позволяет обрабатывать не более 1024 файлов. Вскоре мы увидим, как используются все эти переменные.
После объявлений переменных идут объявления функций (строки 42–45), а затем определение ISARG
, которая различает файл, указанный в командной строке, от файла, найденного при чтении каталога.
49 main(argc, argv) /* int main(int argc, char **argv) */
50 char *argv[];
51 {
52 int i;
53 register struct lbuf *ep, **ep1; /* Объявления переменных и функций */
54 register struct lbuf **slastp;
55 struct lbuf **epp;
56 struct lbuf lb;
57 char *t;
58 int compar();
59
60 setbuf(stdout, stdbuf);
61 time(&lb.lmtime); /* Получить текущее время */
62 year = lb.lmtime – 6L*30L*24L*60L*60L; /* 6 месяцев назад */
Функция main()
начинается с объявления переменных и функций (строки 52–58), устанавливая буфер для стандартного вывода, получая время дня (строки 60–61) и вычисляя значение секунд с начала Эпохи для примерно шести месяцев (строка 62). Обратите внимание, что у всех констант есть суффикс L
, указывающий на использование арифметики long
.
63 if (–argc > 0 && *argv[1] == '-') {
64 argv++;
65 while (*++*argv) switch(**argv) { /* Разбор опций */
66
67 case 'a': /* Все элементы каталога */
68 aflg++;
69 continue;
70
71 case 's': /* Размер в блоках */
72 sflg++;
73 statreq++;
74 continue;
75
76 case 'd': /* Сведения о каталоге, не содержание */
77 dflg++;
78 continue;
79
80 case 'g': /* Имя группы вместо имени владельца */
81 gflg++;
82 continue;
83
84 case 'l': /* Расширенный листинг */
85 lflg++;
86 statreq++;
87 continue;
88
89 case 'r': /* Обратный порядок сортировки */
90 rflg = -1;
91 continue;
92
93 case 't': /* Сортировка по времени, не по имени */
94 tflg++;
95 statreq++;
96 continue;
97
98 case 'u': /* Время доступа, а не изменения */
99 uflg++;
100 continue;
101
102 case 'c': /* Время изменения индекса, а не файла */
103 cflg++;
104 continue;
105
106 case 'i': /* Включить номер индекса */
107 iflg++;
108 continue;
109
110 case 'f': /* Форсировать чтение каждого arg как каталога */
111 fflg++;
112 continue;
113
114 default: /* Незнакомые буквы опций игнорировать */
115 continue;
116 }
117 argc–;
118 }
Строки 63–118 разбирают опции командной строки. Обратите внимание на ручной разбор кода: getopt()
еще не была придумана. Переменная statreq
устанавливается в true
, когда опция требует использования системного вызова stat()
.
Избежание ненужного вызова stat()
для каждого файла дает большой выигрыш в производительности. Вызов stat()
был чрезвычайно дорогим, поскольку он мог вызвать поиск расположения индекса на файле, дисковое чтение для получения индекса, а затем поиск на диске расположения содержимого каталога (для того, чтобы продолжить чтение элементов каталога).
В современных системах индексы находятся в группах, распределенных по всей файловой системе, вместо объединения их вместе в начале. Это дает заметный прирост производительности. Тем не менее, вызовы stat()
до сих пор не бесплатны, вы должны использовать их лишь при необходимости, но не более.
119 if (fflg) { /* -f аннулирует -l, -s, -t, добавляя -a */
120 aflg++;
121 lflg = 0;
122 sflg = 0;
123 tflg = 0;
124 statreq = 0;
125 }
126 if (lflg) { /* Открыть файл паролей или групп */
127 t = "/etc/passwd";
128 if (gflg)
129 t = "/etc/group";
130 pwdf = fopen(t, "r");
131 }
132 if (argc==0) { /* Если нет аргументов, использовать текущий */
133 argc++;
134 argv = &dotp – 1;
135 }
Строки 119–125 обрабатывают опцию -f
, выключая -l
, -s
, -t
и statreq
. Строки 126–131 обрабатывают -l
, устанавливая для файла чтение сведений о владельце или группе. Помните, что V7 показывает лишь одно из этих сведений, но не оба.
Если аргументов больше не осталось, строки 132–135 устанавливают argv
таким образом, что он указывает на строку, представляющую текущий каталог. Назначение 'argr = &dotp – 1
' действительно, хотя и необычно. '– 1
' компенсирует '++argv
' в строке 137. Это позволяет избежать в главной части программы специального случая для 'argc == 1
'.
136 for (i=0; i < argc; i++) { /* Получить сведения о каждом файле */
137 if ((ер = gstat(*++argv, 1))==NULL)
138 continue;
139 ep->ln.namep = *argv;
140 ep->lflags |= ISARG;
141 }
142 qsort(firstp, lastp – firstp, sizeof *lastp, compar);
143 slastp = lastp;
144 for (epp=firstp; epp
145 ер = *epp;
146 if (ep->ltype=='d' && dflg==0 || fflg) {
147 if (argc>1)
148 printf("n%s:n", ep->ln.namep);
149 lastp = slastp;
150 readdir(ep->ln.namep);
151 if (fflg==0)
152 qsort(slastp, lastp – slastp, sizeof *lastp, compar);
153 if (lflg || sflg)
154 printf("total %Dn", tblocks);
155 for (ep1=slastp; ep1
156 pentry(*ep1);
157 } else
158 pentry(ep);
159 }
160 exit(0);
161 } /* Конец main() */
Строки 136–141 перебирают аргументы, собирая сведения о каждом. Второй аргумент gstat()
булевый: true
, если имя является аргументом командной строки, в противном случае false
. Строка 140 добавляет флаг ISARG
к полю lflags
для каждого аргумента командной строки.
Функция gstat()
добавляет каждую новую struct lbuf
к глобальному массиву flist
(строка 137). Она также обновляет глобальный указатель lastp
, чтобы он указывал в этом массиве на текущий последний элемент.
Строки 142–143 сортируют массив, используя qsort()
, и сохраняют текущее значение lastp
в slastp
. Строки 144–159 перебирают в цикле каждый элемент массива, выводя соответствующим образом сведения о файле или каталоге.
Код для каталогов заслуживает дальнейшего объяснения:
if (ep->ltype=='d' && dflg==0 || fflg) ...
Строка 146. Если файл является каталогом и -d
не предусмотрено или было установлено -f
, ls
должна прочесть каталог вместо того, чтобы выводить сведения о самом каталоге.
if (argc>1) printf ("n%s:n", ep->ln.namep)
Строки 147–148. Выводят имя каталога и двоеточие, если в командной строке было указано несколько файлов.
lastp = slastp;
readdir(ep->ln.namep)
Строки 149–150. Восстанавливают lastp
из slastp
. Массив flist
действует как двухуровневый стек имен файлов. Аргументы командной строки хранятся с firstp
до slastp – 1
. Когда readdir()
читает каталог, она помещает структуры struct lbuf
для содержимого каталога в стек, начиная с slastp
и до lastp
. Это показано на рис. 7.1.
Рис. 7.1. Массив flist
как двухуровневый стек
if (fflg==0) qsort(slastp, lastp – slastp, sizeof *lastp, compar)
Строки 151–152. Сортируют элементы подкаталога, если не действует -f
.
if (lflg || sflg) printf("total %Dn", tblocks)
Строки 153–154. Выводят для -l
или -s
общее число блоков, используемых файлами в каталоге. Эта сумма хранится в переменной tblocks
, которая сбрасывается для каждого каталога. На современных системах форматирующая строка %D
для printf()
эквивалентна %ld
; она означает «вывести длинное целое». (В V7 есть также %ld
, см. строку 192.)
for (ep1=slastp; ep1
Строки 155–156. Выводит сведения о каждом файле в подкаталоге. Обратите внимание, что V7 ls
спускается лишь на один уровень в дереве каталогов. У нее отсутствует современная «рекурсивная» опция -R
.
163 pentry(ap) /* void pentry(struct lbuf *ap) */
164 struct lbuf *ap;
165 {
166 struct { char dminor, dmajor;}; /* He использующийся исторический артефакт из V6 ls */
167 register t;
168 register struct lbuf *p;
169 register char *cp;
170
171 p = ap;
172 if (p->lnum == -1)
173 return;
174 if (iflg)
175 printf("%5u ", p->lnum); /* Номер индекса */
176 if (sflg)
177 printf("%4D nblock(p->lsize)); /* Размер в блоках */
Процедура pentry()
выводит сведения о файле. Строки 172–173 проверяют, установлен ли -1 в поле lnum
, и если так, функция возвращается. Когда верно 'p->lnum == -1
', структура struct lbuf
недействительна. В противном случае это поле содержит номер индекса файла.
Строки 174–175 выводят номер индекса, если действует -i
. Строки 176–177 выводят общее число блоков, если действует -s
. (Как мы увидим ниже, это число может быть неточным.)
178 if (lflg) { /* Расширенный листинг: */
179 putchar(p->ltype); /* – Тип файла */
180 pmode(p->lflags); /* – Права доступа */
181 printf("%2d ", p->lnl); /* – Число ссылок */
182 t = p->luid;
183 if (gflg)
184 t = p->lgid;
185 if (getname(t, tbuf)==0)
186 printf("%-6.6s", tbuf); /* – Владелец или группа */
187 else
188 printf("%-6d", t);
189 if (p->ltype=='b' || p->ltype=='c') /* – Устройство: старший и младший номера */
190 printf("%3d,%3d", major((int)p->lsize), minor((int)p->lsize));
191 else
192 printf("%71d", p->lsize); /* – Размер в байтах */
193 cp = ctime(&p->lmtime);
194 if (p->lmtime < year) /* – Время изменения */
195 printf(" %-7.7s %-4.4s ", cp+4, cp+20); else
196 printf(" %-12.12s ", cp+4);
197 }
198 if (p->lflags & ISARG) /* – Имя файла */
199 printf("%sn", p->ln.namep);
200 else
201 printf("%.14sn", p->ln.lname);
202 }
Строки 178–197 обрабатывают опцию -l
. Строки 179–181 выводят тип файла, права доступа и число ссылок. Строки 182–184 устанавливают t
на ID владельца или группы, в зависимости от опции -g
. Строки 185–188 получают соответствующее имя и выводят его, если оно доступно. В противном случае программа выводит числовое значение.
Строки 189–192 проверяют, является ли файл блочным или символьным устройством. Если да, они выводят старшее и младшее номера устройств, извлеченные с помощью макросов major()
и minor()
. В противном случае они выводят размер файла.
Строки 193–196 выводят соответствующее время. Если оно старше шести месяцев, код выводит месяц, день и год. В противном случае, выводятся месяц, день и время (формат результата с time()
см. раздел 6.1.3.1 «Простое форматирование времени: asctime()
и ctime()
»).
Наконец, строки 198–201 выводят имя файла. Мы знаем, что для аргумента командной строки это завершающаяся нулем строка, и может быть использована %s
. Для файла, прочитанного из каталога, оно может не завершаться нулем, поэтому должна использоваться явно указанная точность, %.14s
.
204 getname(uid, buf) /* int getname(int uid, char buf[]) */
205 int uid;
206 char buf[];
207 {
208 int j, c, n, i;
209
210 if (uid==lastuid) /* Простое кэширование, см. текст */
211 return(0);
212 if (pwdf == NULL) /* Проверка безопасности */
213 return(-1);
214 rewind(pwdf); /* Начать с начала файла */
215 lastuid = -1;
216 do {
217 i = 0; /* Индекс в массиве buf */
218 j = 0; /* Число полей в строке */
219 n = 0; /* Преобразование числового значения */
220 while ((c=fgetc(pwdf)) != 'n') { /* Прочесть строки */
221 if (c==EOF)
222 return(-1);
223 if (c==':') { /* Число полей*/
224 j++;
225 c = '0';
226 }
227 if (j==0) /* первое поле – имя */
228 buf[i++] = c;
229 if (j==2) /* Третье поле – числовой ID */
230 n = n*10 + c – '0';
231 }
232 } while (n != uid); /* Продолжать до обнаружения ID */
233 buf[i++] = ' ';
234 lastuid = aid;
235 return(0);
236 }
Функция getname()
преобразует ID владельца или группы в соответствующее имя. Она реализует простую схему кэширования; если переданное uid
то же самое, которое находится в глобальной переменной lastuid
, функция возвращает 0 (все нормально), буфер уже содержит имя (строки 210–211). lastuid
инициализируется в -1 (строка 33), поэтому этот тест не проходит, когда getname()
вызывается первый раз.
pwdf
уже открыт либо в /etc/passwd
, либо в /etc/group
(см. строки 126–130). Код здесь проверяет, что открытие было успешным, и если нет, возвращает -1 (строки 212–213).
Удивительно, ls
не использует getpwuid()
или getgrgid()
. Вместо этого она использует преимущество того факта, что формат /etc/passwd
и /etc/group
идентичен для трех первых полей (имя, пароль, числовой ID) и что оба используют в качестве разделителя двоеточие.
Строки 216–232 реализуют линейный поиск по файлу. j
содержит число обнаруженных до сих пор двоеточий: 0 для имени и 2 для ID. Таким образом, при сканировании строки она заполняет как имя, так и ID.
Строки 233–235 завершают буфер name
, устанавливают в глобальной lastuid
последний найденный ID и возвращают 0 для обозначения успеха.
238 long /* long nblock(long size) */
239 nblock(size)
240 long size;
241 {
242 return ((size+511) >>9);
243 }
Функция nblock()
сообщает, сколько дисковых блоков использует файл. Это вычисление основано на размере файла, возвращенном stat()
. Размер блока V7 равен 512 байтам – размер физического сектора диска.
Вычисление в строке 242 выглядит несколько устрашающим. '>>9
' является сдвигом вправо на девять битов. Это осуществляет деление на 512 для получения числа блоков. (На раннем аппаратном обеспечении сдвиг вправо выполнялся гораздо быстрее деления.) Пока все хорошо. Теперь, файл даже размером в один байт все равно занимает целый дисковый блок. Однако, '1 / 512
' дает ноль (целое деление срезает), что неверно. Это объясняет 'size+511
'. Добавляя 511, этот код гарантирует, что сумма дает правильное число блоков при делении на 512.
Это вычисление, однако, лишь приблизительное. У очень больших файлов есть также дополнительные блоки. Несмотря на заявление в справочной странице V7 ls(1), данное вычисление не принимает в расчет дополнительные блоки.
Более того, рассмотрите случай файла с большими дырами (созданными установкой указателя файла дальше конца файла с помощью lseek()
). Дыры не занимают дисковых блоков; однако, это не отражается в значении размера. Поэтому вычисления, выполненные nblock()
, будучи обычно верными, могут давать результаты больше или меньше реальных.
По этим причинам в struct stat
4 2 BSD были добавлены члены st_blocks
, которые затем были приняты для System V и POSIX.
245 int m1[] = { 1, S_IREAD>>0, 'r', '-' };
246 int m2[] = { 1, S_IWRITE>>0, 'w', '-' };
247 int m3[] = { 2, S_ISUID, 's', S_IEXEC>>0, 'x', '-' };
248 int m4[] = { 1, S_IREAD>>3, 'r', '-' };
249 int m5[] = { 1, S_IWRITE>>3, 'w', '-' };
250 int m6[] = { 2, S_ISGID, 's', S_IEXEC>>3, 'x', '-' };
251 int m7[] = { 1, S_IREAD>>6, 'r', '-' };
252 int m8[] = { 1, S_IWRITE>>6, 'w', '-' };
253 int m9[] = { 2, S_ISVTX, ' t', S_IEXEC>>6, 'x', '-' };
254
255 int *m[] = { m1, m2, m3, m4, m5, m6, m7, m8, m9 };
256
257 pmode(aflag) /* void pmode(int aflag) */
258 {
259 register int **mp;
260
261 flags = aflag;
262 for (mp = &m[0]; mp < &m[sizeof(m)/sizeof(m[0])];)
263 select(*mp++);
264 }
265
266 select(pairp) /* void select(register int *pairp) */
267 register int *pairp;
268 {
269 register int n;
270
271 n = *pairp++;
272 while (–n>=0 && (flags&*pairp++)==0)
273 pairp++;
274 putchar(*pairp);
275 }
Строки 245–275 выдают права доступа к файлу. Код компактен и довольно элегантен, он требует тщательного изучения.
• Строки 245–253: массивы с m1
по m9
кодируют биты прав доступа для проверки вместе с соответствующими буквами для вывода. На каждую выводимую букву режима файла имеется один массив. Первый элемент каждого массива является числом пар (право доступа, буква), закодированных в данном конкретном массиве. Последний элемент является буквой, которая должна быть выведена в случае, если не найден ни один из битов прав доступа.
Обратите также внимание, что права доступа обозначены как 'I_READ>>0
', 'I_READ>>3
', 'I_READ>>6
' и т.д. Отдельные константы для каждого бита (S_IRUSR
, S_IRGRP
и т.п.) не были еще придуманы. (См. табл. 4.5 в разделе 4 6.1 «Указание начальных прав доступа к файлу».)
• Строка 255: массив m
указывает на каждый из массивов с m1
по m9
.
• Строки 257–264: функция pmode()
сначала устанавливает глобальную переменную flags
равной переданному параметру aflag
. Затем она просматривает в цикле массив m
, передавая каждый элемент функции select()
. Переданный элемент представляет один из массивов с m1
по m9
.
• Строки 266–275: функция select()
понимает структуру каждого из массивов с m1
по m9
. n
является числом пар в массиве (первый элемент); его устанавливает строка 271. Строки 272–273 ищут биты прав доступа, проверяя установленную ранее в строке 261 глобальную переменную flags
.
Обратите внимание на использование оператора ++
как в проверке цикла, так и в теле цикла. Результатом является пропуск пары в массиве, если в flags
не обнаружен бит доступа в первом элементе пары.
Когда цикл завершается, либо бит разрешения был найден, в этом случае pairp
указывает на второй элемент пары, являющийся нужным для вывода символом, либо он не был найден, в этом случае pairp
указывает на символ по умолчанию. В любом случае, строка 274 выводит символ, на который указывает pairp
.
Последним стоящим внимания моментом является то, что на С символьные константы (такие как 'x
') имеют тип int
, а не char
[75]75
В C++ это по-другому: там символьные константы действительно имеют тип char. Это различие не влияет на данный конкретный код – Примеч. автора.
[Закрыть]. Поэтому проблем с помещением этих констант в массив целых нет; все работает правильно.
277 char* /* char *makename(char *dir, char *file) */
278 makename(dir, file)
279 char *dir, *file;
280 {
281 static char dfile[100];
282 register char *dp, *fp;
283 register int i;
284