355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Нейл Мэтью » Основы программирования в Linux » Текст книги (страница 13)
Основы программирования в Linux
  • Текст добавлен: 21 сентября 2016, 17:59

Текст книги "Основы программирования в Linux"


Автор книги: Нейл Мэтью


Соавторы: Ричард Стоунс
сообщить о нарушении

Текущая страница: 13 (всего у книги 67 страниц)

Другие потоковые функции

В библиотеке stdio существует ряд других функций, использующих потоки как параметры или стандартные потоки stdin, stdout, stderr:

fgetpos – возвращает текущую позицию в файловом протоке;

fsetpos – устанавливает текущую позицию в файловом потоке;

ftell – возвращает величину текущего смещения файла в потоке;

rewind – сбрасывает текущую позицию файла в потоке и переводит ее в начало файла;

freopen – повторно использует файловый поток;

setvbuf – задает схему буферизации для потока;

remove – эквивалент функции unlink, до тех пор пока параметр path не является каталогом, в этом случае она эквивалентна функции rmdir.

Эти библиотечные функции описаны на страницах интерактивного справочного руководства в разделе 3.

Вы можете использовать функции обработки файловых потоков для повторной реализации с их помощью программы копирования файлов. Взгляните на программу copy_stdio.c в упражнении 3.3.

Упражнение 3.3. Третья версия программы копирования файлов

Эта программа очень похожа на предыдущие версии, но посимвольное копирование выполняется с помощью вызовов функций, заданных в файле stdio.h:

#include

#include

int main() {

 int c; 

 FILE *in, *out;

 in = fopen(«file.in», "r");

 out = fopen(«file.out», "w");

 while((c = fgetc(in)) != EOF) fputc(c, out);

 exit(0);

}

Выполнив эту программу, как прежде, вы получите:

$ TIMEFORMAT="" time ./copy_stdio

0.06user 0.02system 0:00.11elapsed 81%CPU

Как это работает

На этот раз программа выполняется 0,11 с, не так быстро, как низкоуровневая блочная версия, но значительно быстрее другой посимвольной версии. Это произошло потому, что библиотека stdio поддерживает внутренний буфер в структуре FILE, и низкоуровневые системные вызовы выполняются, только когда буфер заполняется. Не ленитесь экспериментировать, тестируя программный код построчного и блочного копирования с помощью stdio, чтобы увидеть, как они действуют в случае проверенных нами трех примеров.

Ошибки потока

Для обозначения ошибок многие функции библиотеки stdio применяют значения за пределами допустимых, например, пустые указатели или константу EOF. В этих случаях ошибка указывается во внешней переменной errno.

#include

extern int errno;

Примечание

Имейте в виду, что многие функции могут изменять значение errno. Оно достоверно, только когда функция закончилась неудачно. Вам следует проверять это значение сразу же, как функция сообщила о сбое. Прежде чем использовать его, скопируйте это значение в другую переменную, поскольку функции вывода, такие как fprintf, могут сами изменять errno.

Вы можете также запросить состояние файлового потока, чтобы выяснить, возникла ли ошибка или достигнут конец файла.

#include

int ferror(FILE *stream);

int feof(FILE *stream);

void clearerr(FILE *stream);

Функция ferror проверяет индикатор ошибок потока и возвращает ненулевое значение, если индикатор установлен, и ноль в противном случае.

Функция feof проверяет индикатор конца файла в потоке и возвращает ненулевое значение, если индикатор установлен, или ноль в противном случае. Применяйте ее следующим образом:

if (feof(some_stream))

 /* Мы в конце */

Функция clearerr очищает индикаторы конца файла и ошибки для потока, на который указывает параметр stream. Она не возвращает никакого значения, и для нее не определены никакие ошибки. Вы можете применять эту функцию для сброса состояния ошибки в потоках. Примером может быть возобновление записи в поток после разрешения проблемы, связанной с ошибкой «disk full» (диск заполнен).

Потоки и дескрипторы файлов

Каждый файловый поток ассоциирован с низкоуровневым дескриптором файла. Вы можете смешивать операции низкоуровневого ввода/вывода с высокоуровневыми потоковыми операциями, но это, как правило, неразумно, потому что трудно предсказать эффект от применения буферизации.

#include

int fileno(FILE *stream);

FILE *fdopen(int fildes, const char *mode);

Вы можете определить, какой низкоуровневый дескриптор файла применяется для файлового потока, вызвав функцию fileno. Она возвращает дескриптор файла для заданного потока или -1 в случае сбоя. Эта функция полезна при необходимости низкоуровневого доступа к открытому потоку, например для вызова функции fstat применительно к этому потоку.

