Книга: Linux программирование в примерах

7.2. Код V7 ls

7.2. Код V7 ls

Файл /usr/src/cmd/ls.c в дистрибутиве V7 содержит код. Весь он занимает 425 строк.

1  /*
2   * перечисляет файлы или каталоги
3   */
4
5  #include <sys/param.h>
6  #include <sys/stat.h>
7  #include <sys/dir.h>
8  #include <stdio.h>
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<slastp; 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<lastp; 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<lastp; ep1++) pentry(*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]. Поэтому проблем с помещением этих констант в массив целых нет; все работает правильно.

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
285  dp = dfile;
286  fp = dir;
287  while (*fp)
288   *dp++ = *fp++;
289  *dp++ = '/';
290  fp = file;
291  for (i=0; i<DIRSIZ; i++)
292   *dp++ = * fp++;
293  *dp = 0;
294  return(dfile);
295 }

Строки 277–295 определяют функцию makename(). Ее работа заключается в соединении имени каталога с именем файла, разделенным символом косой черты, с образованием строки. Она осуществляет это в static буфере dfile. Обратите внимание, что dfile всего лишь 100 символов длиной и что проверка ошибок не выполняется.

Сам код прост, он копирует по одному символу за раз. makename() используется функцией readdir().

297 readdir(dir) /* void readdir(char *dir) */
298 char *dir;
299 {
300  static struct direct dentry;
301  register int j;
302  register struct lbuf *ep;
303
304  if ((dirf = fopen(dir, "r")) == NULL) {
305   printf("%s unreadablen", dir);
306   return;
307  }
308  tblocks = 0;
309  for(;;) {
310   if (fread((char*)&dentry, sizeof(dentry), 1, dirf) != 1)
311    break;
312   if (dentry.d_ino==0
313    || aflg==0 && dentry.d_name[0]=='.' && (dentry.d_name[1]==''
314    || dentry.d_name[1]=='.' && dentry, d_name[2]==''))
315    continue;
316   ep = gstat(makename(dir, dentry.d_name), 0);
317   if (ep==NULL)
318    continue;
319   if (ep->lnum != -1)
320    ep->lnum = dentry.d_ino;
321   for (j =0; j<DIRSIZ; j++)
322    ep->ln.lname[j] = dentry.d_name[j];
323  }
324  fclose(dirf);
325 }

Строки 297–325 определяют функцию readdir(), чья работа заключается в чтении содержимого каталогов, указанных в командной строке.

Строки 304–307 открывают каталог для чтения, завершая функцию, если fopen() возвращает ошибку. Строка 308 инициализирует глобальную переменную tblocks нулем. Ранее (строки 153–154) это использовалось для вывода общего числа блоков, использованных файлами в каталоге.

Строки 309–323 являются циклом, который читает элементы каталога и добавляет их к массиву flist. Строки 310–311 читают один элемент, выходя из цикла в конце файла.

Строки 312–315 пропускают неинтересные элементы. Если номер индекса равен нулю, этот слот не используется. В противном случае, если не был указан -а и имя файла является '.' или '..', оно пропускается.

Строки 316–318 вызывают gstat() с полным именем файла и вторым аргументом, равным false, указывающим, что он не из командной строки. gstat() обновляет глобальный указатель lastp и массив flist. Возвращаемое значение NULL обозначает какую-нибудь разновидность ошибки.

Строки 319–322 сохраняют номер индекса и имя в struct lbuf. Если ep->lnum возвращается из gstat() установленным в -1, это означает, что операция stat() с файлом завершилась неудачей. Наконец, строка 324 закрывает каталог.

Следующая функция, gstat() (строки 327–398), является центральной функцией для получения и сохранения сведений о файле.

327 struct lbuf * /* struct lbuf *gstat(char *file, int argfl) */
328 gstat(file, argfl)
329 char *file;
330 {
331  extern char *malloc();
332  struct stat statb;
333  register struct lbuf *rep;
334  static int nomocore;
335
336  if (nomocore) /* Ранее была нехватка памяти */
337   return(NULL);
338  rep = (struct lbuf*)malloc(sizeof(struct lbuf));
339  if (rep==NULL) {
340   fprintf(stderr, "ls: out of memoryn");
341   nomocore = 1;
342   return(NULL);
343  }
344  if (lastp >= &flist[NFILES]) { /* Проверить, не дано ли слишком много файлов */
345   static int msg;
346   lastp--;
347   if (msg==0) {
348    fprintf(stderr, "ls: too many filesn");
349    msg++;
350   }
351  }
352  *lastp++ = rep; /* Заполнить сведения */
353  rep->lflags = 0;
354  rep->lnum = 0;
355  rep->ltype = '-'; /* Тип файла по умолчанию */

Статическая переменная nomocore [важно] указывает, что malloc() при предыдущем вызове завершилась неудачей. Поскольку она статическая, она автоматически инициализируется 0 (т.е. false). Если на входе она равна true, gstat() просто возвращает NULL. В противном случае, если malloc() завершается неудачей, ls выводит сообщение об ошибке, устанавливает в nomocoretrue и возвращает NULL (строки 334–343).

Строки 344–351 гарантируют, что в массиве flist все еще остается место. Если нет, ls выдает сообщение (но лишь однажды; заметьте использование статической переменной msg), а затем повторно использует последний слот flist.

Строка 352 заставляет слот lastp указывать на новую struct lbuf(rep). Это также обновляет lastp, который используется для сортировки в main() (строки 142 и 152). Строки 353–355 устанавливают значения по умолчанию для полей флагов, номеров индексов и типов в struct lbuf.

356  if (argfl || statreq) {
357   if (stat(file, &statb)<0) { /* stat() завершилась неудачей */
358    printf("%s not foundn", file);
359    statb.st_ino = -1;
360    statb.st_size = 0;
361    statb.st_mode = 0;
362    if (argfl) {
363     lastp--;
364     return(0);
365    }
366   }
367   rep->lnum = statb.st_ino; /* stat() OK, копировать сведения */
368   rep->lsize = statb.st_size;
369   switch(statb.st_mode & S_IFMT) {
370
371   case S_IFDIR:
372    rep->ltype = 'd';
373    break;
374
375   case S_IFBLK:
376    rep->ltype = 'b';
377    rep->lsize = statb.st_rdev;
378    break;
379
380   case S_IFCHR:
381    rep->ltype = 'c';
382    rep->lsize = statb.st_rdfev;
383    break;
384   }
385   rep->lflags = statb.st_mode & ~S_IFMT;
386   rep->luid = statb.st_uid;
387   rep->lgid = statb.st_gid;
388   rep->lnl = statb.st_nlink;
389   if (uflg)
390    rep->lmtime = statb.st_atime;
391   else if (cflg)
392    rep->lmtime = statb.st_ctime;
393   else
394    rep->lmtime = statb.st_mtime;
395   tblocks += nblock(statb.st_size);
396  }
397  return(rep);
398 }

