6.1.5. Напишите функцию рекурсивного обхода дерева подкаталогов и печати имен всех файлов в нем. Ключ U42 означает файловую систему с длинными именами файлов (BSD 4.2).

    /*#!/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 для продолжения. */
    /* Обойти дерево с корнем в 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;
        }
    }

    /* Обработать каталог: прочитать его и найти подкаталоги */
    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;                          }
        }

    #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;
    }

    /* -------------------------------------------------------------- */
    /* Рекурсивное удаление файлов и каталогов                        */
    /* -------------------------------------------------------------- */
    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);
    }

    /* -------------------------------------------------------------- */
    #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, который будет позволять:

            (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 );

разлагает число секунд на отдельные составляющие, содержащиеся в 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-узла файла, время последнего изменения файла и время последнего доступа к файлу.

Модифицируйте функцию typeOf(), чтобы она печатала еще и эти даты.

    utime(имяФайла, NULL);

Он используется для взаимодействия с программой make - в команде touch. Изменить время можно только своему файлу.

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-машины имеют встроенные таймеры (как правило несколько) с довольно высоким разрешением. Некоторые из них могут использоваться как "будильники" с обратным отсчетом времени: в таймер загружается некоторое значение; таймер ведет обратный отсчет, уменьшая загруженный счетчик; как только это время истекает - посылается сигнал процессу, загрузившему таймер.

Вот как, к примеру, выглядит функция задержки в микросекундах (миллионных долях секунды). Примечание: эту функцию не следует использовать вперемежку с функциями 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;

            /* Сохраняем прежнюю реакцию на сигнал 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);
    }

* - Время модификации файла можно изменить на текущее астрономическое время и не производя записи в файл. Для этого используется вызов

© Copyright А. Богатырев, 1992-95
Си в UNIX

Назад | Содержание | Вперед