Можно создать новый поток файла на основе дескриптора файла, открытого только для чтения, применив функцию fdopen. По существу, эта функция предоставляет буферы stdio для уже открытого файлового дескриптора, это может быть самый легкий вариант объяснения ее назначения.

Функция fdopen действует так же, как функция fopen, но в отличие от имени файла она принимает в качестве параметра низкоуровневый дескриптор файла. Это может пригодиться, если вы используете вызов open для создания файла, может быть для более тонкого управления правами доступа, но хотите применить поток для записи в файл. Параметр mode такой же, как у функции fopen и должен быть совместим с режимами доступа к файлу, установленными при первоначальном открытии файла. Функция fdopen возвращает новый файловый поток или NULL в случае неудачного завершения.

Ведение файлов и каталогов

Стандартные библиотеки и системные вызовы обеспечивают полный контроль над созданием и ведением файлов и каталогов.

chmod

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

Далее приведена синтаксическая запись вызова:

#include

int chmod(const char *path, mode_t mode);

Права доступа к файлу, заданному параметром path, изменяются в соответствии со значением параметра mode. Режим файла mode задается как в системном вызове open с помощью поразрядной операции OR, формирующей требуемые права доступа. Если программе не даны соответствующие полномочия, только владелец файла и суперпользователь могут изменять права доступа к файлу.

chown

Суперпользователь может изменить владельца файла с помощью системного вызова chown.

#include #include

int chown(const char *path, uid_t owner, gid_t group);

В вызове применяются числовые значения идентификаторов (ID) нового пользователя и группы (взятые из системных вызовов getuid и getgid) и системная величина, используемая для ограничения пользователей, имеющих разрешение изменять владельца файла. Владелец и группа файла изменяются, если заданы соответствующие полномочия.

Примечание

Стандарт POSIX в действительности допускает существование систем, в которых несуперпользователи могут изменять права владения файлом. Все «правильные» с точки зрения POSIX системы не допускают этого, но строго говоря, это расширение стандарта (в FIPS 151-2). Все виды систем, с которыми мы имеем дело в этой книге, подчиняются спецификации XSI (X/Open System Interface) и соблюдают на деле правила владения.

unlink, link и symlink

С помощью вызова unlink вы можете удалить файл.

Системный вызов unlink удаляет запись о файле в каталоге и уменьшает на единицу счетчик ссылок на файл. Он возвращает 0, если удаление ссылки прошло успешно, и -1 в случае ошибки. Для выполнения вызова у вас должны быть права на запись и выполнение в каталоге, хранящем ссылку на файл.

#include

int unlink(const char *path);

int link(const char *path1, const char *path2);

int symlink(const char *path1, const char *path2);

Если счетчик становится равен нулю и файл не открыт ни в одном процессе, он удаляется. В действительности элемент каталога всегда удаляется немедленно, а место, занятое содержимым файла, не очищается до тех пор, пока последний процесс (если таковой существует) не закроет файл. Этот вызов использует программа rm. Дополнительные ссылки, предоставляющие альтернативные имена файла, обычно создаются программой ln. Вы можете программно создать новые ссылки на файл с помощью системного вызова link.

Примечание

Создание файла с помощью вызова open и последующее обращение к unlink для этого файла – трюк, применяемый некоторыми программистами для создания временных или транзитных файлов. Эти файлы доступны программе, только пока они открыты; и будут удалены автоматически, когда программа завершится, и файлы будут закрыты.

Системный вызов link создает новую ссылку на существующий файл path1. Новый элемент каталога задается в path2. Символические ссылки можно создавать аналогичным образом с помощью системного вызова symlink. Имейте в виду, что символические ссылки на файл не увеличивают значение счетчика ссылок и таким образом, в отличие от обычных (жестких) ссылок, не мешают удалению файла.

mkdir и rmdir

Вы можете создавать и удалять каталоги, применяя системные вызовы mkdir и rmdir.

#include #include

int mkdir(const char *path, mode_t mode);

Системный вызов mkdir используется для создания каталогов и эквивалентен программе mkdir. Вызов mkdir формирует новый каталог с именем, указанным в параметре path. Права доступа к каталогу передаются в параметре mode и задаются как опция о O_CREAT в системном вызове open и также зависят от переменной umask.

#include

int rmdir(const char *path);

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

chdir и getcwd

Программа может перемещаться по каталогам во многом так же, как пользователь перемещается по файловой системе. Как вы применяете в командной оболочке команду cd для смены каталога, так и программа может использовать системный вызов chdir.

#include

int chdir(const char *path);

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

#include

char *getcwd(char *buf, size_t size);

