Число файлов, которые одновременно могут быть открыты программой, ограничено (обычно порядка 20; см.
NOFILE
в
<SYS/param.h>
). Поэтому любая программа, которой предстоит обрабатывать много файлов, должна быть готова неоднократно использовать одни и те же дескрипторы файлов. Системный вызов
close
разрывает связь между именем и дескриптором файла, освобождая дескриптор для использования с некоторым другим файлом. Завершение программы посредством
exit
и возврат из основной программы закрывают все открытые файлы. Вызов системы unlink удаляет файл из файловой системы.
Обработка ошибок:
errno
Обсуждаемые здесь системные вызовы, а по сути все системные вызовы, могут вызывать ошибки. Обычно они сигнализируют об ошибке, возвращая значение -1. Иногда полезно знать, какая именно ошибка произошла, поэтому системные вызовы, когда это приемлемо, оставляют номер ошибки во внешней целой переменной, называемой
errno
. (Значение различных номеров ошибок объясняется во введении к разд. 2 справочного руководства по UNIX.) С помощью
errno
ваша программа может определить, например, чем вызвана неудача при открытии файла — тем, что он не существует, или тем, что у вас нет разрешения на его чтение. Кроме того, есть массив символьных строк
sys_errlist
, индексируемый
errno
, который переводит число в строку, передающую смысл ошибки. Наша версия error использует эти структуры данных:
error(s1, s2) /* print error message and die */
char *s1, *s2;
{
extern int errno, sys_nerr;
extern char *sys_errlist[], *progname;
if (progname)
fprintf(stderr, "%s: ", progname);
fprintf(stderr, s1, s2);
if (errno > 0 && errno < sys_nerr)
fprintf (stderr, " (%s)", sys_errlist[errno]);
fprintf(stderr, "\n");
exit(1);
}
Errno
первоначально равна нулю и всегда должна быть меньше, чем
sys_herr.
Она не становится нулевой вновь при нормальной работе, поэтому вы должны обнулять ее после каждой ошибки, если ваша программа будет продолжать выполняться. Сообщения об ошибках в нашей версии
cp
появляются следующим образом:
$ cp foo bar
cp: can't open foo
(Нет такого файла или каталога)
$ date >foo; chmod 0 foo
Создать нечитаемый файл
$ cp too bar
cp: can't open foo
(В разрешении отказано)
$
Произвольный доступ:
lseek
Файл ввода-вывода обычно последовательный: каждый
read
или
write
занимает место в файле непосредственно после использованного при предыдущем вызове. Однако при необходимости файл может быть прочитан или записан в произвольном порядке. Системный вызов
lseek
позволяет перемещаться по файлу, не осуществляя ни чтения, ни записи:
int fd, origin;
long offset, pos, lseek;
pos = lseek(fd, offset, origin);
Текущая
позиция в файле с дескриптором
fd
перемещается к позиции
offset
, которая отсчитывается относительно места, определяемого
origin
. Последующие процессы чтения или записи будут начинаться с этой позиции.
Origin
может иметь значения 0, 1, 2, задавая тем самым начало отсчета значения
offset
— от начала, от текущей позиции или от конца файла соответственно.
Возвращаемое значение есть новая абсолютная позиция или -1 при ошибке. Например, при добавлении информации в файл нужно дойти до его конца, а затем выполнить запись:
lseek(fd, 0L, 2);
Чтобы вернуться обратно к началу ("перемотать"), необходимо вызвать
lseek(fd, 0L, 0);
Для определения текущей позиции следует выполнить
pos = lseek(fd, 0L, 1);
Обратите внимание на аргумент
0L
: смещение есть длинное целое. ('l' в lseek означает 'long' — длинный, чтобы отличить его от системного вызова
seek
в шестой версии, где используются короткие целые.)
С помощью
lseek
можно обращаться с файлами как с большими массивами, однако при этом время доступа к ним возрастает. Например, следующая функция читает любое число байтов из любого места в файле:
get(fd, pos, buf, n) /* read n bytes from position pos */
int fd, n;
long pos;
char *buf;
{
if (lseek(fd, pos, 0) == -1) /* get to pos */
return -1;
return read(fd, buf, n);
}
Упражнение 7.3
Модифицируйте
readslow
так, чтобы обрабатывать имя файла в качестве аргумента, если оно присутствует. Добавьте
– е
:
$ readslow -е
заставляет
readslow
искать конец входного потока, прежде чем начать чтение. Каковы функции
lseek
при работе с программным каналом?
Упражнение 7.4
Перепишите
efopen
из гл. 6, чтобы вызвать error.
7.2 Файловая система: каталоги
Наша следующая тема — как ориентироваться в иерархии каталогов. При этом мы будем использовать не новые системные вызовы, а лишь несколько старых в новом контексте. В качестве примера приведем функцию
spname
, которая пытается справиться с неверно написанными именами файлов. Функция
n = spname(name, newname);
ищет файл с именем, "достаточно близким" к name. Если такое имя найдено, оно копируется в
newname
. Значение n, возвращаемое
spname
, равно -1, если ничего достаточно близкого не найдено, 0 — при точном совпадении и 1, если была сделана коррекция.