Строки 356–396 обрабатывают вызов stat(). Если это аргумент командной строки или если statreq установлен в true благодаря опции, код заполняет struct lbuf следующим образом:

• Строки 357–366: вызывают stat(), при ее неудаче выводится сообщение об ошибке с установкой соответствующих значений, затем возвращается NULL (выраженный в виде 0).

• Строки 367–368: устанавливают в struct stat поля номера индекса и размера, если вызов stat() был успешным.

• Строки 369–384: обрабатывают особые случаи каталогов, блочных и символьных устройств. Во всех случаях код обновляет поле ltype. Для устройств значение lsize замещается значением st_rdev.

• Строки 385–388. заполняются поля lflags, luid, lgid и lnl из соответствующих полей в struct stat. Строка 385 удаляет биты типа файла, оставляя 12 битов прав доступа (на чтение/запись/исполнение для владельца/группы/остальных, а также setuid, setgid и save-text).

• Строки 389–394: основываясь на опциях командной строки, используют одно из трех полей времени в struct stat для поля lmtime в struct lbuf.

• Строка 395: обновляет глобальную переменную tblocks числом блоков в файле.

400 compar(pp1, pp2) /* int compar(struct lbuf **pp1, */
401 struct lbuf **pp1, **pp2; /* struct lbuf **pp2) */
402 {
403  register struct lbuf *p1, *p2;
404
405  p1 = *pp1;
406  p2 = *pp2;
407  if (dflg==0) {
408   if (p1->lflags&ISARG && p1->ltype=='d') {
409    if (!(p2->lflags&ISARG && p2->ltype=='d'))
410     return(1);
411   } else {
412    if (p2->lflags&ISARG && p2->ltype=='d')
413     return(-1);
414   }
415  }
416  if (tflg) {
417   if(p2->lmtime == p1->lmtime)
418    return(0);
419   if (p2->lmtime > p1->lmtime)
420    return(rflg);
421   return(-rflg);
422  }
423  return(rflg * strcmp(p1->lflags&ISARG ? p1->ln.namep : p1->ln.lname,
424   p2->lflags&ISARG ? p2->ln.namep : p2->ln.lname));
425 }

Функция compar() сжата: в небольшом пространстве происходит многое. Первая вещь, которую следует запомнить, это смысл возвращаемого значения: отрицательное значение означает, что первый файл должен идти перед вторым, ноль означает, что файлы равны, а положительное значение означает, что второй файл должен идти перед первым

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

Наконец, переменная rflg помогает реализовать опцию -r, которая меняет порядок сортировки. Она инициализируется 1 (строка 30). Если -r используется, rflg устанавливается в -1 (строки 89–91).

Следующий псевдокод описывает логику compar(); номера строк на левой границе соответствуют номерам строк ls.c:

407 if ls должна прочесть каталоги # dflg == 0
408  if p1 аргумент командной строки и p1 каталог
409   if p2 не аргумент командной строки и не каталог
410    return 1 # первый идет после второго
      else
       перейти на тест времени
411  else
      # p1 не каталог командной строки
412   if p2 аргумент командной строки и каталог
413    return -1 # первый идет перед вторым
      else
       перейти на тест времени
416 if сортировка основана на времени # tflg равно true
     # сравнить времена:
417  if время p2 равно времени p1
418   return 0
419  if время p2 > времени p1
420   return значение rflg (положительное или отрицательное)
     # время p2 < времени p1
421  return противоположное rflg значение (положительное или отрицательное)
423 Умножить rflg на результат strcmp()
424 для двух имен и вернуть результат

Аргументы strcmp() в строках 423–424 выглядят сбивающими с толку. В зависимости от того, было ли имя файла указано в командной строке или было прочитано из каталога, должны использоваться различные члены объединения ln в struct lbuf.

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


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