Марк Митчелл - Программирование для Linux. Профессиональный подход
fd = open(filename, O_RDONLY);
/* Получение информации о файле. */
fstat(fd, &file_info);
*length = file_info.st_size;
/* Проверка того, что это обычный файл. */
if (!S_ISREG(file_info.st_mode)) {
/* Этот тип файла не поддерживается. */
close(fd);
return NULL;
}
/* выделение буфера достаточного размера. */
buffer = (char*)malloc(*length);
/* Загрузка файла в буфер. */
read(fd, buffer, *length);
/* Конец работы. */
close(fd);
return buffer;
}
Б.3. Векторные чтение и запись
Аргументами функции write() являются указатель на буфер и длина буфера. Эта функция записывает в файл непрерывный блок данных, хранящихся в памяти. Но программам часто требуется записывать группы блоков, хранящихся по разным адресам. Если использовать функцию write(), придется либо предварительно объединять блоки в памяти, что неэффективно, либо многократно вызывать функцию. Последнее тоже не всегда приемлемо. Например, при записи в сокет два вызова функции write() приведут к отправке в сеть двух пакетов, тогда как те же самые данные можно перестать в одном пакете.
Функция writev() позволяет записать в файл несколько несвязанных буферов одновременно. Это называется векторной записью. Сложность применения функции writev() заключается в создании структуры, задающей начало и конец каждого буфера. Эта структура представляет собой массив элементов типа struct iovec. Каждый элемент описывает одну область памяти. В поле iov_base указывается адрес начала области, а в поле iov_len — ее длина. Если число буферов известно заранее, можно просто объявить массив типа struct iovec. В противном случае придется выделять массив динамически.
Функции writev() передается дескриптор записываемого файла, массив структур iovec и размер массива. Функция возвращает общее число записанных байтов.
Программа, показанная в листинге Б.7, записывает свои аргументы командной строки в файл с помощью одной-единственной функции writev(). Первый аргумент — это имя файла, в котором сохраняются все последующие аргументы, каждый в отдельной строке. Число элементов в массиве структур iovec в два раза превышает число аргументов командной строки, так как после каждого аргумента записывается символ новой строки. Поскольку количество аргументов неизвестно заранее, массив создается с помощью функции malloc().
Листинг Б.7. (write-args.c) Запись списка аргументов в файл с помощью функции writev()#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
int fd;
struct iovec* vec;
struct iovec* vec_next;
int i;
/* Символ новой строки хранится в обычной переменной
типа char. */
char newline = 'n';
/* Первый аргумент командной строки -- это имя выходного
файла. */
char* filename = argv[1];
/* Пропускаем первые два элемента списка аргументов.
Элемент номер 0 -- это имя самой программы,
а элемент номер 1 -- это имя выходного файла */
argc -= 2;
argv += 2;
/* Выделяем массив элементов типа iovec каждому аргументу
командной строки соответствует два элемента массива:
один -- для самого аргумента,
а другой -- для символа новой строки. */
vec =
(struct iovec*)malloc(2 * argc * sizeof(struct iovec));
/* Просмотр списка аргументов и создание массива. */
vec_next = vec;
for (i = 0; i < argc; ++i) {
/* первый элемент -- это текст аргумента */
vec_next->iov_base = argv[i];
vec_next->iov_len = strlen(argv[i]);
++vec_next;
/* Второй элемент -- это символ новой строки, допускается,
чтобы несколько элементов массива указывали на одну и
ту же область памяти. */
vec_next->iov_base = &newline;
vec_next->iov_len = 1;
++vec_next;
}
/* Запись аргументов в файл. */
fd = open(filename, O_WRONLY | O_CREAT);
writev(fd, vec, 2 * argc);
close(fd);
free(vec);
return 0;
}
Вот пример работы программы:
% ./write-args outputfile "first arg" "second arg" "third arg"
% cat outputfile
first arg
second arg
third arg
В Linux имеется также функция readv(), которая загружает содержимое файла в несколько несвязанных областей памяти. Как и в функции writev(), массив структур типа iovec определяет начало и размер каждой области.
Б.4. Взаимосвязь с библиотечными функциями ввода-вывода
Выше уже говорилось о том. что функции ввода-вывода стандартной библиотеки языка С реализованы на основе низкоуровневых функций. Иногда удобнее работать с одними, иногда — с другими.
Если файл был открыт с помощью функции fopen(), то узнать его дескриптор позволяет функция fileno(). Она принимает аргумент типа FILE* и возвращает соответствующий ему дескриптор. Например, можно открыть файл с помощью библиотечной функции fopen(), но осуществить в него запись посредством функции writev():
FILE* stream = fopen(filename, "w");
int file_descriptor = fileno(stream);
writev(file_descriptor, vector, vector_length);
Учтите, что переменные stream и file_descriptor соответствуют одному и тому же открытому файлу. Если выполнить следующую функцию, дескриптор file_descriptor станет недействительным:
fclose(stream);
Аналогичным образом следующая функция делает недействительным файловый указатель stream:
close(file_descriptor);
Чтобы получить файловый указатель, соответствующий дескриптору, воспользуйтесь функцией fdopen(). Ее аргументами является дескриптор и строка, определяющая режим создания файлового потока. Синтаксис строки аналогичен синтаксису второго аргумента функции fopen(), а задаваемый режим должен быть совместим с режимом открытия файла. Например, файлу, открытому для чтения, соответствует режим r, а файлу, открытому для записи, — режим w. Как и в случае функции fileno(), файловый указатель и дескриптор ссылаются на один и тот же файл, поэтому закрытие одного сделает недействительным другой.
Б.5. Другие низкоуровневые операции
Есть ряд других полезных функций для работы с файлами и каталогами.
■ Функция getcwd() возвращает имя текущего каталога. Она принимает два аргумента — указатель на буфер и длину буфера — и копирует имя каталога в буфер.
■ Функция chdir() делает текущим заданный каталог.
■ Функция mkdir() создает новый каталог. Ее первым аргументом является путевое имя каталога. Второй аргумент задает права доступа к каталогу. Интерпретация этого аргумента такая же, как и третьего аргумента функции open(). На итоговый код доступа влияет значение umask процесса.
■ Функция rmdir() удаляет указанный каталог.
■ Функция unlink() удаляет файл. Ее аргументом является путевое имя файла. С помощью этой функции можно удалять и другие объекты файловой системы, например именованные каналы и файлы устройств.
В действительности функция unlink() не обязательно удаляет содержимое файла. Как подсказывает ее имя, она удаляет из каталога ссылку на файл. Файл не будет больше фигурировать в списке содержимого каталога, но если какой-то процесс владеет открытым дескриптором этого файла, то содержимое файла не удаляется с диска. Это произойдет только тогда, когда не останется открытых дескрипторов файла. Так что если один процесс откроет файл для чтения или записи, а второй процесс в это время удалит ссылку на файл и создаст новый файл с таким же именем, первый процесс продолжит работать со старым содержимым файла. Чтобы получить доступ к новому содержимому первому процессу придется закрыть и повторно открыть файл.
■ Функция rename() переименовывает или перемещает файл. Двумя ее аргументами являются старое и новое путевые имена. Если путевые имена ссылаются на разные каталоги, функция перемещает файл (при условии, что он остается в той же файловой системе). С помощью функции rename() можно перемещать также каталоги и другие объекты файловой системы.
Б.6. Чтение содержимого каталога
В Linux имеются функции, предназначенные для чтения содержимого каталога. И хотя они не относятся к низкоуровневым функциям, мы все же решили их описать, так как они широко применяются в программах.