Программирование в стандарте POSIX

Файловый ввод/вывод

Разбить на страницы
Показывать лекцию целиком

Основные понятия

В стандарте POSIX-2001 предусмотрены две основные группы функций, обслуживающие операции ввода/вывода:

  • функции нижнего уровня, использующие упоминавшиеся ранее целочисленные файловые дескрипторы (эти функции и ассоциирован- ные объекты определены в заголовочном файле <fcntl.h> );
  • функции более высокого уровня, осуществляющие буферизованный ввод/вывод с применением потоков (см. <stdio.h> ).
  • Применение потоков не расширяет базовых возможностей ввода/вывода, поскольку в конечном счете используются функции нижнего уровня, но в отличие от них позволяет приложениям получить дополнительный сервис в виде управляемой буферизации и форматного ввода/вывода.

    Согласно стандарту POSIX-2001, поток – это объект, служащий для доступа к файлам как к упорядоченной последовательности символов.

    Поток представляется структурой типа FILE, с которой ассоциирован соответствующий дескриптор открытого файла . Несколько дескрипторов и/или потоков могут ссылаться на одно описание открытого файла.

    Существенны не только содержимое, но и адрес объекта типа FILE ; копия подобного объекта не обязательно является корректным представлением потока.

    И файловые дескрипторы, и потоки формируются в результате выполнения функций открытия файлов, которые должны предшествовать операциям ввода/вывода. Имеется, однако, три предопределенных потока: стандартный ввод, стандартный вывод и стандартный протокол, открываемые окружением времени выполнения еще перед началом работы C-программ. Для обращения к ним служат указатели на объекты типа FILE с именами, соответственно, stdin, stdout и stderr.

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

    Если файл поддерживает запросы на позиционирование (таковы обычные файлы в противоположность, например, символьным специальным, соответствующим терминалам), то после открытия индикатор текущей позиции устанавливается в начало (на нулевой байт) при условии, что файл не открывали на добавление; в этом случае от реализации зависит, будет ли индикатор указывать на начало или конец файла.

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

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

    Стандартом C99 [5] предусмотрены байтные и широкие символы. Соответственно, в стандарте POSIX-2001 введено понятие ориентации потока, которая может быть широкой или байтной. Задает ориентацию первая после открытия файла   ввода/вывода операция. Если вначале применяется функция ввода/вывода широких символов, поток получает широкую ориентацию; в противном случае – байтную. Сменить ориентацию можно только повторным открытием файла ; применяемые к потоку функции ввода/вывода должны соответствовать его ориентации.

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

    Стандарт POSIX-2001 предусматривает как синхронный, так и асинхронный ввод/вывод.

    Операция асинхронного ввода/вывода сама по себе не способна привести к приостановке процесса ( потока управления) и лишить его возможности использовать процессор. Это означает, что процесс и асинхронная операция ввода/вывода могут выполняться параллельно.

    При синхронном вводе/выводе процесс ( поток управления) приостанавливается до завершения запрошенной операции обмена данными.

    С одним файлом могут параллельно работать несколько процессов, выполняющихся на разных хостах (узлах сети) и использующих разные буфера. Для корректного обслуживания подобной ситуации в стандарте POSIX-2001 определено (в качестве необязательной возможности) понятие синхронизированного ввода/вывода – так называют механизм, повышающий детерминированность и устойчивость средств обмена данными, поэтому приложение может быть уверено, что данные, которыми оно манипулирует, физически присутствуют на устройствах вторичной (массовой, стабильной) памяти и наряду с файлами находятся в целостном состоянии.

    Операция синхронизированного ввода/вывода считается завершенной, когда данные успешно переданы или она диагностирована как неудачная. Различают завершение с целостностью данных и с целостностью файлов.

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

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

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

    Открытие и закрытие файлов

    Как уже указывалось, открытие файла должно предшествовать операциям ввода/вывода, поскольку оно возвращает дескриптор файла или поток, которые используют подобные операции. Для открытия файлов и формирования новых описаний открытых файлов, файловых дескрипторов и потоков служат функции нижнего уровня open() и pipe() (см. листинг 5.1), а также функции буферизованного ввода/вывода, показанные в листинге 5.2.

    #include <fcntl.h>
    int open (const char *path, int oflag, ...);
    #include <unistd.h>
    int pipe (int fildes [2]);
    #include <stdio.h>
    FILE *fopen (const char *restrict path, 
                       const char *restrict mode);
    #include <stdio.h>
    FILE *fdopen (int fildes, 
                          const char *mode);
    #include <stdio.h>
    FILE *freopen (const char *restrict path, const
    char *restrict mode, FILE *restrict stream);

    Функция open() открывает файл с заданным маршрутным именем (первый аргумент, path ), создавая для него описание – новое и, следовательно, не разделяемое с другими процессами. Возвращаемый в качестве результата файловый дескриптор является минимальным из числа не используемых в данный момент текущим процессом (при неудаче возвращается -1 ).

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

    O_RDONLY

    Открыть файл только на чтение.

    O_WRONLY

    Открыть файл только на запись.

    O_RDWR

    Открыть файл на чтение и запись.

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

    O_APPEND

    Перед каждой записью устанавливать индикатор текущей позиции на конец файла.

    O_CREAT

    Если файл существует, данный флаг принимается во внимание только при наличии описываемого далее флага O_EXCL. Если файла нет, он создается от имени текущего пользователя. Обратим внимание, что функция open() имеет переменное число аргументов. При создании файла предполагается, что в качестве третьего аргумента типа mode_t задается режим доступа.

    O_EXCL

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

    O_TRUNC

    Если файл существует, является обычным и успешно открывается с флагами O_RDWR или O_WRONLY, он опустошается (размер устанавливается равным нулю).

    Отметим, что рассмотренная ранее функция creat (path, mode) по определению эквивалентна вызову open (path, O_WRONLY | O_CREAT | O_TRUNC, mode).

    Следующий флаг относится к асинхронному вводу/выводу.

    O_NONBLOCK

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

    При открытии специального файла вызов open() завершается только после того, как устройство оказывается в состоянии готовности. Флаг O_NONBLOCK отменяет эту задержку, однако последующее поведе- ние устройства зависит от реализации.

    Следующая группа флагов обслуживает синхронизированный ввод/вывод.

    O_DSYNC

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

    O_SYNC

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

    O_RSYNC

    Операции чтения должны завершаться на уровне целостности, заданном флагами O_DSYNC или O_SYNC.

    Смысл последнего из стандартизованных флагов будет пояснен при рассмотрении процессов.

    O_NOCTTY

    Если открывается специальный файл, соответствующий терминалу, последний не должен становиться управляющим терминалом процесса. Для создания и открытия канала предназначена функция pipe(). В массиве fildes она возвращает сразу два дескриптора: fildes [0] служит для чтения, fildes [1] – для записи. Данные читаются в том же порядке, в каком были записаны.

    При успешном завершении pipe() возвращает 0, при неудаче – -1.

    Функция fopen() из группы буферизованного ввода/вывода по сути аналогична open(), только вместо файлового дескриптора в качестве результата возвращается указатель на объект, служащий для управления сформированным потоком (в случае неудачи результат равен пустому указателю NULL ).

    Второй аргумент, mode, определяющий допустимые виды операций ввода/вывода, задается как цепочка символов. Он может принимать следующие значения.

    "r"

    Открыть файл на чтение.

    "w"

    Опустошить или создать файл, открыв его на запись.

    "a"

    Открыть или создать файл на запись в конец.

    "r+"

    Открыть файл на изменение (чтение и запись).

    "w+"

    Опустошить или создать файл, открыв его на изменение.

    "a+"

    Открыть или создать файл на изменение с записью в конец.

    (Стандарт языка C позволяет приписывать к перечисленным цепочкам символ 'b', который, впрочем, ни на что не влияет.)

    Если открытый файл не соответствует интерактивному устройству, ассоциированный с ним поток полностью буферизуется.

    Функция fdopen() формирует поток, ассоциированный с дескриптором ранее открытого файла.

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

    Функция freopen() предназначена для ассоциирования существующего потока (третий аргумент, stream ) с заданным файлом (первый аргумент, path ) и разрешенными видами операций ввода/вывода (второй аргумент, mode ).

    В первую очередь freopen() пытается вытолкнуть буфера потока stream и закрыть ассоциированный с ним файл. Неудача данного действия ни на что не влияет.

    Затем, аналогично fopen(), открывается заданный файл, только без формирования нового потока ; результатом служит stream (лишенный, правда, ориентации) или NULL (в случае неудачи).

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

    Для закрытия файлов (точнее, файловых дескрипторов или потоков ) предназначены функции close() и fclose() (см. листинг 5.3).

    #include <unistd.h>
    int close (int fildes);
    #include <stdio.h>
    int fclose (FILE *stream);

    Функция close() освобождает файловый дескриптор fildes, который становится доступным для последующего использования при открытии файлов.

    Когда закрывается последний дескриптор, ссылающийся на описание открытого файла, оно освобождается.

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

    Когда закрывается последний дескриптор, ассоциированный с каналом, все оставшиеся непрочитанными данные теряются.

    Функция close() возвращает 0 в случае успешного завершения и -1 при неудаче.

    Функция fclose() по сути аналогична, только она освобождает поток, выталкивая при этом буфера. Признаком успешного завершения также служит 0, признаком неудачи – константа EOF.

    Приведем примеры использования описанных функций. Сочетание флагов O_CREAT и O_EXCL функции open() позволяет организовать проверку и создание файлов-замков, для которых важен факт существования в одном экземпляре, а не содержимое (см. листинг 5.4).

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <errno.h>
    #define LOCK_FILE "my_lock"
    /* Функция пытается создать файл-замок */
    /* Результат равен 0 в случае успеха, */
    /* 1, если файл уже существует, */
    /* -1 в случае прочих ошибок */
    static int gate (const char *lock_name) {
    	int fd;
    	if ((fd = open (lock_name, O_WRONLY | O_CREAT |
    		O_EXCL, (mode_t) 0)) < 0) {
    		if (errno == EEXIST) {
    			return (1);
    		}
    		return (-1);
    	}
    	return (close (fd));
    }
    int main (void) {
    	int res;
    	if ((res = gate (LOCK_FILE)) > 0) {
    		perror ("Ошибка при создании файла-замка "
    		LOCK_FILE);
    	} else if (res == 1) {
    		fprintf (stderr, "Файл-замок " LOCK_FILE "
    			уже существует\n");
    	}
    	return (res);
    }

    Читателю предлагается выполнить приведенную программу дважды.

    Следующая программа иллюстрирует перенаправление стандартного вывода в файл (см. листинг 5.5). Ее тоже полезно выполнить дважды и затем самостоятельно осмыслить результаты.

    #include <stdio.h>
    #define LOGFILE "my_logfile"
    int main (void) {
    	FILE *fp;
    	printf ("До перенаправления стандартного вывода
    		в файл " LOGFILE "\n");
    	if ((fp = freopen (LOGFILE, "a", stdout)) ==
    		NULL) {
    		perror ("Не удалось перенаправить стандартный
    			вывод в файл " LOGFILE);
    		return (-1);
    	}
    	printf ("После перенаправления стандартного
    		вывода в файл " LOGFILE "\n");
    	if (fclose (fp) == EOF) {
    		perror ("Не удалось закрыть файл " LOGFILE);
    		return (-1);
    	}
    	printf ("После закрытия файла " LOGFILE "\n");
    	return (0);
    }

    Весьма полезной с практической точки зрения является функция создания и открытия временных файлов tmpfile() (см. листинг 5.6).

    #include <stdio.h>
    FILE *tmpfile (void);

    Временный файл открывается на изменение ( w+ ) и автоматически удаляется после закрытия всех ссылок на него.

    Использование функции tmpfile() предпочтительнее генерации "временного" имени с помощью функции tmpnam() и последующего создания файла с этим именем, поскольку в промежутке какой-либо другой процесс может создать одноименный файл.

    Чтение и запись данных

    Чтение данных из файла выполняют функции read() и fread() (см. листинг 5.7).

    #include <unistd.h>
    ssize_t read (int fd, void *buf, size_t nbyte);
    #include <stdio.h>
    size_t fread (void *restrict buf, size_t size,
    	size_t nitems, FILE *restrict stream);

    Функция read() пытается прочитать nbyte байт из файла, ассоциированного с дескриптором fd, и поместить их в буфер buf.

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

    При успешном завершении read() возвращает количество байт, реально прочитанных и помещенных в буфер; это значение может оказаться меньше значения аргумента nbyte, если до конца файла оставалось меньше, чем nbyte байт. Например, если текущая позиция совпадала с концом файла, результат будет равен 0. В случае ошибки возвращается -1.

    Функция буферизованного ввода/вывода   fread() во многом аналогична read(), но число читаемых байт задается как произведение размера одного элемента (аргумент size ) на число элементов (аргумент nitems ), а результатом служит количество успешно прочитанных элементов. В стандарте оговаривается, что элементы читаются побайтно.

    Число элементов, успешно прочитанных функцией fread(), может быть меньше затребованного, только если достигнут конец файла или произошла ошибка чтения. В таком случае fread() устанавливает для потока индикатор ошибки или конца файла, проверить которые позволяют функции feof() и ferror(), соответственно (см. листинг 5.8), возвращая при установленном индикаторе ненулевой результат.

    #include <stdio.h>
    int feof (FILE *stream);
    #include <stdio.h>
    int ferror (FILE *stream);

    Отметим, что использование функции бинарного ввода   fread() ограничивает мобильность приложений, так как результат зависит от размера элементов и порядка байт, поддерживаемого процессором.

    Обратим также внимание на некоторые нюансы синхронного и асинхронного ввода с помощью функции read(). При попытке чтения из пустого канала, не открытого кем-либо на запись, результат равен 0 (как признак конца файла). Если пустой канал открыт кем-либо на запись, при установленном флаге O_NONBLOCK возвращается -1 (как признак ошибки EAGAIN ); при отсутствии флага O_NONBLOCK процесс ( поток управления) блокируется до появления данных в канале. Аналогичным образом устроен ввод из файлов других типов, поддерживающих чтение в асинхронном режиме.

    Содержимое символьных ссылок приходится читать особым образом (хотя бы потому, что обычно функция open() раскрывает их, т. е. открывает указуемый файл). Для этого служит функция readlink() (см. листинг 5.9). Она помещает содержимое ссылки с именем link_name в буфер buf длины buf_size (если буфер мал, остаток содержимого отбрасывается). Результат равен числу помещенных в буфер байт или -1 в случае неудачи.

    #include <unistd.h>
    ssize_t readlink (const char *restrict 
         link_name,	char *restrict buf, 
    	 size_t buf_size);

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

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #define C_TERM "/dev/tty"
    
    int main (void) {
    	char buf [BUFSIZ];
    	int fd;
    	ssize_t line_len;
    	/* Открытие на чтение специального файла, */
    	/* ассоциированного с управляющим терминалом */
    	if ((fd = open (C_TERM, O_RDONLY)) < 0) {
    		perror ("OPEN");
    		return (-1);
    	}
    	/* Ввод с терминала */
    	printf ("Вводите строки\n");
    	while ((line_len = read (fd, buf, BUFSIZ - 1))
    		> 0) {
    		buf [line_len] = '\0';
    		printf ("Вы ввели: %s", buf);
    	}
    	if (line_len == -1) {
    		perror ("READ");
    		close (fd);
    		return (-1);
    	}
    	return (close (fd));
    }

    В качестве примера мобильного использования функции fread(), а также функций feof() и ferror(), рассмотрим программу, подсчитывающую число символов, слов и строк в файле – аргументе командной строки (см. листинг 5.11).

    /* * * * * * * * * * * * * * * * * * * * * */
    /* Подсчет символов, слов и строк в файле */
    /* * * * * * * * * * * * * * * * * * * * * */
    #include <stdio.h>
    int main (int argc, char *argv[]) {
    	long nwords = 0; /* Счетчик слов */
    	long nlines = 0; /* Счетчик строк */
    	long nchars = 0; /* Счетчик символов */
    	FILE *fp = stdin; /* Если файл не задан, */
    	/* читается стандартный ввод */
    	unsigned char buf [BUFSIZ]; /* Буфер для */
    	/* чтения файла */
    	unsigned char *p1; /* Указатель на */
    	/* обрабатываемую часть буфера */
    	size_t nbytes = 0; /* Количество прочитанных,*/
    	/* но не обработанных байт */
    	register int c; /* Обрабатываемый символ */
    	int inword = 0; /* Признак - находимся ли мы */
    	/* внутри слова */
    	if (argc > 2) {
    		fprintf (stderr, "Использование: %s [файл]\n",
    		argv [0]);
    		return (2);
    	}
    	if (argc > 1  (fp = fopen (argv [1], "r")) ==
    		NULL) {
    		perror ("OPEN");
    		fprintf (stderr, "%s: Не могу открыть файл
    			%s\n", argv [0], argv[1]);
    		return (2);
    	}
    	p1 = buf;
    	for (;;) { /* Посимвольная обработка файла */
    		if (nbytes == 0) { /* Нужно прочитать новую порцию */
    			if (feof (fp)) {
    				/* При предыдущем чтении дошли до конца файла */
    				break;
    			}
    			nbytes = fread (p1 = buf, (size_t) 1,
    			sizeof (buf), fp);
    			if (ferror (fp)) {
    				perror ("READ");
    				fprintf (stderr, "%s: Ошибка чтения из
    					файла %s:", argv [0],
    				argc == 1 ? "стандартного ввода" : argv
    				[1]);
    				break;
    			}
    			if (nbytes == 0) {
    				/* В файле не оставалось непрочитанных символов */
    				break;
    			}
    			nchars += nbytes;
    	}
    	c = *p1++; /* Обработка одного символа */
    	nbytes--;
    	if (c > ' ') {
    		if (!inword) {
    			nwords++;
    			inword = 1;
    		}
    		continue;
    	}
    	if (c == '\n') {
    		nlines++;
    	} else if (c != ' '  c != '\t') {
    		continue;
    	}
    	inword = 0;
    	}
    	if (argc > 1) {
    		fclose (fp);
    	}
    	printf ("Файл %s: строк: %ld, слов: %ld,
    		символов: %ld\n",
    	argc == 1 ? "стандартного ввода" : argv [1],
    		nlines, nwords, nchars);
    	return (0);
    }

    Читателю предлагается убрать из цикла проверку feof ( fp ) и оценить, как изменится обработка интерактивного стандартного ввода.

    Для иллюстрации использования функции readlink() напишем программу, выдающую на стандартный вывод содержимое символьных ссылок, имена которых заданы в командной строке (см. листинг 5.12).

    #include <unistd.h>
    #include <stdio.h>
    /* Программа выдает на стандартный вывод */
    /* содержимое символьных ссылок - */
    /* аргументов командной строки */
    int main (int argc, char *argv[]) {
    	char buf [BUFSIZ];
    	ssize_t link_len;
    	int err = 0;
    	int i;
    	for (i = 1; i < argc; i++) {
    		if ((link_len = readlink (argv [i], buf,
    			sizeof (buf) - 1)) < 0) {
    			perror ("READLINK");
    			fprintf (stderr, "%s: Не удалось прочитать содержимое символьной ссылки %s\n", argv [0], argv [i]);
    			err = -1;
    			continue;
    		}
    		buf [link_len] = '\0';
    		printf ("Содержимое символьной ссылки %s -> %s\n", argv [i], buf);
    	}
    return (err);
    }

    Запись данных в файл выполняют функции write() и fwrite() (см. листинг 5.13).

    #include <unistd.h>
    ssize_t write (int fildes, const void *buf,
    	size_t nbyte);
    #include <stdio.h>
    size_t fwrite (const void *restrict buf, 
        size_t size,	size_t nitems, 
    	FILE *restrict stream);

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

    При записи в канал, если флаг O_NONBLOCK не установлен, процесс ( поток управления) может быть отложен, но после нормального завершения функция write() вернет nbyte. При установленном флаге O_NONBLOCK поведение зависит от значения nbyte и наличия свободного места в канале. Если nbyte не превосходит константы PIPE_BUF, запишется все или ничего (в последнем случае результат будет равен -1 ). При попытке записать порцию данных большего размера запишется сколько можно или ничего.

    Приведем несколько примеров. Следующая программа (см. листинг 5.14) выводит приветствие на управляющий терминал.

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #define C_TERM "/dev/tty"
       char msg [] = "HELLO !!!\n";
       int main (void) {
       int fd;
       /* Открытие на запись специального файла, 
           ассоциированного с управляющим терминалом */
       if ((fd = open (C_TERM, O_WRONLY)) < 0)
       {
          perror ("OPEN");
          return (-1);
       }
       /* Вывод на терминал */
       if (write (fd, msg, sizeof (msg)) != (ssize_t)
          sizeof (msg)) {
          perror ("WRITE");
          return (1);
       }
       return (close (fd));
    }

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

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    
    #define SOURCE_FILE "prnmyself.c"
    #define C_TERM "/dev/tty"
    
    int main (void) {
    	unsigned char buf [BUFSIZ];
    	int fdr, fdw; /* Дескрипторы для чтения и записи */
    	ssize_t nb;
    	if (((fdr = open (SOURCE_FILE, O_RDONLY)) < 0) ||
    		((fdw = open (C_TERM, O_WRONLY)) < 0)) {
    		perror ("OPEN " SOURCE_FILE " or " C_TERM);
    		return (1);
    	}
    	do {
    		if ((nb = read (fdr, buf, BUFSIZ)) < 0) {
    		perror ("READ");
    		break;
    	}
    	if (write (fdw, buf, nb) != nb) {
    		perror ("WRITE");
    		break;
    	}
    	} while (nb == BUFSIZ);
    	(void) close (fdw);
    	(void) close (fdr);
    	return (0);
    }

    Для буферизованного ввода/вывода байт служат функции fgetc() и fputc(), строки рекомендуется вводить, вызывая функцию fgets(), а выводить с помощью функций fputs() и puts() (см. листинг 5.16).

    #include <stdio.h>
    int fgetc (FILE *stream);
    #include <stdio.h>
    int fputc (int c, FILE *stream);
    #include <stdio.h>
    char *fgets (char *restrict s, int n,
    	FILE *restrict stream);
    #include <stdio.h>
    int fputs (const char *restrict s,
    	FILE *restrict stream);
    #include <stdio.h> 
    int puts (const char *s);

    Описание аналогичных функций для широких символов приведено в листинге 5.17.

    #include <stdio.h>
    #include <wchar.h>
    wint_t fgetwc (FILE *stream);
    #include <stdio.h>
    #include <wchar.h>
    wint_t fputwc (wchar_t wc, FILE *stream);
    #include <stdio.h>
    #include <wchar.h>
    wchar_t *fgetws (wchar_t *restrict ws, int n,
    	FILE *restrict stream);
    #include <stdio.h>
    #include <wchar.h>
    int fputws (const wchar_t *restrict ws,
    	FILE *restrict stream);

    Функция fgetc() пытается прочитать из заданного потока один байт, преобразовать его из типа unsigned char в int и вернуть в качестве результата. В случае ошибки или при достижении конца файла возвращается константа EOF.

    Функция fgets() читает из заданного потока и помещает в буфер с адресом s ( n - 1 ) байт или строку, включая символ перевода строки, или байты, оставшиеся до конца файла, если длина строки или число байт до конца меньше ( n - 1 ). После прочитанных добавляется нулевой байт.

    При нормальном завершении fgets() возвращает s. В случае ошибки или при достижении конца файла возвращается пустой указатель.

    Функция fputc() помещает в поток значение c, предварительно преобразовав его к типу unsigned char. Результатом служит c или EOF.

    Функция fputs() выводит в поток цепочку символов (без завершающего нулевого байта) и возвращает неотрицательное целое число или EOF. Функция puts() делает то же для потока stdout, завершая вывод переводом строки.

    Функции для работы с потоками широкой ориентации устроены аналогично с точностью до замены int на wint_t, char на wchar_t и EOF на WEOF.

    Программа, показанная в листинге 5.18, иллюстрирует использование функций fgets() и fputs(). Читателю предлагается сравнить ее с листингом 5.10.

    #include <stdio.h>
    #include <limits.h>
    
    /* Программа копирует строки со стандартного ввода на стандартный вывод */
    int main (void) {
       char line [LINE_MAX];
       fputs ("Вводите строки\n", stdout);
       while (fgets (line, sizeof (line), stdin) != NULL) {
          if ((fputs ("Вы ввели: ", stdout) == EOF) || (fputs (line, stdout) == EOF)) {
                break;
          }
       }
       return (ferror (stdin) || ferror (stdout));
    }

    Использование функций fgetc() и fputc() иллюстрируется программой, написанной С.В. Самборским (см. листинг 5.19). Она выполняет раскодировку файлов формата base64, применяемого, например, в электронной почте.

    #include <stdio.h>
    #include <assert.h>
    #include <string.h>
    #include <endian.h>
    
    FILE *input=NULL, *output=NULL;
    const char str []
    	="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    unsigned int Prm [256]; /* Таблица перекодировки */
    const int WHITE = 100, ERR = 101, END = 102;
    static void usage (char argv0 []) {
    	fprintf (stderr,"Программа раскодирует файлы
    		формата base64\n");
    	fprintf (stderr,"Использование:\n%s входной_файл
    		выходной_файл\n", argv0);
    	fprintf (stderr,"Файл должен начинаться с
    		первого символа в кодировке base64.\n");
    }
    int main (int argc, char *argv []) {
    	int n;
    	union {
    		unsigned long l;
    		char c[4];
    	} a;
    	{
    	int i;
    	for (i = 0; i < 256; i++) Prm [i] = ERR;
    	Prm [' '] = WHITE; Prm ['\t'] = WHITE;
    	Prm ['\n'] = WHITE; Prm ['\r'] = WHITE;
    	Prm ['='] = END;
    	for (i = 0; i < 64; i++) Prm [(int) str [i]] = i;
    	}
    	if (argc != 3) {
    	usage (argv [0]);
    	return (1);
    	}
    	assert (NULL != (input = fopen (argv [1], "r")));
    	assert (NULL != (output = fopen (argv [2], "w")));
    	for (a.l = 0, n = 0; ; ) { /* Цикл обработки
    		входного файла */
    		int c, b, shift;
    		assert (EOF != (c = fgetc (input)));
    		b = Prm [c];
    		if (WHITE == b) continue;
    		if (END == b) break;
    		if (ERR == b) {
    			fprintf (stderr,"Символ номер %d: %d не
    				входит в кодировку base64\n", n, c);
    			return (1);
    		}
    		n++;
    		assert (b < 64);
    		shift = 6 * (4 - n % 4);
    		if (shift != 24) b = b << shift;
    		a.l += b;
    		if (0 == n % 4) {
    			#if __BYTE_ORDER == __BIG_ENDIAN
    			fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output);
    			#elif __BYTE_ORDER == __LITTLE_ENDIAN
    			fputc (a.c[2], output); fputc (a.c[1],
    			output); fputc (a.c[0], output);
    			#elif __BYTE_ORDER == __PDP_ENDIAN
    			fputc (a.c[0], output); fputc (a.c[3],
    			output); fputc (a.c[2], output);
    			#else
    			#error "Unknown endian"
    			#endif
    			a.l = 0;
    		}
    	}
    	{ /* Обработка остатка входного файла */
    	int tl = (((n - 1) % 4) * 6 + 7) / 8;
    	if (tl == 3) {
    		#if __BYTE_ORDER == __BIG_ENDIAN
    		fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output);
    		#elif __BYTE_ORDER == __LITTLE_ENDIAN
    		fputc (a.c[2], output); fputc (a.c[1], output);
    		fputc (a.c[0], output);
    		#elif __BYTE_ORDER == __PDP_ENDIAN
    		fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output);
    		#else
    		#error "Unknown endian"
    		#endif
    	}
    		if (tl == 2) {
    			#if __BYTE_ORDER == __BIG_ENDIAN
    			fputc (a.c[1], output); fputc (a.c[2],
    			output);
    			#elif __BYTE_ORDER == __LITTLE_ENDIAN
    			fputc (a.c[2], output); fputc (a.c[1],
    			output);
    			#elif __BYTE_ORDER == __PDP_ENDIAN
    			fputc (a.c[0], output); fputc (a.c[3],
    			output);
    			#else
    			#error "Unknown endian"
    			#endif
    		}
    		if (tl == 1) {
    			#if __BYTE_ORDER == __BIG_ENDIAN
    			fputc (a.c[1], output);
    			#elif __BYTE_ORDER == __LITTLE_ENDIAN
    			fputc (a.c[2], output);
    			#elif __BYTE_ORDER == __PDP_ENDIAN
    			fputc (a.c[0], output);
    			#else
    			#error "Unknown endian"
    			#endif
    		}
    	}
    	fclose (input);
    	fclose (output);
    	return (0);
    }

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

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

    decode64: decode64.c:39: main: Assertion 
    `((void *)0) != (input = fopen (argv [1], "r"))' 
    failed.

    может оказаться менее информативным, чем выдача функции perror().

    Управляющие операции с файлами и ассоциированными данными

    К числу управляющих операций с файлами мы отнесем прежде всего позиционирование. Индикатор текущей позиции может быть опрошен или передвинут при помощи функции нижнего уровня lseek(), а также функций буферизованного ввода/вывода   fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind() (см. листинг 5.20).

    #include <unistd.h>
    off_t lseek (int fildes, off_t offset, 
         int whence);
    #include <stdio.h>
    int fseek (FILE *stream, long offset, 
         int whence);
    long ftell (FILE *stream);
    off_t ftello (FILE *stream);
    int fgetpos (FILE *restrict stream,
    	fpos_t *restrict pos);
    int fsetpos (FILE *stream, const fpos_t *pos);
    void rewind (FILE *stream);

    Функция lseek() устанавливает индикатор текущей позиции следующим образом. Сначала, в зависимости от значения третьего аргумента, whence, выбирается точка отсчета: 0, если это значение равно SEEK_SET, текущая позиция для SEEK_CUR и размер файла для SEEK_END. Затем к точке отсчета прибавляется смещение offset (второй аргумент).

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

    Результатом функции lseek() служит новое значение индикатора текущей позиции, отсчитанное в байтах от начала файла. В случае ошибки возвращается ( off_t ) ( -1 ), а текущая позиция остается прежней.

    Функция fseek() по сути аналогична, только в случае нормального завершения возвращается 0. Кроме того, если поток имеет широкую ориентацию, значение аргумента whence должно равняться SEEK_SET, а значение offset – нулю или результату вызова функции ftell() для того же потока.

    Функция ftell() возвращает значение индикатора текущей позиции для заданного потока (в случае ошибки – (long) (-1) ). Функция ftello() эквивалентна ftell() с точностью до типа результата; в новых приложениях рекомендуется использовать ftello(), так как эта функция применима к большим файлам.

    Функции fgetpos() и fsetpos() являются парными. Первая из них заполняет структуру, на которую указывает аргумент pos, а вторая применяет ее для установки индикатора текущей позиции. Нормальным результатом служит 0.

    Функция rewind(), если пренебречь некоторыми тонкостями, сводится к вызову

    (void) fseek (stream, 0L, SEEK_SET).

    Приведем несколько примеров. В листинге 5.21 показана установка индикатора текущей позиции в начало и конец файла, а также его (индикатора) приращение.

    (void) lseek (fildes, (off_t) 0, SEEK_SET);
    (void) lseek (fildes, (off_t) 0, SEEK_END);
    (void) lseek (fildes, inc, SEEK_CUR);

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

    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Набор функций для занесения текстов в файл, */
    /* который составляется из двух частей: */
    /* таблицы длин и смещений текстов от начала */
    /* файла собственно текстов */
    /* */
    /* В начало файла помещается специальный */
    /* элемент таблицы с магическим числом и общим */
    /* количеством текстов */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    #include <stdio.h>
    #include <string.h>
    /* Магическое число файла с текстами */
    #define G_TXT_MAGIC 0x1993
    /* Элемент таблицы длин и смещений */
    typedef struct {
    	unsigned int l_txt; /* Длина текста (без */
    	/* (нулевого байта) */
    	unsigned long off_txt; /* Смещение текста от */
    	/* начала файла */
    } txt_table_elem;
    static FILE *fp = NULL;/* Указатель на поток */
    /* файла с текстами */
    static unsigned long max_n_txt; /* Общее число */
    /* текстов в файле */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Функция для инициализации набора. */
    /* n_txts - максимальное число добавляемых текстов */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    int g_init_add_txt (const int argc, char *argv [],
    	const unsigned long n_txts) {
    	char *path; /* Имя файла, куда нужно поместить
    		тексты */
    	int magic; /* Магическое число файла с текстами */
    	txt_table_elem tte;
    	unsigned int i;
    	if (argc != 2) {
    	fprintf (stderr, "Использование: %s
    		файл_для_текстов\n", argv [0]);
    	return (-1);
    }
    path = argv [1];
    /* Аккуратно откроем файл с текстами */
    /* Если он уже есть и в нем правильное */
    /* магическое число, */
    /* будем добавлять тексты. */
    /* В противном случае создадим и */
    /* инициализируем файл */
    if (((fp = fopen (path, "r+")) != NULL)  (fread (magic, sizeof (unsigned int), 1, fp) == 1)  (magic == G_TXT_MAGIC)) {
    	/* Перед нами - наш файл */
    	/* Проверим, не превышает ли заказанная */
    	/* верхняя граница существующую */
    	if (fread (max_n_txt, sizeof (unsigned long), 1, fp) != 1) {
    		fprintf (stderr, "Не удается прочитать
    			информацию из файла %s\n", path);
    		return (-1);
    	}
    if (n_txts > max_n_txt) {
    	fprintf (stderr, "***** Новая верхняя
    		граница числа сообщений %lu больше
    			существующей %lu\n", n_txts, max_n_txt);
    }
    } else {
    	/* Файла нет или он не наш */
    	(void) fclose (fp);
    	if ((fp = fopen (path, "w+")) == NULL) {
    		fprintf (stderr, "Не удается открыть файл
    			%s\n", path);
    		return (-1);
    	}
    tte.l_txt = magic = G_TXT_MAGIC;
    tte.off_txt = max_n_txt = n_txts;
    if (fwrite (tte, sizeof (txt_table_elem), 1, fp) != 1) {
    	fprintf (stderr, "Не удается записать
    	информацию в файл %s\n", path);
    	return (-1);
    }
    /* Пропишем нулями индексную таблицу */
    /* Заодно конец файла окажется в будущем */
    /* начале текстов */
    tte.l_txt = 0;
    tte.off_txt = 0;
    for (i = 0; i < max_n_txt; i++) {
    	if (fwrite (tte, sizeof (txt_table_elem), 1, fp) != 1) {
    		fprintf (stderr, "Не удается записать информацию
    			в файл %s\n", path);
    		return (-1);
    	}
    }
    } /* if - существует ли файл с текстами */
    return 0;
    }
    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Функция для добавления одного текста */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    int g_add_txt (const unsigned long n_t, const char *txt) {
    	unsigned int l; /* Длина текста txt */
    	txt_table_elem tte;
    	if (n_t >= max_n_txt) {
    		fprintf (stderr, "Номер текста: %lu должен
    			быть меньше: %lu\n", n_t, max_n_txt);
    	return (-1);
    	}
    l = strlen (txt);
    tte.l_txt = l;
    if (fseek (fp, 0L, SEEK_END)) {
    		fprintf (stderr, "Ошибка позиционирования при
    			добавлении текста номер %lu\n", n_t);
    		return (-1);
    }
    tte.off_txt = ftell (fp);
    if (fseek (fp, (n_t + 1) * sizeof (txt_table_elem), SEEK_SET)) {
    	fprintf (stderr, "Ошибка позиционирования при
    		добавлении текста номер %lu\n", n_t);
    	return (-1);
    }
    if (fwrite (tte, sizeof (tte), 1, fp) != 1) {
    	fprintf (stderr, "Ошибка записи при добавлении
    		текста номер %lu\n", n_t);
    	return (-1);
    }
    if (fseek (fp, tte.off_txt, SEEK_SET)) {
    	fprintf (stderr, "Ошибка позиционирования при
    		добавлении текста номер %lu\n", n_t);
    	return (-1);
    }
    if (fwrite (txt, sizeof (char), l, fp) != l) {
    	fprintf (stderr, "Ошибка записи при добавлении
    		текста номер %lu\n", n_t);
    	return (-1);
    }
    return 0;
    }
    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Функция для завершения добавления текстов */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    int g_term_add_txt () {
    	return (fclose (fp));
    }
    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Главная программа, вызывающая определенные */
    /* выше функции */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    #define MAX_TXTS 10240
    
    int main (int argc, char *argv[]) {
    	if (g_init_add_txt (argc, argv, MAX_TXTS) ||
    		g_add_txt (0, "Reference to section number %d in %s\n") ||
    		g_add_txt (1, "Data .init section in %s\n")) {
    		(void) g_term_add_txt ();
    		return (-1);
    		}
    	return (g_term_add_txt ());
    }

    Функция fcntl() предназначена для выполнения разнообразных управляющих операций над открытым файлом (см. листинг 5.23).

    #include <fcntl.h>
    int fcntl (int fildes, int cmd, ...);

    Аргумент fildes задает дескриптор открытого файла, cmd – управляющую команду, дополнительные данные для которой могут быть переданы в качестве третьего (необязательного) аргумента arg (обычно имеющего тип int ). В случае успешного завершения возвращаемое значение естественным образом зависит от команды; при неудаче всегда возвращается -1.

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

    F_DUPFD

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

    F_GETFL

    Вернуть флаги статуса и режим доступа к файлу (см. выше описание функции open() ). Для выделения режима доступа из возвращаемого значения предоставляется маска O_ACCMODE.

    F_SETFL

    Установить флаги статуса файла в соответствии со значением третьего аргумента arg.

    Приведем пример использования описанных команд функции fcntl(). Перенаправить стандартный вывод в файл, а затем стандартный протокол на стандартный вывод позволяет программа, показанная в листинге 5.24 (читателю рекомендуется сопоставить ее с программой перенаправления стандартного вывода с помощью функции freopen(), см. выше листинг 5.5). Кроме того, в ней демонстрируется изменение набора флагов статуса файла, ассоциированного с дескриптором.

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <assert.h>
    #define LOGFILE "my_logfile"
    
    int main (void) {
    	int fd;
    	int flags;
    	assert ((fd = open (LOGFILE, O_WRONLY | O_CREAT
    		| O_APPEND, S_IRWXU)) > 2);
    	printf ("До перенаправления стандартного вывода
    		в файл " LOGFILE "\n");
    	close (1);
    	assert (fcntl (fd, F_DUPFD, 1) == 1);
    	close (fd);
    	printf ("После перенаправления стандартного
    		вывода в файл " LOGFILE "\n");
    	/* Добавим флаг обеспечения целостности файла */
    	/* при записи */
    	assert ((flags = fcntl (1, F_GETFL, 0)) != -1);
    	assert (fcntl (1, F_SETFL, flags | O_SYNC) != -1);
    	fprintf (stderr, "До перенаправления стандартного
    		протокола на стандартный вывод\n");
    	close (2);
    	assert (fcntl (1, F_DUPFD, 2) == 2);
    	fprintf (stderr, "После перенаправления
    		стандартного протокола на стандартный вывод\n");
    	close (1);
    	close (2);
    	return (0);
    }

    Команды F_GETFD и F_SETFD функции fcntl() предназначены, соответственно, для опроса и изменения флагов, ассоциированных с заданным файловым дескриптором (а не с файлом). В стандарте упомянут один такой флаг – FD_CLOEXEC, предписывающий закрывать дескриптор при смене программы процесса.

    Особый класс управляющих операций с файлами, имеющих свою систему понятий, составляют блокировки, хотя и они оформляются как команды функции fcntl().

    Назначение механизма блокировок – дать возможность процессам ( потокам управления), одновременно обрабатывающим одни и те же данные, синхронизировать свою работу. В стандарте POSIX-2001 представлены только так называемые рекомендательные блокировки, которые взаимодействуют исключительно с другими блокировками и не препятствуют операциям ввода/вывода. Это значит, что для достижения синхронизации процессы должны окружать операции ввода/вывода разделяемых данных операциями установки и снятия соответствующей блокировки.

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

    Блокировка на чтение используется для того, чтобы ограничить доступ к сегментам: если сегмент заблокирован на чтение, то другие процессы также могут заблокировать на чтение весь сегмент или его часть, однако никакие сегменты, заблокированные на запись, с ним не пересекаются.

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

    Блокировка сегмента описывается структурой flock, которая, согласно стандарту, должна содержать по крайней мере следующие поля:

    short l_type /* Тип блокировки: */
       /* F_RDLCK (на чтение) */
       /* F_WRLCK (на запись), */
       /* F_UNLCK (снятие блокировки) */
    short l_whence/* Точка отсчета для смещения */
       /* l_start: SEEK_SET, SEEK_CUR */
       /* или SEEK_END */
    off_t l_start /* Смещение в байтах начала */
       /* блокируемого сегмента */
       /* относительно точки отсчета */
    off_t l_len /* Размер блокируемого сегмента. */
       /* 0 означает блокировку до конца файла. */
    pid_t l_pid /* Идентификатор процесса,*/
       /* установившего блокировку; */
       /* (возвращается по команде F_GETLK, */
       /* см. далее) */

    Отметим, что блокируемый сегмент может выходить за конец, но не за начало файла. Длину сегмента разрешается задавать отрицательным числом, если тип off_t допускает такую возможность; в таком случае смещение начала блокируемого сегмента относительно точки отсчета равно l_start+l_len, а смещение конца – l_start-1.

    Приведем несколько примеров заполнения структуры flock (см. листинг 5.25).

    #include <fcntl.h>
    struct flock lck;
    . . .
    lck.l_type = F_RDLCK; 
        /* Блокировка на чтение */
        /* всего файла */
    lck.l_whence = SEEK_SET;
    lck.l_start = 0;
    lck.l_len = 0;

    Установка блокировки осуществляется управляющими командами F_SETLK и F_SETLKW функции fcntl() (см. листинг 5.26).

    if (fcntl (fd, F_SETLK, lck) != -1) ...
    if (fcntl (fd, F_SETLKW, lck) != -1) ...

    Если блокировка не может быть установлена, выполнение команды F_SETLK немедленно завершается, и возвращается -1. Команда F_SETLKW отличается только тем, что в аналогичной ситуации процесс переходит в состояние ожидания до тех пор, пока нужный сегмент файла не будет разблокирован.

    Для снятия блокировки можно воспользоваться командами F_SETLK или F_SETLKW. Для этого значение поля l_type должно быть установлено равным F_UNLCK.

    Получить характеристики блокировки, мешающей установить новую, позволяет управляющая команда F_GETLK (новая блокировка задается структурой, на которую при обращении к fcntl() указывает третий аргумент arg ). Если запрашиваемую блокировку установить нельзя, информация о первой помещается в ту же структуру; в частности, будет задано значение поля l_pid – оно идентифицирует процесс, установивший блокировку. (Естественно, значение поля l_whence будет установлено равным SEEK_SET.) Если нет помех для создания нужной блокировки, полю l_type присваивается значение F_UNLCK, а остальные поля в структуре не изменяются.

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

    Приведем пример двух программ, первая из которых (назовем ее set_locks ) устанавливает блокировки нескольких сегментов файла и засыпает на некоторое время (см. листинг 5.27), а вторая ( test_locks, см. листинг 5.28), выполняющаяся, разумеется, параллельно, в рамках другого процесса, получает и выводит информацию о заблокированных сегментах того же файла (см. листинг 5.29).

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <assert.h>
    
    #define LOCKFILE "my_lockfile"
    
    /* Программа устанавливает несколько блокировок */
    /* на файл LOCKFILE */
    int main (void) {
    	int fd;
    	struct flock lck;
    	assert ((fd = open (LOCKFILE, O_RDWR | O_CREAT |
    		O_TRUNC, S_IRWXU)) != -1);
    	/* Установим блокировку на запись на весь файл */
    	lck.l_type = F_WRLCK;
    	lck.l_whence = SEEK_SET;
    	lck.l_start = (off_t) 0;
    	lck.l_len = (off_t) 0;
    	if (fcntl (fd, F_SETLK, lck) == -1) {
    		perror ("FCNTL-F_SETLK-1");
    		close (fd);
    		return (-1);
    	}
    	/* Сделаем размер файла ненулевым */
    	if (lseek (fd, (off_t) 1024, SEEK_SET) == -1) {
    		perror ("LSEEK");
    		close (fd);
    		return (-1);
    	}
    	if (write (fd, lck, sizeof (lck)) != sizeof (lck)) {
    		perror ("WRITE");
    		close (fd);
    		return (-1);
    	}
    	/* Снимем блокировку в середине файла */
    	lck.l_type = F_UNLCK;
    	lck.l_whence = SEEK_SET;
    	lck.l_start = (off_t) 512;
    	lck.l_len = (off_t) sizeof (lck);
    	if (fcntl (fd, F_SETLK, lck) == -1) {
    		perror ("FCNTL-F_SETLK-2");
    		close (fd);
    		return (-1);
    	}
    	/* Установим блокировку на чтение в конце файла */
    	lck.l_type = F_RDLCK;
    	lck.l_whence = SEEK_END;
    	lck.l_start = (off_t) -sizeof (lck);
    	lck.l_len = (off_t) sizeof (lck);
    	if (fcntl (fd, F_SETLK, lck) == -1) {
    		perror ("FCNTL-F_SETLK-2");
    		close (fd);
    		return (-1);
    	}
    	sleep (10);
    	return (close (fd));
    }
    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <assert.h>
    
    #define LOCKFILE "my_lockfile"
    
    /* Программа выявляет блокировки, установленные */
    /* на файл LOCKFILE */
    int main (void) {
    	int fd;
    	struct flock lck;
    	assert ((fd = open (LOCKFILE, O_WRONLY)) != -1);
    	(void) printf ("ид-р проц. тип начало длина\n");
    	/* Начнем с попытки установить блокировку на */
    	/* весь файл */
    	lck.l_whence = SEEK_SET;
    	lck.l_start = 0;
    	lck.l_len = 0;
    	do {
    		lck.l_type = F_WRLCK;
    		(void) fcntl (fd, F_GETLK, lck);
    		if (lck.l_type != F_UNLCK) {
    			(void) printf ("%9d %3c %7ld %5ld\n",
    				lck.l_pid,
    				(lck.l_type == F_WRLCK) ? 'W' : 'R',
    				lck.l_start, lck.l_len);
    				/* Если эта блокировка покрывает остаток файла, */
    				/* нет нужды выявлять другие блокировки */
    		if (lck.l_len == 0) break;
    		/* Иначе поищем новую блокировку после найденной */
    		lck.l_start += lck.l_len;
    	}
    	while (lck.l_type != F_UNLCK);
    	return (close (fd));
    }
    ид-р проц. тип начало длина
    31174 	   W 	0     512
    31174 	   W 	528   496
    31174 	   R 	1024  16
    31174 	   W 	1040  0

    Отметим, что блокировка на чтение расщепила блокировку на запись, первоначально покрывавшую весь файл.

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

    Функции setbuf(), setvbuf( ) и fflush() выполняют управляющие операции с буферами потоков (см. листинг 5.30).

    #include <stdio.h>
    void setbuf (FILE *restrict stream,
       char *restrict buf);
    #include <stdio.h>
    int setvbuf (FILE *restrict stream,
       char *restrict buf, int type, size_t size);
    #include <stdio.h>
    int fflush (FILE *stream);

    Функция setvbuf(), которую можно использовать после открытия файла, но до первой операции ввода/вывода, устанавливает режим буферизации в соответствии со значением своего третьего аргумента, type:

    _IOFBF – полная буферизация;

    _IOLBF – построчная буферизация;

    _IONBF – отсутствие буферизации.

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

    Функцию setbuf() можно считать частным случаем setvbuf(). С точностью до возвращаемого значения вызов setbuf (stream, NULL) эквивалентен setvbuf (stream, NULL, _IONBF, BUFSIZ) (отмена буферизации); если же значение buf не равно NULL, вызов setbuf (stream, buf) сводится к setvbuf (stream, buf, _IOFBF, BUFSIZ) (полная буферизация).

    Функцию setbuf() чаще всего применяют для отмены буферизации стандартного вывода и/или стандартного протокола, выполняя вызовы setbuf (stdout, NULL) и/или setbuf (stderr, NULL).

    Использование функций setbuf() и setvbuf() требует известной аккуратности. Типичная ошибка – указание в качестве аргумента buf автоматического массива, определенного внутри блока, и продолжение работы с потоком после выхода из этого блока.

    Функция fflush() выталкивает буфера, помещая в файл предназначенные для записи, но еще не записанные данные. Если в качестве аргумента stream задан пустой указатель, выталкиваются все буфера всех потоков.

    Вызов fflush() – необходимый элемент завершения транзакций, но он полезен и применительно к стандартному выводу (протоколу), если нужно выдать приглашение для пользовательского ввода на экран, а не в буфер (см. листинг 5.31).

    char name [LINE_MAX];
    (void) printf ("Введите Ваше имя: "); 
    (void) fflush (stdout); (void) fgets (name, 
       sizeof (name), stdin);
    Страницы:

    Основные понятия

    В стандарте POSIX-2001 предусмотрены две основные группы функций, обслуживающие операции ввода/вывода:

  • функции нижнего уровня, использующие упоминавшиеся ранее целочисленные файловые дескрипторы (эти функции и ассоциирован- ные объекты определены в заголовочном файле <fcntl.h> );
  • функции более высокого уровня, осуществляющие буферизованный ввод/вывод с применением потоков (см. <stdio.h> ).
  • Применение потоков не расширяет базовых возможностей ввода/вывода, поскольку в конечном счете используются функции нижнего уровня, но в отличие от них позволяет приложениям получить дополнительный сервис в виде управляемой буферизации и форматного ввода/вывода.

    Согласно стандарту POSIX-2001, поток – это объект, служащий для доступа к файлам как к упорядоченной последовательности символов.

    Поток представляется структурой типа FILE, с которой ассоциирован соответствующий дескриптор открытого файла . Несколько дескрипторов и/или потоков могут ссылаться на одно описание открытого файла.

    Существенны не только содержимое, но и адрес объекта типа FILE ; копия подобного объекта не обязательно является корректным представлением потока.

    И файловые дескрипторы, и потоки формируются в результате выполнения функций открытия файлов, которые должны предшествовать операциям ввода/вывода. Имеется, однако, три предопределенных потока: стандартный ввод, стандартный вывод и стандартный протокол, открываемые окружением времени выполнения еще перед началом работы C-программ. Для обращения к ним служат указатели на объекты типа FILE с именами, соответственно, stdin, stdout и stderr.

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

    Если файл поддерживает запросы на позиционирование (таковы обычные файлы в противоположность, например, символьным специальным, соответствующим терминалам), то после открытия индикатор текущей позиции устанавливается в начало (на нулевой байт) при условии, что файл не открывали на добавление; в этом случае от реализации зависит, будет ли индикатор указывать на начало или конец файла.

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

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

    Стандартом C99 [5] предусмотрены байтные и широкие символы. Соответственно, в стандарте POSIX-2001 введено понятие ориентации потока, которая может быть широкой или байтной. Задает ориентацию первая после открытия файла   ввода/вывода операция. Если вначале применяется функция ввода/вывода широких символов, поток получает широкую ориентацию; в противном случае – байтную. Сменить ориентацию можно только повторным открытием файла ; применяемые к потоку функции ввода/вывода должны соответствовать его ориентации.

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

    Стандарт POSIX-2001 предусматривает как синхронный, так и асинхронный ввод/вывод.

    Операция асинхронного ввода/вывода сама по себе не способна привести к приостановке процесса ( потока управления) и лишить его возможности использовать процессор. Это означает, что процесс и асинхронная операция ввода/вывода могут выполняться параллельно.

    При синхронном вводе/выводе процесс ( поток управления) приостанавливается до завершения запрошенной операции обмена данными.

    С одним файлом могут параллельно работать несколько процессов, выполняющихся на разных хостах (узлах сети) и использующих разные буфера. Для корректного обслуживания подобной ситуации в стандарте POSIX-2001 определено (в качестве необязательной возможности) понятие синхронизированного ввода/вывода – так называют механизм, повышающий детерминированность и устойчивость средств обмена данными, поэтому приложение может быть уверено, что данные, которыми оно манипулирует, физически присутствуют на устройствах вторичной (массовой, стабильной) памяти и наряду с файлами находятся в целостном состоянии.

    Операция синхронизированного ввода/вывода считается завершенной, когда данные успешно переданы или она диагностирована как неудачная. Различают завершение с целостностью данных и с целостностью файлов.

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

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

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

    Открытие и закрытие файлов

    Как уже указывалось, открытие файла должно предшествовать операциям ввода/вывода, поскольку оно возвращает дескриптор файла или поток, которые используют подобные операции. Для открытия файлов и формирования новых описаний открытых файлов, файловых дескрипторов и потоков служат функции нижнего уровня open() и pipe() (см. листинг 5.1), а также функции буферизованного ввода/вывода, показанные в листинге 5.2.

    #include <fcntl.h>
    int open (const char *path, int oflag, ...);
    #include <unistd.h>
    int pipe (int fildes [2]);
    #include <stdio.h>
    FILE *fopen (const char *restrict path, 
                       const char *restrict mode);
    #include <stdio.h>
    FILE *fdopen (int fildes, 
                          const char *mode);
    #include <stdio.h>
    FILE *freopen (const char *restrict path, const
    char *restrict mode, FILE *restrict stream);

    Функция open() открывает файл с заданным маршрутным именем (первый аргумент, path ), создавая для него описание – новое и, следовательно, не разделяемое с другими процессами. Возвращаемый в качестве результата файловый дескриптор является минимальным из числа не используемых в данный момент текущим процессом (при неудаче возвращается -1 ).

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

    O_RDONLY

    Открыть файл только на чтение.

    O_WRONLY

    Открыть файл только на запись.

    O_RDWR

    Открыть файл на чтение и запись.

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

    O_APPEND

    Перед каждой записью устанавливать индикатор текущей позиции на конец файла.

    O_CREAT

    Если файл существует, данный флаг принимается во внимание только при наличии описываемого далее флага O_EXCL. Если файла нет, он создается от имени текущего пользователя. Обратим внимание, что функция open() имеет переменное число аргументов. При создании файла предполагается, что в качестве третьего аргумента типа mode_t задается режим доступа.

    O_EXCL

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

    O_TRUNC

    Если файл существует, является обычным и успешно открывается с флагами O_RDWR или O_WRONLY, он опустошается (размер устанавливается равным нулю).

    Отметим, что рассмотренная ранее функция creat (path, mode) по определению эквивалентна вызову open (path, O_WRONLY | O_CREAT | O_TRUNC, mode).

    Следующий флаг относится к асинхронному вводу/выводу.

    O_NONBLOCK

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

    При открытии специального файла вызов open() завершается только после того, как устройство оказывается в состоянии готовности. Флаг O_NONBLOCK отменяет эту задержку, однако последующее поведе- ние устройства зависит от реализации.

    Следующая группа флагов обслуживает синхронизированный ввод/вывод.

    O_DSYNC

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

    O_SYNC

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

    O_RSYNC

    Операции чтения должны завершаться на уровне целостности, заданном флагами O_DSYNC или O_SYNC.

    Смысл последнего из стандартизованных флагов будет пояснен при рассмотрении процессов.

    O_NOCTTY

    Если открывается специальный файл, соответствующий терминалу, последний не должен становиться управляющим терминалом процесса. Для создания и открытия канала предназначена функция pipe(). В массиве fildes она возвращает сразу два дескриптора: fildes [0] служит для чтения, fildes [1] – для записи. Данные читаются в том же порядке, в каком были записаны.

    При успешном завершении pipe() возвращает 0, при неудаче – -1.

    Функция fopen() из группы буферизованного ввода/вывода по сути аналогична open(), только вместо файлового дескриптора в качестве результата возвращается указатель на объект, служащий для управления сформированным потоком (в случае неудачи результат равен пустому указателю NULL ).

    Второй аргумент, mode, определяющий допустимые виды операций ввода/вывода, задается как цепочка символов. Он может принимать следующие значения.

    "r"

    Открыть файл на чтение.

    "w"

    Опустошить или создать файл, открыв его на запись.

    "a"

    Открыть или создать файл на запись в конец.

    "r+"

    Открыть файл на изменение (чтение и запись).

    "w+"

    Опустошить или создать файл, открыв его на изменение.

    "a+"

    Открыть или создать файл на изменение с записью в конец.

    (Стандарт языка C позволяет приписывать к перечисленным цепочкам символ 'b', который, впрочем, ни на что не влияет.)

    Если открытый файл не соответствует интерактивному устройству, ассоциированный с ним поток полностью буферизуется.

    Функция fdopen() формирует поток, ассоциированный с дескриптором ранее открытого файла.

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

    Функция freopen() предназначена для ассоциирования существующего потока (третий аргумент, stream ) с заданным файлом (первый аргумент, path ) и разрешенными видами операций ввода/вывода (второй аргумент, mode ).

    В первую очередь freopen() пытается вытолкнуть буфера потока stream и закрыть ассоциированный с ним файл. Неудача данного действия ни на что не влияет.

    Затем, аналогично fopen(), открывается заданный файл, только без формирования нового потока ; результатом служит stream (лишенный, правда, ориентации) или NULL (в случае неудачи).

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

    Для закрытия файлов (точнее, файловых дескрипторов или потоков ) предназначены функции close() и fclose() (см. листинг 5.3).

    #include <unistd.h>
    int close (int fildes);
    #include <stdio.h>
    int fclose (FILE *stream);

    Функция close() освобождает файловый дескриптор fildes, который становится доступным для последующего использования при открытии файлов.

    Когда закрывается последний дескриптор, ссылающийся на описание открытого файла, оно освобождается.

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

    Когда закрывается последний дескриптор, ассоциированный с каналом, все оставшиеся непрочитанными данные теряются.

    Функция close() возвращает 0 в случае успешного завершения и -1 при неудаче.

    Функция fclose() по сути аналогична, только она освобождает поток, выталкивая при этом буфера. Признаком успешного завершения также служит 0, признаком неудачи – константа EOF.

    Приведем примеры использования описанных функций. Сочетание флагов O_CREAT и O_EXCL функции open() позволяет организовать проверку и создание файлов-замков, для которых важен факт существования в одном экземпляре, а не содержимое (см. листинг 5.4).

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <errno.h>
    #define LOCK_FILE "my_lock"
    /* Функция пытается создать файл-замок */
    /* Результат равен 0 в случае успеха, */
    /* 1, если файл уже существует, */
    /* -1 в случае прочих ошибок */
    static int gate (const char *lock_name) {
    	int fd;
    	if ((fd = open (lock_name, O_WRONLY | O_CREAT |
    		O_EXCL, (mode_t) 0)) < 0) {
    		if (errno == EEXIST) {
    			return (1);
    		}
    		return (-1);
    	}
    	return (close (fd));
    }
    int main (void) {
    	int res;
    	if ((res = gate (LOCK_FILE)) > 0) {
    		perror ("Ошибка при создании файла-замка "
    		LOCK_FILE);
    	} else if (res == 1) {
    		fprintf (stderr, "Файл-замок " LOCK_FILE "
    			уже существует\n");
    	}
    	return (res);
    }

    Читателю предлагается выполнить приведенную программу дважды.

    Следующая программа иллюстрирует перенаправление стандартного вывода в файл (см. листинг 5.5). Ее тоже полезно выполнить дважды и затем самостоятельно осмыслить результаты.

    #include <stdio.h>
    #define LOGFILE "my_logfile"
    int main (void) {
    	FILE *fp;
    	printf ("До перенаправления стандартного вывода
    		в файл " LOGFILE "\n");
    	if ((fp = freopen (LOGFILE, "a", stdout)) ==
    		NULL) {
    		perror ("Не удалось перенаправить стандартный
    			вывод в файл " LOGFILE);
    		return (-1);
    	}
    	printf ("После перенаправления стандартного
    		вывода в файл " LOGFILE "\n");
    	if (fclose (fp) == EOF) {
    		perror ("Не удалось закрыть файл " LOGFILE);
    		return (-1);
    	}
    	printf ("После закрытия файла " LOGFILE "\n");
    	return (0);
    }

    Весьма полезной с практической точки зрения является функция создания и открытия временных файлов tmpfile() (см. листинг 5.6).

    #include <stdio.h>
    FILE *tmpfile (void);

    Временный файл открывается на изменение ( w+ ) и автоматически удаляется после закрытия всех ссылок на него.

    Использование функции tmpfile() предпочтительнее генерации "временного" имени с помощью функции tmpnam() и последующего создания файла с этим именем, поскольку в промежутке какой-либо другой процесс может создать одноименный файл.

    Чтение и запись данных

    Чтение данных из файла выполняют функции read() и fread() (см. листинг 5.7).

    #include <unistd.h>
    ssize_t read (int fd, void *buf, size_t nbyte);
    #include <stdio.h>
    size_t fread (void *restrict buf, size_t size,
    	size_t nitems, FILE *restrict stream);

    Функция read() пытается прочитать nbyte байт из файла, ассоциированного с дескриптором fd, и поместить их в буфер buf.

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

    При успешном завершении read() возвращает количество байт, реально прочитанных и помещенных в буфер; это значение может оказаться меньше значения аргумента nbyte, если до конца файла оставалось меньше, чем nbyte байт. Например, если текущая позиция совпадала с концом файла, результат будет равен 0. В случае ошибки возвращается -1.

    Функция буферизованного ввода/вывода   fread() во многом аналогична read(), но число читаемых байт задается как произведение размера одного элемента (аргумент size ) на число элементов (аргумент nitems ), а результатом служит количество успешно прочитанных элементов. В стандарте оговаривается, что элементы читаются побайтно.

    Число элементов, успешно прочитанных функцией fread(), может быть меньше затребованного, только если достигнут конец файла или произошла ошибка чтения. В таком случае fread() устанавливает для потока индикатор ошибки или конца файла, проверить которые позволяют функции feof() и ferror(), соответственно (см. листинг 5.8), возвращая при установленном индикаторе ненулевой результат.

    #include <stdio.h>
    int feof (FILE *stream);
    #include <stdio.h>
    int ferror (FILE *stream);

    Отметим, что использование функции бинарного ввода   fread() ограничивает мобильность приложений, так как результат зависит от размера элементов и порядка байт, поддерживаемого процессором.

    Обратим также внимание на некоторые нюансы синхронного и асинхронного ввода с помощью функции read(). При попытке чтения из пустого канала, не открытого кем-либо на запись, результат равен 0 (как признак конца файла). Если пустой канал открыт кем-либо на запись, при установленном флаге O_NONBLOCK возвращается -1 (как признак ошибки EAGAIN ); при отсутствии флага O_NONBLOCK процесс ( поток управления) блокируется до появления данных в канале. Аналогичным образом устроен ввод из файлов других типов, поддерживающих чтение в асинхронном режиме.

    Содержимое символьных ссылок приходится читать особым образом (хотя бы потому, что обычно функция open() раскрывает их, т. е. открывает указуемый файл). Для этого служит функция readlink() (см. листинг 5.9). Она помещает содержимое ссылки с именем link_name в буфер buf длины buf_size (если буфер мал, остаток содержимого отбрасывается). Результат равен числу помещенных в буфер байт или -1 в случае неудачи.

    #include <unistd.h>
    ssize_t readlink (const char *restrict 
         link_name,	char *restrict buf, 
    	 size_t buf_size);

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

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #define C_TERM "/dev/tty"
    
    int main (void) {
    	char buf [BUFSIZ];
    	int fd;
    	ssize_t line_len;
    	/* Открытие на чтение специального файла, */
    	/* ассоциированного с управляющим терминалом */
    	if ((fd = open (C_TERM, O_RDONLY)) < 0) {
    		perror ("OPEN");
    		return (-1);
    	}
    	/* Ввод с терминала */
    	printf ("Вводите строки\n");
    	while ((line_len = read (fd, buf, BUFSIZ - 1))
    		> 0) {
    		buf [line_len] = '\0';
    		printf ("Вы ввели: %s", buf);
    	}
    	if (line_len == -1) {
    		perror ("READ");
    		close (fd);
    		return (-1);
    	}
    	return (close (fd));
    }

    В качестве примера мобильного использования функции fread(), а также функций feof() и ferror(), рассмотрим программу, подсчитывающую число символов, слов и строк в файле – аргументе командной строки (см. листинг 5.11).

    /* * * * * * * * * * * * * * * * * * * * * */
    /* Подсчет символов, слов и строк в файле */
    /* * * * * * * * * * * * * * * * * * * * * */
    #include <stdio.h>
    int main (int argc, char *argv[]) {
    	long nwords = 0; /* Счетчик слов */
    	long nlines = 0; /* Счетчик строк */
    	long nchars = 0; /* Счетчик символов */
    	FILE *fp = stdin; /* Если файл не задан, */
    	/* читается стандартный ввод */
    	unsigned char buf [BUFSIZ]; /* Буфер для */
    	/* чтения файла */
    	unsigned char *p1; /* Указатель на */
    	/* обрабатываемую часть буфера */
    	size_t nbytes = 0; /* Количество прочитанных,*/
    	/* но не обработанных байт */
    	register int c; /* Обрабатываемый символ */
    	int inword = 0; /* Признак - находимся ли мы */
    	/* внутри слова */
    	if (argc > 2) {
    		fprintf (stderr, "Использование: %s [файл]\n",
    		argv [0]);
    		return (2);
    	}
    	if (argc > 1  (fp = fopen (argv [1], "r")) ==
    		NULL) {
    		perror ("OPEN");
    		fprintf (stderr, "%s: Не могу открыть файл
    			%s\n", argv [0], argv[1]);
    		return (2);
    	}
    	p1 = buf;
    	for (;;) { /* Посимвольная обработка файла */
    		if (nbytes == 0) { /* Нужно прочитать новую порцию */
    			if (feof (fp)) {
    				/* При предыдущем чтении дошли до конца файла */
    				break;
    			}
    			nbytes = fread (p1 = buf, (size_t) 1,
    			sizeof (buf), fp);
    			if (ferror (fp)) {
    				perror ("READ");
    				fprintf (stderr, "%s: Ошибка чтения из
    					файла %s:", argv [0],
    				argc == 1 ? "стандартного ввода" : argv
    				[1]);
    				break;
    			}
    			if (nbytes == 0) {
    				/* В файле не оставалось непрочитанных символов */
    				break;
    			}
    			nchars += nbytes;
    	}
    	c = *p1++; /* Обработка одного символа */
    	nbytes--;
    	if (c > ' ') {
    		if (!inword) {
    			nwords++;
    			inword = 1;
    		}
    		continue;
    	}
    	if (c == '\n') {
    		nlines++;
    	} else if (c != ' '  c != '\t') {
    		continue;
    	}
    	inword = 0;
    	}
    	if (argc > 1) {
    		fclose (fp);
    	}
    	printf ("Файл %s: строк: %ld, слов: %ld,
    		символов: %ld\n",
    	argc == 1 ? "стандартного ввода" : argv [1],
    		nlines, nwords, nchars);
    	return (0);
    }

    Читателю предлагается убрать из цикла проверку feof ( fp ) и оценить, как изменится обработка интерактивного стандартного ввода.

    Для иллюстрации использования функции readlink() напишем программу, выдающую на стандартный вывод содержимое символьных ссылок, имена которых заданы в командной строке (см. листинг 5.12).

    #include <unistd.h>
    #include <stdio.h>
    /* Программа выдает на стандартный вывод */
    /* содержимое символьных ссылок - */
    /* аргументов командной строки */
    int main (int argc, char *argv[]) {
    	char buf [BUFSIZ];
    	ssize_t link_len;
    	int err = 0;
    	int i;
    	for (i = 1; i < argc; i++) {
    		if ((link_len = readlink (argv [i], buf,
    			sizeof (buf) - 1)) < 0) {
    			perror ("READLINK");
    			fprintf (stderr, "%s: Не удалось прочитать содержимое символьной ссылки %s\n", argv [0], argv [i]);
    			err = -1;
    			continue;
    		}
    		buf [link_len] = '\0';
    		printf ("Содержимое символьной ссылки %s -> %s\n", argv [i], buf);
    	}
    return (err);
    }

    Запись данных в файл выполняют функции write() и fwrite() (см. листинг 5.13).

    #include <unistd.h>
    ssize_t write (int fildes, const void *buf,
    	size_t nbyte);
    #include <stdio.h>
    size_t fwrite (const void *restrict buf, 
        size_t size,	size_t nitems, 
    	FILE *restrict stream);

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

    При записи в канал, если флаг O_NONBLOCK не установлен, процесс ( поток управления) может быть отложен, но после нормального завершения функция write() вернет nbyte. При установленном флаге O_NONBLOCK поведение зависит от значения nbyte и наличия свободного места в канале. Если nbyte не превосходит константы PIPE_BUF, запишется все или ничего (в последнем случае результат будет равен -1 ). При попытке записать порцию данных большего размера запишется сколько можно или ничего.

    Приведем несколько примеров. Следующая программа (см. листинг 5.14) выводит приветствие на управляющий терминал.

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #define C_TERM "/dev/tty"
       char msg [] = "HELLO !!!\n";
       int main (void) {
       int fd;
       /* Открытие на запись специального файла, 
           ассоциированного с управляющим терминалом */
       if ((fd = open (C_TERM, O_WRONLY)) < 0)
       {
          perror ("OPEN");
          return (-1);
       }
       /* Вывод на терминал */
       if (write (fd, msg, sizeof (msg)) != (ssize_t)
          sizeof (msg)) {
          perror ("WRITE");
          return (1);
       }
       return (close (fd));
    }

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

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    
    #define SOURCE_FILE "prnmyself.c"
    #define C_TERM "/dev/tty"
    
    int main (void) {
    	unsigned char buf [BUFSIZ];
    	int fdr, fdw; /* Дескрипторы для чтения и записи */
    	ssize_t nb;
    	if (((fdr = open (SOURCE_FILE, O_RDONLY)) < 0) ||
    		((fdw = open (C_TERM, O_WRONLY)) < 0)) {
    		perror ("OPEN " SOURCE_FILE " or " C_TERM);
    		return (1);
    	}
    	do {
    		if ((nb = read (fdr, buf, BUFSIZ)) < 0) {
    		perror ("READ");
    		break;
    	}
    	if (write (fdw, buf, nb) != nb) {
    		perror ("WRITE");
    		break;
    	}
    	} while (nb == BUFSIZ);
    	(void) close (fdw);
    	(void) close (fdr);
    	return (0);
    }

    Для буферизованного ввода/вывода байт служат функции fgetc() и fputc(), строки рекомендуется вводить, вызывая функцию fgets(), а выводить с помощью функций fputs() и puts() (см. листинг 5.16).

    #include <stdio.h>
    int fgetc (FILE *stream);
    #include <stdio.h>
    int fputc (int c, FILE *stream);
    #include <stdio.h>
    char *fgets (char *restrict s, int n,
    	FILE *restrict stream);
    #include <stdio.h>
    int fputs (const char *restrict s,
    	FILE *restrict stream);
    #include <stdio.h> 
    int puts (const char *s);

    Описание аналогичных функций для широких символов приведено в листинге 5.17.

    #include <stdio.h>
    #include <wchar.h>
    wint_t fgetwc (FILE *stream);
    #include <stdio.h>
    #include <wchar.h>
    wint_t fputwc (wchar_t wc, FILE *stream);
    #include <stdio.h>
    #include <wchar.h>
    wchar_t *fgetws (wchar_t *restrict ws, int n,
    	FILE *restrict stream);
    #include <stdio.h>
    #include <wchar.h>
    int fputws (const wchar_t *restrict ws,
    	FILE *restrict stream);

    Функция fgetc() пытается прочитать из заданного потока один байт, преобразовать его из типа unsigned char в int и вернуть в качестве результата. В случае ошибки или при достижении конца файла возвращается константа EOF.

    Функция fgets() читает из заданного потока и помещает в буфер с адресом s ( n - 1 ) байт или строку, включая символ перевода строки, или байты, оставшиеся до конца файла, если длина строки или число байт до конца меньше ( n - 1 ). После прочитанных добавляется нулевой байт.

    При нормальном завершении fgets() возвращает s. В случае ошибки или при достижении конца файла возвращается пустой указатель.

    Функция fputc() помещает в поток значение c, предварительно преобразовав его к типу unsigned char. Результатом служит c или EOF.

    Функция fputs() выводит в поток цепочку символов (без завершающего нулевого байта) и возвращает неотрицательное целое число или EOF. Функция puts() делает то же для потока stdout, завершая вывод переводом строки.

    Функции для работы с потоками широкой ориентации устроены аналогично с точностью до замены int на wint_t, char на wchar_t и EOF на WEOF.

    Программа, показанная в листинге 5.18, иллюстрирует использование функций fgets() и fputs(). Читателю предлагается сравнить ее с листингом 5.10.

    #include <stdio.h>
    #include <limits.h>
    
    /* Программа копирует строки со стандартного ввода на стандартный вывод */
    int main (void) {
       char line [LINE_MAX];
       fputs ("Вводите строки\n", stdout);
       while (fgets (line, sizeof (line), stdin) != NULL) {
          if ((fputs ("Вы ввели: ", stdout) == EOF) || (fputs (line, stdout) == EOF)) {
                break;
          }
       }
       return (ferror (stdin) || ferror (stdout));
    }

    Использование функций fgetc() и fputc() иллюстрируется программой, написанной С.В. Самборским (см. листинг 5.19). Она выполняет раскодировку файлов формата base64, применяемого, например, в электронной почте.

    #include <stdio.h>
    #include <assert.h>
    #include <string.h>
    #include <endian.h>
    
    FILE *input=NULL, *output=NULL;
    const char str []
    	="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    unsigned int Prm [256]; /* Таблица перекодировки */
    const int WHITE = 100, ERR = 101, END = 102;
    static void usage (char argv0 []) {
    	fprintf (stderr,"Программа раскодирует файлы
    		формата base64\n");
    	fprintf (stderr,"Использование:\n%s входной_файл
    		выходной_файл\n", argv0);
    	fprintf (stderr,"Файл должен начинаться с
    		первого символа в кодировке base64.\n");
    }
    int main (int argc, char *argv []) {
    	int n;
    	union {
    		unsigned long l;
    		char c[4];
    	} a;
    	{
    	int i;
    	for (i = 0; i < 256; i++) Prm [i] = ERR;
    	Prm [' '] = WHITE; Prm ['\t'] = WHITE;
    	Prm ['\n'] = WHITE; Prm ['\r'] = WHITE;
    	Prm ['='] = END;
    	for (i = 0; i < 64; i++) Prm [(int) str [i]] = i;
    	}
    	if (argc != 3) {
    	usage (argv [0]);
    	return (1);
    	}
    	assert (NULL != (input = fopen (argv [1], "r")));
    	assert (NULL != (output = fopen (argv [2], "w")));
    	for (a.l = 0, n = 0; ; ) { /* Цикл обработки
    		входного файла */
    		int c, b, shift;
    		assert (EOF != (c = fgetc (input)));
    		b = Prm [c];
    		if (WHITE == b) continue;
    		if (END == b) break;
    		if (ERR == b) {
    			fprintf (stderr,"Символ номер %d: %d не
    				входит в кодировку base64\n", n, c);
    			return (1);
    		}
    		n++;
    		assert (b < 64);
    		shift = 6 * (4 - n % 4);
    		if (shift != 24) b = b << shift;
    		a.l += b;
    		if (0 == n % 4) {
    			#if __BYTE_ORDER == __BIG_ENDIAN
    			fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output);
    			#elif __BYTE_ORDER == __LITTLE_ENDIAN
    			fputc (a.c[2], output); fputc (a.c[1],
    			output); fputc (a.c[0], output);
    			#elif __BYTE_ORDER == __PDP_ENDIAN
    			fputc (a.c[0], output); fputc (a.c[3],
    			output); fputc (a.c[2], output);
    			#else
    			#error "Unknown endian"
    			#endif
    			a.l = 0;
    		}
    	}
    	{ /* Обработка остатка входного файла */
    	int tl = (((n - 1) % 4) * 6 + 7) / 8;
    	if (tl == 3) {
    		#if __BYTE_ORDER == __BIG_ENDIAN
    		fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output);
    		#elif __BYTE_ORDER == __LITTLE_ENDIAN
    		fputc (a.c[2], output); fputc (a.c[1], output);
    		fputc (a.c[0], output);
    		#elif __BYTE_ORDER == __PDP_ENDIAN
    		fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output);
    		#else
    		#error "Unknown endian"
    		#endif
    	}
    		if (tl == 2) {
    			#if __BYTE_ORDER == __BIG_ENDIAN
    			fputc (a.c[1], output); fputc (a.c[2],
    			output);
    			#elif __BYTE_ORDER == __LITTLE_ENDIAN
    			fputc (a.c[2], output); fputc (a.c[1],
    			output);
    			#elif __BYTE_ORDER == __PDP_ENDIAN
    			fputc (a.c[0], output); fputc (a.c[3],
    			output);
    			#else
    			#error "Unknown endian"
    			#endif
    		}
    		if (tl == 1) {
    			#if __BYTE_ORDER == __BIG_ENDIAN
    			fputc (a.c[1], output);
    			#elif __BYTE_ORDER == __LITTLE_ENDIAN
    			fputc (a.c[2], output);
    			#elif __BYTE_ORDER == __PDP_ENDIAN
    			fputc (a.c[0], output);
    			#else
    			#error "Unknown endian"
    			#endif
    		}
    	}
    	fclose (input);
    	fclose (output);
    	return (0);
    }

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

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

    decode64: decode64.c:39: main: Assertion 
    `((void *)0) != (input = fopen (argv [1], "r"))' 
    failed.

    может оказаться менее информативным, чем выдача функции perror().

    Управляющие операции с файлами и ассоциированными данными

    К числу управляющих операций с файлами мы отнесем прежде всего позиционирование. Индикатор текущей позиции может быть опрошен или передвинут при помощи функции нижнего уровня lseek(), а также функций буферизованного ввода/вывода   fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind() (см. листинг 5.20).

    #include <unistd.h>
    off_t lseek (int fildes, off_t offset, 
         int whence);
    #include <stdio.h>
    int fseek (FILE *stream, long offset, 
         int whence);
    long ftell (FILE *stream);
    off_t ftello (FILE *stream);
    int fgetpos (FILE *restrict stream,
    	fpos_t *restrict pos);
    int fsetpos (FILE *stream, const fpos_t *pos);
    void rewind (FILE *stream);

    Функция lseek() устанавливает индикатор текущей позиции следующим образом. Сначала, в зависимости от значения третьего аргумента, whence, выбирается точка отсчета: 0, если это значение равно SEEK_SET, текущая позиция для SEEK_CUR и размер файла для SEEK_END. Затем к точке отсчета прибавляется смещение offset (второй аргумент).

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

    Результатом функции lseek() служит новое значение индикатора текущей позиции, отсчитанное в байтах от начала файла. В случае ошибки возвращается ( off_t ) ( -1 ), а текущая позиция остается прежней.

    Функция fseek() по сути аналогична, только в случае нормального завершения возвращается 0. Кроме того, если поток имеет широкую ориентацию, значение аргумента whence должно равняться SEEK_SET, а значение offset – нулю или результату вызова функции ftell() для того же потока.

    Функция ftell() возвращает значение индикатора текущей позиции для заданного потока (в случае ошибки – (long) (-1) ). Функция ftello() эквивалентна ftell() с точностью до типа результата; в новых приложениях рекомендуется использовать ftello(), так как эта функция применима к большим файлам.

    Функции fgetpos() и fsetpos() являются парными. Первая из них заполняет структуру, на которую указывает аргумент pos, а вторая применяет ее для установки индикатора текущей позиции. Нормальным результатом служит 0.

    Функция rewind(), если пренебречь некоторыми тонкостями, сводится к вызову

    (void) fseek (stream, 0L, SEEK_SET).

    Приведем несколько примеров. В листинге 5.21 показана установка индикатора текущей позиции в начало и конец файла, а также его (индикатора) приращение.

    (void) lseek (fildes, (off_t) 0, SEEK_SET);
    (void) lseek (fildes, (off_t) 0, SEEK_END);
    (void) lseek (fildes, inc, SEEK_CUR);

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

    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Набор функций для занесения текстов в файл, */
    /* который составляется из двух частей: */
    /* таблицы длин и смещений текстов от начала */
    /* файла собственно текстов */
    /* */
    /* В начало файла помещается специальный */
    /* элемент таблицы с магическим числом и общим */
    /* количеством текстов */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    #include <stdio.h>
    #include <string.h>
    /* Магическое число файла с текстами */
    #define G_TXT_MAGIC 0x1993
    /* Элемент таблицы длин и смещений */
    typedef struct {
    	unsigned int l_txt; /* Длина текста (без */
    	/* (нулевого байта) */
    	unsigned long off_txt; /* Смещение текста от */
    	/* начала файла */
    } txt_table_elem;
    static FILE *fp = NULL;/* Указатель на поток */
    /* файла с текстами */
    static unsigned long max_n_txt; /* Общее число */
    /* текстов в файле */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Функция для инициализации набора. */
    /* n_txts - максимальное число добавляемых текстов */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    int g_init_add_txt (const int argc, char *argv [],
    	const unsigned long n_txts) {
    	char *path; /* Имя файла, куда нужно поместить
    		тексты */
    	int magic; /* Магическое число файла с текстами */
    	txt_table_elem tte;
    	unsigned int i;
    	if (argc != 2) {
    	fprintf (stderr, "Использование: %s
    		файл_для_текстов\n", argv [0]);
    	return (-1);
    }
    path = argv [1];
    /* Аккуратно откроем файл с текстами */
    /* Если он уже есть и в нем правильное */
    /* магическое число, */
    /* будем добавлять тексты. */
    /* В противном случае создадим и */
    /* инициализируем файл */
    if (((fp = fopen (path, "r+")) != NULL)  (fread (magic, sizeof (unsigned int), 1, fp) == 1)  (magic == G_TXT_MAGIC)) {
    	/* Перед нами - наш файл */
    	/* Проверим, не превышает ли заказанная */
    	/* верхняя граница существующую */
    	if (fread (max_n_txt, sizeof (unsigned long), 1, fp) != 1) {
    		fprintf (stderr, "Не удается прочитать
    			информацию из файла %s\n", path);
    		return (-1);
    	}
    if (n_txts > max_n_txt) {
    	fprintf (stderr, "***** Новая верхняя
    		граница числа сообщений %lu больше
    			существующей %lu\n", n_txts, max_n_txt);
    }
    } else {
    	/* Файла нет или он не наш */
    	(void) fclose (fp);
    	if ((fp = fopen (path, "w+")) == NULL) {
    		fprintf (stderr, "Не удается открыть файл
    			%s\n", path);
    		return (-1);
    	}
    tte.l_txt = magic = G_TXT_MAGIC;
    tte.off_txt = max_n_txt = n_txts;
    if (fwrite (tte, sizeof (txt_table_elem), 1, fp) != 1) {
    	fprintf (stderr, "Не удается записать
    	информацию в файл %s\n", path);
    	return (-1);
    }
    /* Пропишем нулями индексную таблицу */
    /* Заодно конец файла окажется в будущем */
    /* начале текстов */
    tte.l_txt = 0;
    tte.off_txt = 0;
    for (i = 0; i < max_n_txt; i++) {
    	if (fwrite (tte, sizeof (txt_table_elem), 1, fp) != 1) {
    		fprintf (stderr, "Не удается записать информацию
    			в файл %s\n", path);
    		return (-1);
    	}
    }
    } /* if - существует ли файл с текстами */
    return 0;
    }
    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Функция для добавления одного текста */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    int g_add_txt (const unsigned long n_t, const char *txt) {
    	unsigned int l; /* Длина текста txt */
    	txt_table_elem tte;
    	if (n_t >= max_n_txt) {
    		fprintf (stderr, "Номер текста: %lu должен
    			быть меньше: %lu\n", n_t, max_n_txt);
    	return (-1);
    	}
    l = strlen (txt);
    tte.l_txt = l;
    if (fseek (fp, 0L, SEEK_END)) {
    		fprintf (stderr, "Ошибка позиционирования при
    			добавлении текста номер %lu\n", n_t);
    		return (-1);
    }
    tte.off_txt = ftell (fp);
    if (fseek (fp, (n_t + 1) * sizeof (txt_table_elem), SEEK_SET)) {
    	fprintf (stderr, "Ошибка позиционирования при
    		добавлении текста номер %lu\n", n_t);
    	return (-1);
    }
    if (fwrite (tte, sizeof (tte), 1, fp) != 1) {
    	fprintf (stderr, "Ошибка записи при добавлении
    		текста номер %lu\n", n_t);
    	return (-1);
    }
    if (fseek (fp, tte.off_txt, SEEK_SET)) {
    	fprintf (stderr, "Ошибка позиционирования при
    		добавлении текста номер %lu\n", n_t);
    	return (-1);
    }
    if (fwrite (txt, sizeof (char), l, fp) != l) {
    	fprintf (stderr, "Ошибка записи при добавлении
    		текста номер %lu\n", n_t);
    	return (-1);
    }
    return 0;
    }
    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Функция для завершения добавления текстов */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    int g_term_add_txt () {
    	return (fclose (fp));
    }
    /* * * * * * * * * * * * * * * * * * * * * * * */
    /* Главная программа, вызывающая определенные */
    /* выше функции */
    /* * * * * * * * * * * * * * * * * * * * * * * */
    #define MAX_TXTS 10240
    
    int main (int argc, char *argv[]) {
    	if (g_init_add_txt (argc, argv, MAX_TXTS) ||
    		g_add_txt (0, "Reference to section number %d in %s\n") ||
    		g_add_txt (1, "Data .init section in %s\n")) {
    		(void) g_term_add_txt ();
    		return (-1);
    		}
    	return (g_term_add_txt ());
    }

    Функция fcntl() предназначена для выполнения разнообразных управляющих операций над открытым файлом (см. листинг 5.23).

    #include <fcntl.h>
    int fcntl (int fildes, int cmd, ...);

    Аргумент fildes задает дескриптор открытого файла, cmd – управляющую команду, дополнительные данные для которой могут быть переданы в качестве третьего (необязательного) аргумента arg (обычно имеющего тип int ). В случае успешного завершения возвращаемое значение естественным образом зависит от команды; при неудаче всегда возвращается -1.

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

    F_DUPFD

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

    F_GETFL

    Вернуть флаги статуса и режим доступа к файлу (см. выше описание функции open() ). Для выделения режима доступа из возвращаемого значения предоставляется маска O_ACCMODE.

    F_SETFL

    Установить флаги статуса файла в соответствии со значением третьего аргумента arg.

    Приведем пример использования описанных команд функции fcntl(). Перенаправить стандартный вывод в файл, а затем стандартный протокол на стандартный вывод позволяет программа, показанная в листинге 5.24 (читателю рекомендуется сопоставить ее с программой перенаправления стандартного вывода с помощью функции freopen(), см. выше листинг 5.5). Кроме того, в ней демонстрируется изменение набора флагов статуса файла, ассоциированного с дескриптором.

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <assert.h>
    #define LOGFILE "my_logfile"
    
    int main (void) {
    	int fd;
    	int flags;
    	assert ((fd = open (LOGFILE, O_WRONLY | O_CREAT
    		| O_APPEND, S_IRWXU)) > 2);
    	printf ("До перенаправления стандартного вывода
    		в файл " LOGFILE "\n");
    	close (1);
    	assert (fcntl (fd, F_DUPFD, 1) == 1);
    	close (fd);
    	printf ("После перенаправления стандартного
    		вывода в файл " LOGFILE "\n");
    	/* Добавим флаг обеспечения целостности файла */
    	/* при записи */
    	assert ((flags = fcntl (1, F_GETFL, 0)) != -1);
    	assert (fcntl (1, F_SETFL, flags | O_SYNC) != -1);
    	fprintf (stderr, "До перенаправления стандартного
    		протокола на стандартный вывод\n");
    	close (2);
    	assert (fcntl (1, F_DUPFD, 2) == 2);
    	fprintf (stderr, "После перенаправления
    		стандартного протокола на стандартный вывод\n");
    	close (1);
    	close (2);
    	return (0);
    }

    Команды F_GETFD и F_SETFD функции fcntl() предназначены, соответственно, для опроса и изменения флагов, ассоциированных с заданным файловым дескриптором (а не с файлом). В стандарте упомянут один такой флаг – FD_CLOEXEC, предписывающий закрывать дескриптор при смене программы процесса.

    Особый класс управляющих операций с файлами, имеющих свою систему понятий, составляют блокировки, хотя и они оформляются как команды функции fcntl().

    Назначение механизма блокировок – дать возможность процессам ( потокам управления), одновременно обрабатывающим одни и те же данные, синхронизировать свою работу. В стандарте POSIX-2001 представлены только так называемые рекомендательные блокировки, которые взаимодействуют исключительно с другими блокировками и не препятствуют операциям ввода/вывода. Это значит, что для достижения синхронизации процессы должны окружать операции ввода/вывода разделяемых данных операциями установки и снятия соответствующей блокировки.

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

    Блокировка на чтение используется для того, чтобы ограничить доступ к сегментам: если сегмент заблокирован на чтение, то другие процессы также могут заблокировать на чтение весь сегмент или его часть, однако никакие сегменты, заблокированные на запись, с ним не пересекаются.

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

    Блокировка сегмента описывается структурой flock, которая, согласно стандарту, должна содержать по крайней мере следующие поля:

    short l_type /* Тип блокировки: */
       /* F_RDLCK (на чтение) */
       /* F_WRLCK (на запись), */
       /* F_UNLCK (снятие блокировки) */
    short l_whence/* Точка отсчета для смещения */
       /* l_start: SEEK_SET, SEEK_CUR */
       /* или SEEK_END */
    off_t l_start /* Смещение в байтах начала */
       /* блокируемого сегмента */
       /* относительно точки отсчета */
    off_t l_len /* Размер блокируемого сегмента. */
       /* 0 означает блокировку до конца файла. */
    pid_t l_pid /* Идентификатор процесса,*/
       /* установившего блокировку; */
       /* (возвращается по команде F_GETLK, */
       /* см. далее) */

    Отметим, что блокируемый сегмент может выходить за конец, но не за начало файла. Длину сегмента разрешается задавать отрицательным числом, если тип off_t допускает такую возможность; в таком случае смещение начала блокируемого сегмента относительно точки отсчета равно l_start+l_len, а смещение конца – l_start-1.

    Приведем несколько примеров заполнения структуры flock (см. листинг 5.25).

    #include <fcntl.h>
    struct flock lck;
    . . .
    lck.l_type = F_RDLCK; 
        /* Блокировка на чтение */
        /* всего файла */
    lck.l_whence = SEEK_SET;
    lck.l_start = 0;
    lck.l_len = 0;

    Установка блокировки осуществляется управляющими командами F_SETLK и F_SETLKW функции fcntl() (см. листинг 5.26).

    if (fcntl (fd, F_SETLK, lck) != -1) ...
    if (fcntl (fd, F_SETLKW, lck) != -1) ...

    Если блокировка не может быть установлена, выполнение команды F_SETLK немедленно завершается, и возвращается -1. Команда F_SETLKW отличается только тем, что в аналогичной ситуации процесс переходит в состояние ожидания до тех пор, пока нужный сегмент файла не будет разблокирован.

    Для снятия блокировки можно воспользоваться командами F_SETLK или F_SETLKW. Для этого значение поля l_type должно быть установлено равным F_UNLCK.

    Получить характеристики блокировки, мешающей установить новую, позволяет управляющая команда F_GETLK (новая блокировка задается структурой, на которую при обращении к fcntl() указывает третий аргумент arg ). Если запрашиваемую блокировку установить нельзя, информация о первой помещается в ту же структуру; в частности, будет задано значение поля l_pid – оно идентифицирует процесс, установивший блокировку. (Естественно, значение поля l_whence будет установлено равным SEEK_SET.) Если нет помех для создания нужной блокировки, полю l_type присваивается значение F_UNLCK, а остальные поля в структуре не изменяются.

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

    Приведем пример двух программ, первая из которых (назовем ее set_locks ) устанавливает блокировки нескольких сегментов файла и засыпает на некоторое время (см. листинг 5.27), а вторая ( test_locks, см. листинг 5.28), выполняющаяся, разумеется, параллельно, в рамках другого процесса, получает и выводит информацию о заблокированных сегментах того же файла (см. листинг 5.29).

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <assert.h>
    
    #define LOCKFILE "my_lockfile"
    
    /* Программа устанавливает несколько блокировок */
    /* на файл LOCKFILE */
    int main (void) {
    	int fd;
    	struct flock lck;
    	assert ((fd = open (LOCKFILE, O_RDWR | O_CREAT |
    		O_TRUNC, S_IRWXU)) != -1);
    	/* Установим блокировку на запись на весь файл */
    	lck.l_type = F_WRLCK;
    	lck.l_whence = SEEK_SET;
    	lck.l_start = (off_t) 0;
    	lck.l_len = (off_t) 0;
    	if (fcntl (fd, F_SETLK, lck) == -1) {
    		perror ("FCNTL-F_SETLK-1");
    		close (fd);
    		return (-1);
    	}
    	/* Сделаем размер файла ненулевым */
    	if (lseek (fd, (off_t) 1024, SEEK_SET) == -1) {
    		perror ("LSEEK");
    		close (fd);
    		return (-1);
    	}
    	if (write (fd, lck, sizeof (lck)) != sizeof (lck)) {
    		perror ("WRITE");
    		close (fd);
    		return (-1);
    	}
    	/* Снимем блокировку в середине файла */
    	lck.l_type = F_UNLCK;
    	lck.l_whence = SEEK_SET;
    	lck.l_start = (off_t) 512;
    	lck.l_len = (off_t) sizeof (lck);
    	if (fcntl (fd, F_SETLK, lck) == -1) {
    		perror ("FCNTL-F_SETLK-2");
    		close (fd);
    		return (-1);
    	}
    	/* Установим блокировку на чтение в конце файла */
    	lck.l_type = F_RDLCK;
    	lck.l_whence = SEEK_END;
    	lck.l_start = (off_t) -sizeof (lck);
    	lck.l_len = (off_t) sizeof (lck);
    	if (fcntl (fd, F_SETLK, lck) == -1) {
    		perror ("FCNTL-F_SETLK-2");
    		close (fd);
    		return (-1);
    	}
    	sleep (10);
    	return (close (fd));
    }
    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <assert.h>
    
    #define LOCKFILE "my_lockfile"
    
    /* Программа выявляет блокировки, установленные */
    /* на файл LOCKFILE */
    int main (void) {
    	int fd;
    	struct flock lck;
    	assert ((fd = open (LOCKFILE, O_WRONLY)) != -1);
    	(void) printf ("ид-р проц. тип начало длина\n");
    	/* Начнем с попытки установить блокировку на */
    	/* весь файл */
    	lck.l_whence = SEEK_SET;
    	lck.l_start = 0;
    	lck.l_len = 0;
    	do {
    		lck.l_type = F_WRLCK;
    		(void) fcntl (fd, F_GETLK, lck);
    		if (lck.l_type != F_UNLCK) {
    			(void) printf ("%9d %3c %7ld %5ld\n",
    				lck.l_pid,
    				(lck.l_type == F_WRLCK) ? 'W' : 'R',
    				lck.l_start, lck.l_len);
    				/* Если эта блокировка покрывает остаток файла, */
    				/* нет нужды выявлять другие блокировки */
    		if (lck.l_len == 0) break;
    		/* Иначе поищем новую блокировку после найденной */
    		lck.l_start += lck.l_len;
    	}
    	while (lck.l_type != F_UNLCK);
    	return (close (fd));
    }
    ид-р проц. тип начало длина
    31174 	   W 	0     512
    31174 	   W 	528   496
    31174 	   R 	1024  16
    31174 	   W 	1040  0

    Отметим, что блокировка на чтение расщепила блокировку на запись, первоначально покрывавшую весь файл.

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

    Функции setbuf(), setvbuf( ) и fflush() выполняют управляющие операции с буферами потоков (см. листинг 5.30).

    #include <stdio.h>
    void setbuf (FILE *restrict stream,
       char *restrict buf);
    #include <stdio.h>
    int setvbuf (FILE *restrict stream,
       char *restrict buf, int type, size_t size);
    #include <stdio.h>
    int fflush (FILE *stream);

    Функция setvbuf(), которую можно использовать после открытия файла, но до первой операции ввода/вывода, устанавливает режим буферизации в соответствии со значением своего третьего аргумента, type:

    _IOFBF – полная буферизация;

    _IOLBF – построчная буферизация;

    _IONBF – отсутствие буферизации.

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

    Функцию setbuf() можно считать частным случаем setvbuf(). С точностью до возвращаемого значения вызов setbuf (stream, NULL) эквивалентен setvbuf (stream, NULL, _IONBF, BUFSIZ) (отмена буферизации); если же значение buf не равно NULL, вызов setbuf (stream, buf) сводится к setvbuf (stream, buf, _IOFBF, BUFSIZ) (полная буферизация).

    Функцию setbuf() чаще всего применяют для отмены буферизации стандартного вывода и/или стандартного протокола, выполняя вызовы setbuf (stdout, NULL) и/или setbuf (stderr, NULL).

    Использование функций setbuf() и setvbuf() требует известной аккуратности. Типичная ошибка – указание в качестве аргумента buf автоматического массива, определенного внутри блока, и продолжение работы с потоком после выхода из этого блока.

    Функция fflush() выталкивает буфера, помещая в файл предназначенные для записи, но еще не записанные данные. Если в качестве аргумента stream задан пустой указатель, выталкиваются все буфера всех потоков.

    Вызов fflush() – необходимый элемент завершения транзакций, но он полезен и применительно к стандартному выводу (протоколу), если нужно выдать приглашение для пользовательского ввода на экран, а не в буфер (см. листинг 5.31).

    char name [LINE_MAX];
    (void) printf ("Введите Ваше имя: "); 
    (void) fflush (stdout); (void) fgets (name, 
       sizeof (name), stdin);
    Вернуться к учебному плану