fopencookie(3) открывает нестандартный поток

ОБЗОР

#define _GNU_SOURCE /* см. feature_test_macros(7) */
#include <stdio.h>
FILE *fopencookie(void *cookie, const char *mode,
cookie_io_functions_t io_funcs);

ОПИСАНИЕ

Функция fopencookie() позволяет программисту создать нестандартную реализацию стандартного потока ввода-вывода. Эта реализация может хранить данные потока в расположении по своему выбору; например, fopencookie() используется для реализации функции fmemopen(3), которая предоставляет потоковый интерфейс для данных, хранящихся в буфере в памяти.

Для создания нестандартного потока программист должен:

*
Реализовать четыре «обрабатывающих» (hook) функции, которые используются внутри стандартной библиотеки ввода-вывода при операциях ввода-вывода над потоком.
*
Определить тип данных «cookie» — структуру для учёта информации (например, где хранятся данные), используемую вышеупомянутыми обрабатывающими функциями. Стандартный пакет ввода-вывода ничего не знает о содержимом этого cookie (к нему обращаются как к void * при передаче в fopencookie()), но автоматически передаёт cookie в первом аргументе при вызове обрабатывающих функций.
*
Вызвать fopencookie() для открытия нового потока и связывания cookie и обрабатывающими функциями с этим потоком.

Функция fopencookie() подобна fopen(3): она открывает новый поток и возвращает указатель на объект FILE, который используется для работы с потоком.

Аргумент cookie — это указатель на структуру cookie вызывающего, которая связывается с новым потоком. Данный указатель передаётся в первом аргументе в моменты, когда стандартная библиотека ввода-вывода вызывает одну из обрабатывающих функций, описанных ниже.

Аргумент mode служит той же цели что и для fopen(3). Поддерживаются следующие режимы: r, w, a, r+, w+ и a+. Подробности смотрите в fopen(3).

Аргумент io_funcs — это структура, которая содержит четыре поля с задаваемыми программистом обрабатывающими функциями, которые используются для реализации этого потока. Структура определена как

typedef struct {
    cookie_read_function_t  *read;
    cookie_write_function_t *write;
    cookie_seek_function_t  *seek;
    cookie_close_function_t *close;
} cookie_io_functions_t;
Поля:
cookie_read_function_t *read
Эта функция реализует операции чтения из потока. Она вызывается с тремя аргументами:


    ssize_t read(void *cookie, char *buf, size_t size);

Аргументы buf и size — буфер для получаемых данных и его размер. В качестве результата функция read возвращает количество байт, скопированных в buf, 0 — при окончании файла и -1 при ошибке. Функция read обновляет смещение в потоке соответствующим образом.

Если значение *read равно null, то при чтении из нестандартного потока всегда возвращается конец файла.

cookie_write_function_t *write
Эта функция реализует операции записи в поток. Она вызывается с тремя аргументами:


    ssize_t write(void *cookie, const char *buf, size_t size);

Аргументы buf и size — буфер для выходных данных и его размер (данные, записываемые в поток). В качестве результата функция write возвращает количество байт, скопированных из buf, и 0 при ошибке (функция не должна возвращать отрицательное значение). Функция write обновляет смещение в потоке соответствующим образом.

Если значение *write равно null, то вывод в поток отбрасывается.

cookie_seek_function_t *seek
Эта функция реализует операции смещения в потоке. Она вызывается с тремя аргументами:


    int seek(void *cookie, off64_t *offset, int whence);

В аргументе *offset указывается новое файловое смещение, зависящее от значения whence:

SEEK_SET
Значение смещения приравнивается *offset байт от начала потока.
SEEK_CUR
Значение *offset должно быть добавлено к текущему смещению в потоке.
SEEK_END
Значение смещения приравнивается к размеру потока плюс *offset.
Перед возвратом функция seek должна обновить *offset, чтобы показать новое смещение в потоке.

В качестве результата функция seek должна возвращать 0 при успешном выполнении и -1 при ошибке.

Если значение *seek равно null, то операции смещения в потоке выполнить невозможно.

cookie_close_function_t *close
Эта функция закрывает поток. Обрабатывающая функция может выполнить такие операции как освобождение буферов, выделенных для потока. При вызове она принимает один аргумент:


    int close(void *cookie);

Аргумент cookie — это cookie, которую программист передал при вызове fopencookie().

В качестве результата функция close должна возвращать 0 при успешном выполнении и EOF при ошибке.

Если значение *close равно NULL, то при закрытии потока не выполняется никаких действий.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При успешном выполнении fopencookie() возвращает указатель на новый поток. При ошибке возвращается NULL.

АТРИБУТЫ

Описание терминов данного раздела смотрите в attributes(7).
ИнтерфейсАтрибутЗначение
fopencookie() безвредность в нитяхбезвредно (MT-Safe)