Функция getcwd записывает имя текущего каталога в заданный буфер buf. Она возвращает NULL, если имя каталога превысит размер буфера (ошибка ERANGE), заданный в параметре size. В случае успешного завершения она возвращает buf.

Функция getcwd может также вернуть значение NULL, если во время выполнения программы каталог удален (EINVAL) или изменились его права доступа (EACCESS).

Просмотр каталогов

Широко распространенная проблема систем Linux – просмотр каталогов, т.е. определение файлов, размещенных в конкретном каталоге. В программах командной оболочки она решается легко – просто скомандуйте оболочке выполнить подстановку в выражении с метасимволами. В прошлом в разных вариантах UNIX был разрешен программный доступ к низкоуровневой структуре файловой системы. Вы все еще можете открывать каталог как обычный файл и непосредственно считывать элементы каталога, но разные структуры файловых систем и реализации сделали такой подход непереносимым с машины на машину. Был разработан стандартный комплект библиотечных функций, существенно упрощающий просмотр каталогов.

Функции работы с каталогами объявлены в заголовочном файле dirent.h. В них используется структура DIR как основа обработки каталогов. Указатель на эту структуру, называемый потоком каталога (DIR*), действует во многом так же, как действует поток файла (FILE*) при работе с обычным файлом. Элементы каталога возвращаются в структурах dirent, также объявленных в файле dirent.h, поскольку никому не следует изменять поля непосредственно в структуре DIR.

Мы рассмотрим следующие функции:

opendir, closedir;

readdir;

telldir;

seekdir;

closedir.

opendir

Функция opendir открывает каталог и формирует поток каталога. Если она завершается успешно, то возвращает указатель на структуру DIR, которая будет использоваться для чтения элементов каталога.

#include

#include

DIR *opendir(const char *name);

В случае неудачи функция opendir возвращает пустой указатель. Имейте в виду, что для доступа к самому каталогу поток каталога использует низкоуровневый дескриптор файла, поэтому opendir может дать сбой, если открыто слишком много файлов.

readdir

Функция readdir возвращает указатель на структуру, содержащую следующий элемент каталога в потоке каталога dirp. Успешные вызовы readdir возвращают следующие элементы каталогов. При возникновении ошибки и в конце каталога readdir возвращает NULL. Системы, удовлетворяющие стандарту POSIX, возвращая NULL, не меняют переменную errno в случае достижения конца каталога и устанавливают ее значение, если обнаружена ошибка.

#include

#include

struct dirent *readdir(DIR *dirp);

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

В состав структуры dirent, содержащей реквизиты элемента каталога, входят следующие компоненты.

ino_t d_ino – индекс файла;

char d_name[] – имя файла.

Для выяснения других реквизитов файла в каталоге вам необходимо вызвать stat, который мы обсуждали ранее.

telldir

Функция telldir возвращает значение, регистрирующее текущую позицию в потоке каталога. Вы можете использовать ее в последующих вызовах функции seekdir для переустановки просмотра каталога, начиная с текущей позиции.

#include

#include

long int telldir(DIR *dirp);

seekdir

Функция seekdir устанавливает указатель на элемент каталога в потоке каталога, заданном в параметре dirp. Значение параметра loc, применяемого для установки позиции, следует получить из предшествующего вызова функции telldir.

#include

#include

void seekdir (DIR *dirp, long int loc);

closedir

Функция closedir закрывает поток каталога и освобождает ресурсы, выделенные ему. Она возвращает 0 в случае успеха и -1 при наличии ошибки.

#include

#include

int closedir(DIR *dirp);

В приведенной далее программе printdir.c (упражнение 3.4) вы соберете вместе множество функций обработки файлов для создания простого перечня содержимого каталога. Каждый файл представлен отдельной строкой. У каждого подкаталога есть имя, за которым следует слэш, и файлы, содержащиеся в подкаталоге, выводятся с отступом шириной в четыре пробела.

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

Мы могли бы сделать программу более универсальной, принимая в качестве аргумента командной строки начальную точку просмотра.

Для того чтобы познакомиться с методами повышения универсальности программ, посмотрите исходный код таких утилит Linux, как ls и find.

Упражнение 3.4. Программа просмотра каталога

1. Начните с соответствующих заголовочных файлов и функции printdir, которая выводит содержимое текущего каталога. Она будет рекурсивно вызываться для вывода подкаталогов, применяя параметр depth для задания отступа.

#include

#include

#include

#include

#include

#include

