В настоящее время актуальный стандарт - это 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 представлены только так называемые рекомендательные блокировки, которые взаимодействуют исключительно с другими блокировками и не препятствуют операциям ввода/вывода. Это значит, что для достижения синхронизации процессы должны окружать операции ввода/вывода разделяемых данных операциями установки и снятия соответствующей блокировки.
Блокировка связывается с сегментом (произвольным последовательным участком) файла. Различают блокировку на запись и на чтение. Блокировка на запись носит монопольный характер. Когда сегмент блокирован на запись, никакие другие процессы не имеют возможности заблокировать на чтение или запись этот же или пересекающийся с ним сегмент.
Блокировка на чтение используется для того, чтобы ограничить доступ к сегментам: если сегмент заблокирован на чтение, то другие процессы также могут заблокировать на чтение весь сегмент или его часть, однако никакие сегменты, заблокированные на запись, с ним не пересекаются.
Для установки блокировки на чтение требуется, чтобы файл был открыт как минимум на чтение. Соответственно, для блокировки на запись необходим доступ на запись.