|
В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13 |
Файловый ввод/вывод
Управляющие операции с файлами и ассоциированными данными
К числу управляющих операций с файлами мы отнесем прежде всего позиционирование. Индикатор текущей позиции может быть опрошен или передвинут при помощи функции нижнего уровня 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);
Листинг
5.20.
Описание функций lseek(), fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind().
Функция 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.21. Примеры вызова функции lseek().
Применение функций буферизованного ввода/вывода иллюстрируется программой, показанной в листинге 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 ());
}
Листинг
5.22.
Пример использования функций буферизованного ввода/вывода.
Функция fcntl() предназначена для выполнения разнообразных управляющих операций над открытым файлом (см. листинг 5.23).
#include <fcntl.h> int fcntl (int fildes, int cmd, ...);Листинг 5.23. Описание функции fcntl().
Аргумент 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);
}
Листинг
5.24.
Пример перенаправления стандартного вывода в файл, а стандартного протокола – на стандартный вывод.
Команды F_GETFD и F_SETFD функции fcntl() предназначены, соответственно, для опроса и изменения флагов, ассоциированных с заданным файловым дескриптором (а не с файлом). В стандарте упомянут один такой флаг – FD_CLOEXEC, предписывающий закрывать дескриптор при смене программы процесса.
Особый класс управляющих операций с файлами, имеющих свою систему понятий, составляют блокировки, хотя и они оформляются как команды функции fcntl().
Назначение механизма блокировок – дать возможность процессам ( потокам управления), одновременно обрабатывающим одни и те же данные, синхронизировать свою работу. В стандарте POSIX-2001 представлены только так называемые рекомендательные блокировки, которые взаимодействуют исключительно с другими блокировками и не препятствуют операциям ввода/вывода. Это значит, что для достижения синхронизации процессы должны окружать операции ввода/вывода разделяемых данных операциями установки и снятия соответствующей блокировки.
Блокировка связывается с сегментом (произвольным последовательным участком) файла. Различают блокировку на запись и на чтение. Блокировка на запись носит монопольный характер. Когда сегмент блокирован на запись, никакие другие процессы не имеют возможности заблокировать на чтение или запись этот же или пересекающийся с ним сегмент.
Блокировка на чтение используется для того, чтобы ограничить доступ к сегментам: если сегмент заблокирован на чтение, то другие процессы также могут заблокировать на чтение весь сегмент или его часть, однако никакие сегменты, заблокированные на запись, с ним не пересекаются.
Для установки блокировки на чтение требуется, чтобы файл был открыт как минимум на чтение. Соответственно, для блокировки на запись необходим доступ на запись.