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


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


К числу управляющих операций с файлами мы отнесем прежде всего позиционирование. Индикатор текущей позиции может быть опрошен или передвинут при помощи функции нижнего уровня 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 представлены только так называемые рекомендательные блокировки, которые взаимодействуют исключительно с другими блокировками и не препятствуют операциям ввода/вывода. Это значит, что для достижения синхронизации процессы должны окружать операции ввода/вывода разделяемых данных операциями установки и снятия соответствующей блокировки.

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

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

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

Блокировка сегмента описывается структурой 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;

Листинг 5.25. Примеры заполнения структуры flock.

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

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

Листинг 5.26. Примеры вызова функции 2 для установки блокировок.

Если блокировка не может быть установлена, выполнение команды 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)); }

Листинг 5.27. Пример программы set_locks, устанавливающей блокировки файла.

#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)); }

Листинг 5.28. Пример программы test_locks, выявляющей блокировки файла.

ид-р проц. тип начало длина 31174 W 0 512 31174 W 528 496 31174 R 1024 16 31174 W 1040 0

Листинг 5.29. Возможный результат выполнения командной строки set_locks &amp; test_locks.

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

Читателю предлагается выполнить командную строку 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);

Листинг 5.30. Описание функций setbuf(), setvbuf() и fflush().

Функция 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);

Листинг 5.31. Пример использования функции fflush().

<


Начало  Назад  Вперед



Книжный магазин