Текст книги "Основы программирования в 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.
#include
int chown(const char *path, uid_t owner, gid_t group);
В вызове применяются числовые значения идентификаторов (ID) нового пользователя и группы (взятые из системных вызовов getuid
и getgid
) и системная величина, используемая для ограничения пользователей, имеющих разрешение изменять владельца файла. Владелец и группа файла изменяются, если заданы соответствующие полномочия.
unlink, link и symlinkПримечание
Стандарт POSIX в действительности допускает существование систем, в которых несуперпользователи могут изменять права владения файлом. Все «правильные» с точки зрения POSIX системы не допускают этого, но строго говоря, это расширение стандарта (в FIPS 151-2). Все виды систем, с которыми мы имеем дело в этой книге, подчиняются спецификации XSI (X/Open System Interface) и соблюдают на деле правила владения.
С помощью вызова 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
.
#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
использует этот системный вызов для выполнения аналогичной работы.
Программа может перемещаться по каталогам во многом так же, как пользователь перемещается по файловой системе. Как вы применяете в командной оболочке команду 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
открывает каталог и формирует поток каталога. Если она завершается успешно, то возвращает указатель на структуру 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
возвращает значение, регистрирующее текущую позицию в потоке каталога. Вы можете использовать ее в последующих вызовах функции seekdir
для переустановки просмотра каталога, начиная с текущей позиции.
#include
#include
long int telldir(DIR *dirp);
Функция seekdir
устанавливает указатель на элемент каталога в потоке каталога, заданном в параметре dirp
. Значение параметра loc
, применяемого для установки позиции, следует получить из предшествующего вызова функции telldir
.
#include
#include
void seekdir (DIR *dirp, long int loc);
Функция 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
преобразует номер ошибки в строку, описывающую тип возникшей ошибки. Она может быть полезна для регистрации условий, вызывающих ошибку.
Далее приведена ее синтаксическая запись:
#include
char *strerror(int errnum);