СООТВЕТСТВИЕ СТАНДАРТАМ

Эта функция является нестандартным расширением GNU.

ПРИМЕР

Программа, представленная ниже, реализует нестандартный поток, свойства которого похожи (но не одинаковы) на свойство потока, получаемого от fmemopen(3). Она реализует поток, данные которого хранятся в буфере памяти. Программа записывает свои аргументы командной строки в поток, а затем перемещается по потоку, читая два из каждых пяти символов и записывая их в стандартный вывод. Сеанс оболочки, демонстрирующий использование программы:
$ ./a.out 'hello world'
/he/
/ w/
/d/
Достигнут конец файла
Заметим, что представленную версию можно сильно улучшить, добавив обработку ошибок (например, открытие потока с cookie, которая уже имеет открытый поток; закрытие потока, который уже был закрыт).

Исходный код программы

#define _GNU_SOURCE
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define INIT_BUF_SIZE 4
struct memfile_cookie {
    char   *buf;        /* динамически изменяемый буфер для данных */
    size_t  allocated;  /* размер буфера */
    size_t  endpos;     /* количество символов в буфере */
    off_t   offset;     /* текущее файловое смещение в буфере */
};
ssize_t
memfile_write(void *c, const char *buf, size_t size)
{
    char *new_buff;
    struct memfile_cookie *cookie = c;
    /* Буфер мал? Удваиваем размер, пока не станет достаточным */
    while (size + cookie->offset > cookie->allocated) {
        new_buff = realloc(cookie->buf, cookie->allocated * 2);
        if (new_buff == NULL) {
            return -1;
        } else {
            cookie->allocated *= 2;
            cookie->buf = new_buff;
        }
    }
    memcpy(cookie->buf + cookie->offset, buf, size);
    cookie->offset += size;
    if (cookie->offset > cookie->endpos)
        cookie->endpos = cookie->offset;
    return size;
}
ssize_t
memfile_read(void *c, char *buf, size_t size)
{
    ssize_t xbytes;
    struct memfile_cookie *cookie = c;
    /* Выбираем минимум запрашиваемых и доступных байт */
    xbytes = size;
    if (cookie->offset + size > cookie->endpos)
        xbytes = cookie->endpos - cookie->offset;
    if (xbytes < 0)     /* смещение может быть за endpos */
       xbytes = 0;
    memcpy(buf, cookie->buf + cookie->offset, xbytes);
    cookie->offset += xbytes;
    return xbytes;
}
int
memfile_seek(void *c, off64_t *offset, int whence)
{
    off64_t new_offset;
    struct memfile_cookie *cookie = c;
    if (whence == SEEK_SET)
        new_offset = *offset;
    else if (whence == SEEK_END)
        new_offset = cookie->endpos + *offset;
    else if (whence == SEEK_CUR)
        new_offset = cookie->offset + *offset;
    else
        return -1;
    if (new_offset < 0)
        return -1;
    cookie->offset = new_offset;
    *offset = new_offset;
    return 0;
}
int
memfile_close(void *c)
{
    struct memfile_cookie *cookie = c;
    free(cookie->buf);
    cookie->allocated = 0;
    cookie->buf = NULL;
    return 0;
}
int
main(int argc, char *argv[])
{
    cookie_io_functions_t  memfile_func = {
        .read  = memfile_read,
        .write = memfile_write,
        .seek  = memfile_seek,
        .close = memfile_close
    };
    FILE *stream;
    struct memfile_cookie mycookie;
    ssize_t nread;
    long p;
    int j;
    char buf[1000];
    /* Устанавливаем cookie перед вызовом fopencookie() */
    mycookie.buf = malloc(INIT_BUF_SIZE);
    if (mycookie.buf == NULL) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    mycookie.allocated = INIT_BUF_SIZE;
    mycookie.offset = 0;
    mycookie.endpos = 0;
    stream = fopencookie(&mycookie,"w+", memfile_func);
    if (stream == NULL) {
        perror("fopencookie");
        exit(EXIT_FAILURE);
    }
    /* Записываем аргументы командной строки в файл */
    for (j = 1; j < argc; j++)
        if (fputs(argv[j], stream) == EOF) {
            perror("fputs");
            exit(EXIT_FAILURE);
        }
    /* Читаем два байта из пяти пока не получим EOF */
    for (p = 0; ; p += 5) {
        if (fseek(stream, p, SEEK_SET) == -1) {
            perror("fseek");
            exit(EXIT_FAILURE);
        }
        nread = fread(buf, 1, 2, stream);
        if (nread == -1) {
            perror("fread");
            exit(EXIT_FAILURE);
        }
        if (nread == 0) {
            printf("Достигнут конец файла\n");
            break;
        }
        printf("/%.*s/\n", nread, buf);
    }
    exit(EXIT_SUCCESS);
}