Однако, если мы ЗАРАНЕЕ знаем имена файлов в каталоге, мы МОЖЕМ рабо- тать с ними - если имеем право доступа "выполнение" для этого каталога! x выполнение S_IEXEC. Разрешает поиск в каталоге. Для открытия файла, создания/удаления файла, перехода в другой каталог (chdir), система выполняет следующие действия (осуществляемые функцией namei() в ядре): чтение каталога и поиск в нем указан- ного имени файла или каталога; найденному имени соответствует номер I-узла d_ino; по номеру узла система считывает с диска сам I-узел нужного файла и по нему добирается до содержимого файла. Код "выполнение" - это как раз разрешение такого просмотра каталога системой. Если каталог имеет доступ на чтение - мы можем получить список файлов (т.е. применить команду ls); но если он при этом не имеет кода доступа "выполнение" - мы не сможем получить доступа ни к одному из файлов каталога (ни открыть, ни удалить, ни создать, ни сделать stat, ни chdir). Т.е. "чтение" разрешает применение вызова read, а "выполнение" - функции ядра namei. Фактически "выполнение" означает "доступ к файлам в данном каталоге"; еще более точно - к I-nodам файлов этого каталога. t sticky bit S_ISVTX - для каталога он означает, что удалить или переименовать некий файл в данном каталоге могут только: владелец каталога, владелец данного файла, супер- пользователь. И никто другой. Это исключает удаление файлов чужими. Совет: для каталога полезно иметь такие коды доступа: chmod o-w,+t каталог В системах BSD используется, как уже было упомянуто, формат каталога с переменной длиной записей. Чтобы иметь удобный доступ к именам в каталоге, возникли специальные функции чтения каталога: opendir, closedir, readdir. Покажем, как простейшая команда ls реализуется через эти функции. А. Богатырев, 1992-95 - 192 - Си в UNIX #include <stdio.h> #include <sys/types.h> #include <dirent.h> int listdir(char *dirname){ register struct dirent *dirbuf; DIR *fddir; ino_t dot_ino = 0, dotdot_ino = 0; if((fddir = opendir (dirname)) == NULL){ fprintf(stderr, "Can't read %s\n", dirname); return 1; } /* Без сортировки по алфавиту */ while ((dirbuf = readdir (fddir)) != NULL ) { if (dirbuf->d_ino == 0) continue; if (strcmp (dirbuf->d_name, "." ) == 0){ dot_ino = dirbuf->d_ino; continue; } else if(strcmp (dirbuf->d_name, "..") == 0){ dotdot_ino = dirbuf->d_ino; continue; } else printf("%s\n", dirbuf->d_name); } closedir (fddir); if(dot_ino == 0) printf("Поврежденный каталог: нет имени \".\"\n"); if(dotdot_ino == 0) printf("Поврежденный каталог: нет имени \"..\"\n"); if(dot_ino && dot_ino == dotdot_ino) printf("Это корневой каталог диска\n"); return 0; } int main(int ac, char *av[]){ int i; if(ac > 1) for(i=1; i < ac; i++) listdir(av[i]); else listdir("."); return 0; } Обратите внимание, что тут не требуется добавление '\0' в конец поля d_name, пос- кольку его предоставляет нам сама функция readdir(). 6.1.4. Напишите программу удаления файлов и каталогов, заданных в argv. Делайте stat, чтобы определить тип файла (файл/каталог). Программа должна отказываться уда- лять файлы устройств. Для удаления пустого каталога (не содержащего иных имен, кроме "." и "..") сле- дует использовать сисвызов rmdir(имя_каталога); (если каталог не пуст - errno получит значение EEXIST); а для удаления обычных файлов (не каталогов) unlink(имя_файла); Программа должна запрашивать подтверждение на удаление каждого файла, выдавая его имя, тип, размер в килобайтах и вопрос "удалить ?". 6.1.5. Напишите функцию рекурсивного обхода дерева подкаталогов и печати имен всех файлов в нем. Ключ U42 означает файловую систему с длинными именами файлов (BSD 4.2). А. Богатырев, 1992-95 - 193 - Си в UNIX /*#!/bin/cc -DFIND -DU42 -DMATCHONLY treemk.c match.c -o tree -lx * Обход поддерева каталогов (по мотивам Керниган & Ритчи). * Ключи компиляции: * BSD-4.2 BSD-4.3 -DU42 * XENIX с канонической файл.сист. ничего * XENIX с библиотекой -lx -DU42 * программа поиска файлов -DFIND * программа рекурсивного удаления -DRM_REC * программа подсчета используемого места на диске БЕЗ_КЛЮЧА */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/param.h> /* для MAXPATHLEN */ #if defined(M_XENIX) && defined(U42) # include <sys/ndir.h> /* XENIX + U42 эмуляция */ #else # include <dirent.h> # define stat(f,s) lstat(f,s) /* не проходить по символьным ссылкам */ # define d_namlen d_reclen #endif /* проверка: каталог ли это */ #define isdir(st) ((st.st_mode & S_IFMT) == S_IFDIR) struct stat st; /* для сисвызова stat() */ char buf[MAXPATHLEN+1]; /* буфер для имени файла */ #define FAILURE (-1) /* код неудачи */ #define SUCCESS 1 /* код успеха */ #define WARNING 0 /* нефатальная ошибка */ /* Сообщения об ошибках во время обхода дерева: */ #ifndef ERR_CANT_READ # define ERR_CANT_READ(name) \ fprintf( stderr, "\tНе могу читать \"%s\"\n", name), WARNING # define ERR_NAME_TOO_LONG() \ fprintf( stderr, "\tСлишком длинное полное имя\n" ), WARNING #endif /* Прототипы для предварительного объявления функций. */ extern char *strrchr(char *, char); int directory (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)); /* Функции-обработчики enter, leave, touch должны * возвращать (-1) для прерывания просмотра дерева, * либо значение >= 0 для продолжения. */ А. Богатырев, 1992-95 - 194 - Си в UNIX /* Обойти дерево с корнем в rootdir */ int walktree ( char *rootdir, /* корень дерева */ int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st) ){ /* проверка корректности корня */ if( stat(rootdir, &st) < 0 || !isdir(st)){ fprintf( stderr, "\tПлохой корень дерева \"%s\"\n", rootdir ); return FAILURE; /* неудача */ } strcpy (buf, rootdir); return act (buf, 0, enter, leave, touch); } /* Оценка файла с именем name. */ int act (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)) { if (stat (name, &st) < 0) return WARNING; /* ошибка, но не фатальная */ if(isdir(st)){ /* позвать обработчик каталогов */ if(enter) if( enter(name, level, &st) == FAILURE ) return FAILURE; return directory (name, level+1, enter, leave, touch); } else { /* позвать обработчик файлов */ if(touch) return touch (name, level, &st); else return SUCCESS; } } А. Богатырев, 1992-95 - 195 - Си в UNIX /* Обработать каталог: прочитать его и найти подкаталоги */ int directory (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)) { #ifndef U42 struct direct dirbuf; int fd; #else register struct dirent *dirbuf; DIR *fd; extern DIR *opendir(); #endif char *nbp, *tail, *nep; int i, retcode = SUCCESS; #ifndef U42 if ((fd = open (name, 0)) < 0) { #else if ((fd = opendir (name)) == NULL) { #endif return ERR_CANT_READ(name); } tail = nbp = name + strlen (name); /* указатель на закрывающий \0 */ if( strcmp( name, "/" )) /* если не "/" */ *nbp++ = '/'; *nbp = '\0'; #ifndef U42 if (nbp + DIRSIZ + 2 >= name + MAXPATHLEN) { *tail = '\0'; return ERR_NAME_TOO_LONG(); } #endif #ifndef U42 while (read(fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf)){ if (dirbuf.d_ino == 0) /* стертый файл */ continue; if (strcmp (dirbuf.d_name, "." ) == 0 || strcmp (dirbuf.d_name, "..") == 0) /* не интересуют */ continue; for (i = 0, nep = nbp; i < DIRSIZ; i++) *nep++ = dirbuf.d_name[i]; # else /*U42*/ while ((dirbuf = readdir (fd)) != NULL ) { if (dirbuf->d_ino == 0) continue; if (strcmp (dirbuf->d_name, "." ) == 0 || strcmp (dirbuf->d_name, "..") == 0) continue; for (i = 0, nep = nbp; i < dirbuf->d_namlen ; i++) *nep++ = dirbuf->d_name[i]; #endif /*U42*/ *nep = '\0'; if( act(name, level, enter, leave, touch) == FAILURE) { retcode = FAILURE; break; } } А. Богатырев, 1992-95 - 196 - Си в UNIX #ifndef U42 close (fd); #else closedir(fd); #endif *tail = '\0'; /* восстановить старое name */ if(retcode != FAILURE && leave) if( leave(name, level) == FAILURE) retcode = FAILURE; return retcode; } /* -------------------------------------------------------------- */ /* Disk Usage -- Оценка места, занимаемого файлами поддерева */ /* -------------------------------------------------------------- */ /* Пересчет байтов в килобайты */ #define KB(s) (((s)/1024L) + ((s)%1024L ? 1L:0L)) /* или #define KB(s) (((s) + 1024L - 1) / 1024L) */ long size; /* общий размер */ long nfiles; /* всего файлов */ long ndirs; /* из них каталогов */ #define WARNING_LIMIT 150L /* подозрительно большой файл */ static int du_touch (char *name, int level, struct stat *st){ long sz; size += (sz = KB(st->st_size)); /* размер файла в Кб. */ nfiles++; #ifndef TREEONLY if( sz >= WARNING_LIMIT ) fprintf(stderr,"\tВнимание! \"%s\" очень большой: %ld Кб.\n", name, sz); #endif /*TREEONLY*/ return SUCCESS; } static int du_enter (char *name, int level, struct stat *st){ #ifndef TREEONLY fprintf( stderr, "Каталог \"%s\"\n", name ); #endif size += KB(st->st_size); /* размер каталога в Кб. */ nfiles++; ++ndirs; return SUCCESS; } long du (char *name){ size = nfiles = ndirs = 0L; walktree(name, du_enter, NULL, du_touch ); return size; } А. Богатырев, 1992-95 - 197 - Си в UNIX /* -------------------------------------------------------------- */ /* Рекурсивное удаление файлов и каталогов */ /* -------------------------------------------------------------- */ int deleted; /* сколько файлов и каталогов удалено */ static int recrm_dir (char *name, int level){ if( rmdir(name) >= 0){ deleted++; return SUCCESS; } fprintf(stderr, "Не могу rmdir '%s'\n", name); return WARNING; } static int recrm_file(char *name, int level, struct stat *st){ if( unlink(name) >= 0){ deleted++; return SUCCESS; } fprintf(stderr, "Не могу rm '%s'\n", name); return WARNING; } int recrmdir(char *name){ int ok_code; deleted = 0; ok_code = walktree(name, NULL, recrm_dir, recrm_file); printf("Удалено %d файлов и каталогов в %s\n", deleted, name); return ok_code; } /* -------------------------------------------------------------- */ /* Поиск файлов с подходящим именем (по шаблону имени) */ /* -------------------------------------------------------------- */ char *find_PATTERN; static int find_check(char *fullname, int level, struct stat *st){ char *basename = strrchr(fullname, '/'); if(basename) basename++; else basename = fullname; if( match(basename, find_PATTERN)) printf("Level#%02d %s\n", level, fullname); if( !strcmp( basename, "core")){ printf("Найден дамп %s, поиск прекращен.\n", fullname); return FAILURE; } return SUCCESS; } void find (char *root, char *pattern){ find_PATTERN = pattern; walktree(root, find_check, NULL, find_check); } А. Богатырев, 1992-95 - 198 - Си в UNIX /* -------------------------------------------------------------- */ #ifndef TREEONLY void main(int argc, char *argv[]){ #ifdef FIND if(argc != 3){ fprintf(stderr, "Arg count\n"); exit(1); } find(argv[1], argv[2]); #else # ifdef RM_REC for(argv++; *argv; argv++) recrmdir(*argv); # else du( argc == 1 ? "." : argv[1] ); printf( "%ld килобайт в %ld файлах.\n", size, nfiles ); printf( "%ld каталогов.\n", ndirs ); # endif #endif exit(0); } #endif /*TREEONLY*/ 6.1.6. Используя предыдущий алгоритм, напишите программу рекурсивного копирования поддерева каталогов в другое место. Для создания новых каталогов используйте систем- ный вызов mkdir(имя_каталога, коды_доступа); 6.1.7. Используя тот же алгоритм, напишите программу удаления каталога, которая уда- ляет все файлы в нем и, рекурсивно, все его подкаталоги. Таким образом, удаляется дерево каталогов. В UNIX подобную операцию выполняет команда rm -r имя_каталога_корня_дерева 6.1.8. Используя все тот же алгоритм обхода, напишите аналог команды find, который будет позволять: - находить все файлы, чьи имена удовлетворяют заданному шаблону (используйте функ- цию match() из главы "Текстовая обработка"); - находить все выполняемые файлы: обычные файлы S_IFREG, у которых (st.st_mode & 0111) != 0 Как уже ясно, следует пользоваться вызовом stat для проверки каждого файла. 6.2. Время в UNIX. 6.2.1. Напишите функцию, переводящую год, месяц, день, часы, минуты и секунды в число секунд, прошедшее до указанного момента с 00 часов 00 минут 00 секунд 1 Января 1970 года. Внимание: результат должен иметь тип long (точнее time_t). Эта функция облегчит вам сравнение двух моментов времени, заданных в общеприня- том "человеческом" формате, поскольку сравнить два long числа гораздо проще, чем сравнивать по очереди годы, затем, если они равны - месяцы, если месяцы равны - даты, и.т.д.; а также облегчит измерение интервала между двумя событиями - он вычисляется просто как разность двух чисел. В системе UNIX время обрабатывается и хранится именно в виде числа секунд; в частности текущее астрономическое время можно узнать системным вызовом #include <sys/types.h> #include <time.h> time_t t = time(NULL); /* time(&t); */ Функция struct tm *tm = localtime( &t ); А. Богатырев, 1992-95 - 199 - Си в UNIX разлагает число секунд на отдельные составляющие, содержащиеся в int-полях структуры: tm_year год (надо прибавлять 1900) tm_yday день в году 0..365 tm_mon номер месяца 0..11 (0 - Январь) tm_mday дата месяца 1..31 tm_wday день недели 0..6 (0 - Воскресенье) tm_hour часы 0..23 tm_min минуты 0..59 tm_sec секунды 0..59 Номера месяца и дня недели начинаются с нуля, чтобы вы могли использовать их в качестве индексов: char *months[] = { "Январь", "Февраль", ..., "Декабрь" }; printf( "%s\n", months[ tm->tm_mon ] ); Пример использования этих функций есть в приложении. Установить время в системе может суперпользователь вызовом stime(&t); 6.2.2. Напишите функцию печати текущего времени в формате ЧЧ:ММ:СС ДД-МЕС-ГГ. Используйте системный вызов time() и функцию localtime(). Существует стандартная функция ctime(), которая печатает время в формате: /* Mon Mar 25 18:56:36 1991 */ #include <stdio.h> #include <time.h> main(){ /* команда date */ time_t t = time(NULL); char *s = ctime(&t); printf("%s", s); } Обратите внимание, что строка s уже содержит на конце символ '\n'. 6.2.3. Структура stat, заполняемая системным вызовом stat(), кроме прочих полей содержит поля типа time_t st_ctime, st_mtime и st_atime - время последнего изменения содержимого I-узла файла, время последнего изменения файла и время последнего доступа к файлу. - Поле st_ctime изменяется (устанавливается равным текущему астрономическому вре- мени) при применении к файлу вызовов creat, chmod, chown, link, unlink, mknod, utime|-, write (т.к. изменяется длина файла); Это поле следует рассматривать как время модификации прав доступа к файлу; - st_mtime - write, creat, mknod, utime; Это поле следует рассматривать как время модификации содержимого файла (данных); - st_atime - read, creat, mknod, utime; Это поле следует рассматривать как время чтения содержимого файла (данных). Модифицируйте функцию typeOf(), чтобы она печатала еще и эти даты. ____________________ |- Время модификации файла можно изменить на текущее астрономическое время и не производя записи в файл. Для этого используется вызов utime(имяФайла, NULL); Он используется для взаимодействия с программой make - в команде touch. Изменить время можно только своему файлу. А. Богатырев, 1992-95 - 200 - Си в UNIX 6.2.4. Напишите аналог команды ls -tm, выдающей список имен файлов текущего ката- лога, отсортированный по убыванию поля st_mtime, то есть недавно модифицированные файлы выдаются первыми. Для каждого прочитанного из каталога имени надо сделать stat; имена файлов и времена следует сохранить в массиве структур, а затем отсортиро- вать его. 6.2.5. Напишите аналогичную программу, сортирующую файлы в порядке возрастания их размера (st_size). 6.2.6. Напишите аналог команды ls -l, выдающий имена файлов каталога и их коды дос- тупа в формате rwxrw-r--. Для получения кодов доступа используйте вызов stat stat( имяФайла, &st); кодыДоступа = st.st_mode & 0777; Для изменения кодов доступа используется вызов chmod(имя_файла, новые_коды); Можно изменять коды доступа, соответствующие битовой маске 0777 | S_ISUID | S_ISGID | S_ISVTX (смотри <sys/stat.h>). Тип файла (см. функцию typeOf) не может быть изменен. Изме- нить коды доступа к файлу может только его владелец. Печатайте еще номер I-узла файла: поле d_ino каталога либо поле st_ino структуры stat. 6.2.7. Вот программа, которая каждые 2 секунды проверяет - не изменилось ли содержи- мое текущего каталога: #include <sys/types.h> #include <sys/stat.h> extern char *ctime(); main(){ time_t last; struct stat st; for( stat(".", &st), last=st.st_mtime; ; sleep(2)){ stat(".", &st); if(last != st.st_mtime){ last = st.st_mtime; printf("Был создан или удален какой-то файл: %s", ctime(&last)); } } } Модифицируйте ее, чтобы она сообщала какое имя (имена) было удалено или создано (для этого надо при запуске программы прочитать и запомнить содержимое каталога, а при обнаружении модификации - перечитать каталог и сравнить его с прежним содержимым). 6.2.8. Напишите по аналогии программу, которая выдает сообщение, если указанный вами файл был кем-то прочитан, записан или удален. Вам следует отслеживать изменение полей st_atime, st_mtime и значение stat() < 0 соответственно. Если файл удален - программа завершается. 6.2.9. Современные UNIX-машины имеют встроенные таймеры (как правило несколько) с довольно высоким разрешением. Некоторые из них могут использоваться как "будильники" с обратным отсчетом времени: в таймер загружается некоторое значение; таймер ведет обратный отсчет, уменьшая загруженный счетчик; как только это время истекает - посы- лается сигнал процессу, загрузившему таймер. А. Богатырев, 1992-95 - 201 - Си в UNIX Вот как, к примеру, выглядит функция задержки в микросекундах (миллионных долях секунды). Примечание: эту функцию не следует использовать вперемежку с функциями sleep и alarm (смотри статью про них ниже, в главе про сигналы). #include <sys/types.h> #include <signal.h> #include <sys/time.h> void do_nothing() {} /* Задержка на usec миллионных долей секунды (микросекунд) */ void usleep(unsigned int usec) { struct itimerval new, old; /* struct itimerval содержит поля: struct timeval it_interval; struct timeval it_value; Где struct timeval содержит поля: long tv_sec; -- число целых секунд long tv_usec; -- число микросекунд */ struct sigaction new_vec, old_vec; if (usec == 0) return; /* Поле tv_sec содержит число целых секунд. Поле tv_usec содержит число микросекунд. it_value - это время, через которое В ПЕРВЫЙ раз таймер "прозвонит", то есть пошлет нашему процессу сигнал SIGALRM. Время, равное нулю, немедленно остановит таймер. it_interval - это интервал времени, который будет загружаться в таймер после каждого "звонка" (но не в первый раз). Время, равное нулю, остановит таймер после его первого "звонка". */ new.it_interval.tv_sec = 0; new.it_interval.tv_usec = 0; new.it_value.tv_sec = usec / 1000000; new.it_value.tv_usec = usec % 1000000; А. Богатырев, 1992-95 - 202 - Си в UNIX /* Сохраняем прежнюю реакцию на сигнал SIGALRM в old_vec, заносим в качестве новой реакции do_nothing() */ new_vec.sa_handler = do_nothing; sigemptyset(&new_vec.sa_mask); new_vec.sa_flags = 0; sighold(SIGALRM); sigaction(SIGALRM, &new_vec, &old_vec); /* Загрузка интервального таймера значением new, начало отсчета. * Прежнее значение спасти в old. * Вместо &old можно также NULL - не спасать. */ setitimer(ITIMER_REAL, &new, &old); /* Ждать прихода сигнала SIGALRM */ sigpause(SIGALRM); /* Восстановить реакцию на SIGALRM */ sigaction(SIGALRM, &old_vec, (struct sigaction *) 0); sigrelse(SIGALRM); /* Восстановить прежние параметры таймера */ setitimer(ITIMER_REAL, &old, (struct itimerval *) 0); } 6.2.10. Второй пример использования таймера - это таймер, отсчитывающий текущее время суток (а также дату). Чтобы получить значение этого таймера используется вызов функции gettimeofday #include <time.h> void main(){ struct timeval timenow; gettimeofday(&timenow, NULL); printf("%u sec, %u msec\n", timenow.tv_sec, timenow.tv_usec ); printf("%s", ctime(&timenow.tv_sec)); exit(0); } Поле tv_sec содержит число секунд, прошедшее с полуночи 1 января 1970 года до данного момента; в чем полностью соответствует системному вызову time. Однако плюс к тому поле tv_usec содержит число миллионных долей текущей секунды (значение этого поля всегда меньше 1000000). 6.2.11. К данному параграфу вернитесь, изучив раздел про fork() и exit(). Каждый процесс может пребывать в двух фазах: системной (внутри тела системного вызова - его выполняет для нас ядро операционной системы) и пользовательской (внутри кода самой программы). Время, затраченное процессом в каждой фазе, может быть измеряно системным вызовом times(). Кроме того, этот вызов позволяет узнать суммарное время, затраченное порожденными процессами (порожденными при помощи fork). Системный вызов заполняет структуру А. Богатырев, 1992-95 - 203 - Си в UNIX struct tms { clock_t tms_utime; clock_t tms_stime; clock_t tms_cutime; clock_t tms_cstime; }; и возвращает значение #include <sys/times.h> struct tms time_buf; clock_t real_time = times(&time_buf); Все времена измеряются в "тиках" - некоторых долях секунды. Число тиков в секунде можно узнать таким системным вызовом (в системе Solaris): #include <unistd.h> clock_t HZ = sysconf(_SC_CLK_TCK); В старых системах, где таймер работал от сети переменного тока, это число получалось равным 60 (60 Герц - частота сети переменного тока). В современных системах это 100. Поля структуры содержат: tms_utime время, затраченное вызывающим процессом в пользовательской фазе. tms_stime время, затраченное вызывающим процессом в системной фазе. tms_cutime время, затраченное порожденными процессами в пользовательской фазе: оно равно сумме всех tms_utime и tms_cutime порожденных процессов (рекурсивное суммирова- ние). tms_cstime время, затраченное порожденными процессами в системной фазе: оно равно сумме всех tms_stime и tms_cstime порожденных процессов (рекурсивное суммирование). real_time время, соответствующее астрономическому времени системы. Имеет смысл мерять только их разность. Вот пример программы: #include <stdio.h> #include <unistd.h> /* _SC_CLK_TCK */ #include <signal.h> /* SIGALRM */ #include <sys/time.h> /* не используется */ #include <sys/times.h> /* struct tms */ struct tms tms_stop, tms_start; clock_t real_stop, real_start; clock_t HZ; /* число ticks в секунде */ А. Богатырев, 1992-95 - 204 - Си в UNIX /* Засечь время момента старта процесса */ void hello(void){ real_start = times(&tms_start); } /* Засечь время окончания процесса */ void bye(int n){ real_stop = times(&tms_stop); #ifdef CRONO /* Разность времен */ tms_stop.tms_utime -= tms_start.tms_utime; tms_stop.tms_stime -= tms_start.tms_stime; #endif /* Распечатать времена */ printf("User time = %g seconds [%lu ticks]\n", tms_stop.tms_utime / (double)HZ, tms_stop.tms_utime); printf("System time = %g seconds [%lu ticks]\n", tms_stop.tms_stime / (double)HZ, tms_stop.tms_stime); printf("Children user time = %g seconds [%lu ticks]\n", tms_stop.tms_cutime / (double)HZ, tms_stop.tms_cutime); printf("Children system time = %g seconds [%lu ticks]\n", tms_stop.tms_cstime / (double)HZ, tms_stop.tms_cstime); printf("Real time = %g seconds [%lu ticks]\n", (real_stop - real_start) / (double)HZ, real_stop - real_start); exit(n); } /* По сигналу SIGALRM - завершить процесс */ void onalarm(int nsig){ printf("Выход #%d ================\n", getpid()); bye(0); } /* Порожденный процесс */ void dochild(int n){ hello(); printf("Старт #%d ================\n", getpid()); signal(SIGALRM, onalarm); /* Заказать сигнал SIGALRM через 1 + n*3 секунд */ alarm(1 + n*3); for(;;){} /* зациклиться в user mode */ } А. Богатырев, 1992-95 - 205 - Си в UNIX #define NCHLD 4 int main(int ac, char *av[]){ int i; /* Узнать число тиков в секунде */ HZ = sysconf(_SC_CLK_TCK); setbuf(stdout, NULL); hello(); for(i=0; i < NCHLD; i++) if(fork() == 0) dochild(i); while(wait(NULL) > 0); printf("Выход MAIN =================\n"); bye(0); return 0; } и ее выдача: Старт #3883 ================ Старт #3884 ================ Старт #3885 ================ Старт #3886 ================ Выход #3883 ================ User time = 0.72 seconds [72 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 1.01 seconds [101 ticks] Выход #3884 ================ User time = 1.88 seconds [188 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 4.09 seconds [409 ticks] Выход #3885 ================ User time = 4.41 seconds [441 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 7.01 seconds [701 ticks] Выход #3886 ================ User time = 8.9 seconds [890 ticks] System time = 0 seconds [0 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 10.01 seconds [1001 ticks] Выход MAIN ================= User time = 0.01 seconds [1 ticks] System time = 0.04 seconds [4 ticks] Children user time = 15.91 seconds [1591 ticks] Children system time = 0.03 seconds [3 ticks] Real time = 10.41 seconds [1041 ticks] Обратите внимание, что 72+188+441+890=1591 (поле tms_cutime для main). 6.2.12. Еще одна программа: хронометрирование выполнения другой программы. Пример: timer ls -l А. Богатырев, 1992-95 - 206 - Си в UNIX /* Хронометрирование выполнения программы */ #include <stdio.h> #include <unistd.h> #include <sys/times.h> extern errno; typedef struct _timeStamp { clock_t real_time; clock_t cpu_time; clock_t child_time; clock_t child_sys, child_user; } TimeStamp; TimeStamp TIME(){ struct tms tms; TimeStamp st; st.real_time = times(&tms); st.cpu_time = tms.tms_utime + tms.tms_stime + tms.tms_cutime + tms.tms_cstime; st.child_time = tms.tms_cutime + tms.tms_cstime; st.child_sys = tms.tms_cstime; st.child_user = tms.tms_cutime; return st; } void PRTIME(TimeStamp start, TimeStamp stop){ clock_t HZ = sysconf(_SC_CLK_TCK); clock_t real_time = stop.real_time - start.real_time; clock_t cpu_time = stop.cpu_time - start.cpu_time; clock_t child_time = stop.child_time - start.child_time; printf("%g real, %g cpu, %g child (%g user, %g sys), %ld%%\n", real_time / (double)HZ, cpu_time / (double)HZ, child_time / (double)HZ, stop.child_user / (double)HZ, stop.child_sys / (double)HZ, (child_time * 100L) / (real_time ? real_time : 1) ); } А. Богатырев, 1992-95 - 207 - Си в UNIX TimeStamp start, stop; int main(int ac, char *av[]){ char *prog = *av++; if(*av == NULL){ fprintf(stderr, "Usage: %s command [args...]\n", prog); return(1); } start = TIME(); if(fork() == 0){ execvp(av[0], av); perror(av[0]); exit(errno); } while(wait(NULL) > 0); stop = TIME(); PRTIME(start, stop); return(0); } 6.3. Свободное место на диске. 6.3.1. Системный вызов ustat() позволяет узнать количество свободного места в файло- вой системе, содержащей заданный файл (в примере ниже - текущий каталог): #include <sys/types.h> #include <sys/stat.h> #include <ustat.h> struct stat st; struct ustat ust; void main(int ac, char *av[]){ char *file = (ac==1 ? "." : av[1]); if( stat(file, &st) < 0) exit(1); ustat(st.st_dev, &ust); printf("На диске %*.*s\n" "%ld свободных блоков (%ld Кб)\n" "%d свободных I-узлов\n", sizeof ust.f_fname, sizeof ust.f_fname, ust.f_fname, /* название файловой системы (метка) */ ust.f_tfree, /* блоки по 512 байт */ (ust.f_tfree * 512L) / 1024, ust.f_tinode ); } Обратите внимание на запись длинной строки в printf: строки, перечисленные последова- тельно, склеиваются ANSI C компилятором в одну длинную строку: char s[] = "This is" " a line " "of words"; совпадает с char s[] = "This is a line of words"; 6.3.2. Более правильно, однако, пользоваться сисвызовом statvfs - статистика по вир- туальной файловой системе. Рассмотрим его в следующем примере: копирование файла с проверкой на наличие свободного места. А. Богатырев, 1992-95 - 208 - Си в UNIX #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <fcntl.h> /* O_RDONLY */ #include <sys/types.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <sys/param.h> /* MAXPATHLEN */ char *progname; /* имя программы */ void error(char *fmt, ...){ va_list args; va_start(args, fmt); fprintf(stderr, "%s: ", progname); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); } int copyFile(char *to, char *from){ /* куда, откуда */ char newname[MAXPATHLEN+1]; char answer[20]; struct stat stf, stt; int fdin, fdout; int n, code = 0; char iobuf[64 * 1024]; char *dirname = NULL, *s; if((fdin = open(from, O_RDONLY)) < 0){ error("Cannot read %s", from); return (-1); } fstat(fdin, &stf); if((stf.st_mode & S_IFMT) == S_IFDIR){ close(fdin); error("%s is a directory", from); return (-2); } А. Богатырев, 1992-95 - 209 - Си в UNIX if(stat(to, &stt) >= 0){ /* Файл уже существует */ if((stt.st_mode & S_IFMT) == S_IFDIR){ /* И это каталог */ /* Выделить последнюю компоненту пути from */ if((s = strrchr(from, '/')) && s[1]) s++; else s = from; dirname = to; /* Целевой файл - файл в этом каталоге */ sprintf(newname, "%s/%s", to, s); to = newname; if(stat(to, &stt) < 0) goto not_exist; } if(stt.st_dev == stf.st_dev && stt.st_ino == stf.st_ino){ error("%s: cannot copy file to itself", from); return (-3); } switch(stt.st_mode & S_IFMT){ case S_IFBLK: case S_IFCHR: case S_IFIFO: break; default: printf("%s already exists, overwrite ? ", to); fflush(stdout); *answer = '\0'; gets(answer); if(*answer != 'y'){ /* NO */ close(fdin); return (-4); } break; } } А. Богатырев, 1992-95 - 210 - Си в UNIX not_exist: printf("COPY %s TO %s\n", from, to);