void printdir(char *dir, int depth) {

 DIR *dp;

 struct dirent *entry;

 struct stat statbuf;

 if ((dp = opendir(dir)) == NULL) {

  fprintf(stderr, «cannot open directory: %sn», dir);

  return;

 }

 chdir(dir);

 while((entry = readdir(dp)) != NULL) {

  lstat(entry->d_name, &statbuf);

  if (S_ISDIR(statbuf.st_mode)) {

   /* Находит каталог, но игнорирует . и .. */

   if (strcmp(".", entry->d_name) == 0 || strcmp(«..», entry->d_name) == 0)

    continue;

   printf(«%*s%s/n», depth, "", entry->d_name);

   /* Рекурсивный вызов с новый отступом */

   printdir(entry->d_name, depth+4);

  } else printf(«%*s%sn», depth, " ", entry->d_name);

 }

 chdir(«..»);

 closedir(dp);

}

2. Теперь переходите к функции main.

int main() {

 /* Обзор каталога /home */

 printf(«Directory scan of /home:n»);

 printdir(«/home», 0);

 printf(«done.n»);

 exit(0);

}

Программа просматривает исходные каталоги и формирует вывод, похожий на приведенный далее (отредактированный для краткости). Для того чтобы заглянуть в каталоги других пользователей, вам могут понадобиться права доступа суперпользователя.

$ ./printdir

Directory scan of /home:

neil/

    .Xdefaults

    .Xmodmap

    .Xresources

    .bash_history

    .bashrc

    .kde/

        share/

            apps/

                konqueror/

                    dirtree/

                        public_html.desktop

                    toolbar/

                        bookmarks.xml

                        konq_history

                    kdisplay/

                        color-schemes/

    BLP4e/

        Gnu_Public_License

        chapter04/

            argopt.с

            args.с

        chapter03/

            file.out

            mmap.с

            printdir

done.

Как это работает

Большинство операций сосредоточено в функции printdir. После некоторой начальной проверки ошибок с помощью функции opendir, проверяющей наличие каталога, printdir выполняет вызов функции chdir для заданного каталога. До тех пор пока элементы, возвращаемые функцией readdir, не нулевые, программа проверяет, не является ли очередной элемент каталогом. Если нет, она печатает элемент-файл с отступом, равным depth.

Если элемент – каталог, вы встречаетесь с рекурсией. После игнорирования элементов . и .. (текущего и родительского каталогов) функция printdir вызывает саму себя и повторяет весь процесс снова. Как она выбирается из этих повторений? Как только цикл while заканчивается, вызов chdir(«..») возвращает программу вверх по дереву каталогов, и предыдущий перечень можно продолжать. Вызов closedir(dp) гарантирует, что количество открытых потоков каталогов не больше того, которое должно быть.

Для того чтобы составить представление об окружении в системе Linux, обсуждаемом в главе 4, познакомьтесь с одним из способов, повышающих универсальность программы. Рассматриваемая программа ограничена, потому что привязана каталогу /home. Следующие изменения в функции main могли бы превратить эту программу в полезный обозреватель каталогов:

int main(int argc, char* argv[]) {

 char *topdir = ".";

 if (argc >= 2) topdir = argv[1];

 printf(«Directory scan of %sn», topdir);

 printdir(topdir, 0);

 printf(«done.n»);

 exit(0);

}

Три строки изменены и пять добавлено, но это уже универсальная утилита с необязательным параметром, содержащим имя каталога, по умолчанию равным текущему каталогу. Вы можете выполнять ее с помощью следующей командной строки:

$ ./printdir2 /usr/local | more

Вывод будет разбит на страницы, и пользователь сможет листать их. Таким образом, у него появится маленький удобный универсальный обозреватель дерева каталогов. Приложив минимум усилий, вы могли бы добавить статистический показатель использования пробелов, предельную глубину отображения и т.д.

Ошибки

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

Имена констант и варианты ошибок перечислены в заголовочном файле errno.h. К ним относятся следующие:

EPERM – Operation not permitted (операция не разрешена);

ENOENT – No such file or directory (нет такого файла или каталога);

EINTR – Interrupted system call (прерванный системный вызов);

EIO – I/O Error (ошибка ввода/вывода);

EBUSY – Device or resource busy (устройство или ресурс заняты);

EEXIST – File exists (файл существует);

EINVAL – Invalid argument (неверный аргумент);

EMFILE – Too many open files (слишком много открытых файлов);

ENODEV – No such device (нет такого устройства);

EISDIR – Is a directory (это каталог);

ENOTDIR – Isn't a directory (это не каталог).

Есть пара полезных функций, сообщающих об ошибках при их возникновении: strerror и perror.

strerror

Функция strerror преобразует номер ошибки в строку, описывающую тип возникшей ошибки. Она может быть полезна для регистрации условий, вызывающих ошибку.

Далее приведена ее синтаксическая запись:

#include

char *strerror(int errnum);


    Ваша оценка произведения:

Популярные книги за неделю