Опубликован: 15.06.2004 | Доступ: свободный | Студентов: 2557 / 712 | Оценка: 4.35 / 3.96 | Длительность: 27:47:00
ISBN: 978-5-9556-0011-6
Лекция 5:

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

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

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

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

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

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

Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?