FOPENCOOKIE(3)

名前
fopencookie - 独自のストリームをオープンする

書式
#define _GNU_SOURCE         /* feature_test_macros(7) 参照 */
#include <stdio.h>

FILE *fopencookie(void *cookie, const char *mode,
                  cookie_io_functions_t io_funcs);

説明
fopencookie() を使うと、 プログラマーは標準 I/O ストリームの独自の実装を作成することができる。 この実装はストリームのデータを自分が選んだ場所に格納することができる。 例えば、 fopencookie() は fmemopen(3) を実装するのに使用されている。 fmemopen(3) はメモリー上のバッファーに格納されたデータに対するストリームインターフェースを提供している。

独自のストリームを作成するためには、 プログラマーは以下を行う必要がある。

*   ストリームに対する I/O を実行する際に標準 I/O ライブラリが内部で使用する 4 つの "フック" 関数を実装する。

*   "cookie" データ型を定義する。 "cookie" データ型は、上記のフック関数が使用する管理情報 (例えば、データを格納する場所など) を提供する構造体である。 標準の I/O パッケージにはこの cookie の内容に関する情報を持たないが (したがって fopencookie() に渡される際の型は void * である)、 フック関数が呼び出される際に第一引数として cookie が渡される。

*   fopencookie() を呼び出して、新しいストリームをオープンし、 そのストリームに cookie とフック関数を関連付ける。

fopencookie() 関数は fopen(3) と同様の機能を持つ。 新しいストリームをオープンし、 そのストリームに対して操作を行うのに使用する FILE オブジェクトへのポインターを返す。

cookie 引数は、 新しいストリームに関連付けられる呼び出し元の cookie 構造体へのポインターである。 このポインターは、 標準 I/O ライブラリが以下で説明するフック関数のいずれかを呼び出す際に第 1 引数として渡される。

mode 引数は fopen(3) と同じ意味を持つ。 指定できるモードは r, w, a, r+, w+, a+ である。 詳細は fopen(3) を参照。

io_funcs 引数は、 このストリームを実装するのに使用されるプログラマーが定義した関数を指す 4 つのフィールドを持つ構造体である。 この構造体は以下のように定義されている。

    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;

4 つのフィールドの詳細は以下のとおりである。

cookie_read_function_t *read
    この関数はストリームに対する read 操作を実装する。 呼び出される際、 3 つの引数を受け取る。

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

    引数 buf と size は、 それぞれ、 入力データを配置できるバッファーとそのバッファーのサイズである。 関数の結果として、 read 関数は buf にコピーされたバイト数を、 ファイル末尾の場合は 0 を、 エラーの場合は -1 を返す。 read 関数はストリームのオフセットを適切に更新すべきである。

    *read がヌルポインターの場合、 独自のストリームからの読み出しは常にファイル末尾 (end of file) を返す。

cookie_write_function_t *write
    この関数はストリームに対する write 操作を実装する。 呼び出される際、 3 つの引数を受け取る。

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

    引数 buf と size は、 それぞれ、 ストリームへの出力するデータが入ったバッファーとそのバッファーのサイズである。 関数の結果として、 write 関数は buf からコピーされたバイト数を返し、 エラーの場合は -1 を返す。 (この関数は負の値を返してはならない。) write 関数はストリームのオフセットを適切に更新すべきである。

    *write がヌルポインターの場合、 このストリームへの出力は破棄される。

cookie_seek_function_t *seek
    この関数はストリームに対する seek 操作を実装する。 呼び出される際、 3 つの引数を受け取る。

        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 がヌルポインターの場合、 このストリームに対して seek 操作を行うことができない。

cookie_close_function_t *close
    この関数はストリームをクローズする。 このフック関数では、 このストリームに割り当てられたバッファーを解放するといったことができる。 呼び出される際、 1 つの引数を受け取る。

        int close(void *cookie);

    cookie 引数は fopencookie() の呼び出し時にプログラマーが渡した cookie である。

    関数の結果として、 close 関数は成功すると 0 を、 エラーの場合 EOF を返す。

    *close が NULL の場合、 ストリームがクローズされる際に特別な操作は何も行われない。

返り値
成功すると fopencookie() は新しいストリームへのポインターを返す。 エラーの場合、 NULL が返される。

属性
この節で使用されている用語の説明については、 attributes(7) を参照。
インターフェース | 属性 | 値
fopencookie() | Thread safety | MT-Safe

準拠
この関数は非標準の GNU 拡張である。

例
以下のプログラムは、 fmemopen(3) で利用できるのと似た (同じではない) 機能を持つ独自のストリームを実装している。 データがメモリーバッファーに格納されるストリームを実装している。 このプログラムは、 コマンドライン引数をストリームに書き込み、 それからストリームをたどって 5 文字ごとに 2 文字を読み出して、 それを標準出力に書き込む。 以下のシェルセッションはこのプログラムの使用例である。

    $ ./a.out 'hello world'
    /he/
    / w/
    /d/
    Reached end of file

このプログラムを改良して様々なエラー状況に強くすることもできる (例えば、 オープン済みのストリームに対応する 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;        /* Dynamically sized buffer for data */
        size_t  allocated;  /* Size of buf */
        size_t  endpos;     /* Number of characters in buf */
        off_t   offset;     /* Current file offset in buf */
    };

    ssize_t
    memfile_write(void *c, const char *buf, size_t size)
    {
        char *new_buff;
        struct memfile_cookie *cookie = c;

        /* Buffer too small? Keep doubling size until big enough */

        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;

        /* Fetch minimum of bytes requested and bytes available */

        xbytes = size;
        if (cookie->offset + size > cookie->endpos)
            xbytes = cookie->endpos - cookie->offset;
        if (xbytes < 0)     /* offset may be past 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;
        size_t nread;
        char buf[1000];

        /* Set up the cookie before calling 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);
        }

        /* Write command-line arguments to our file */

        for (int j = 1; j < argc; j++)
            if (fputs(argv[j], stream) == EOF) {
                perror("fputs");
                exit(EXIT_FAILURE);
            }

        /* Read two bytes out of every five, until EOF */

        for (long p = 0; ; p += 5) {
            if (fseek(stream, p, SEEK_SET) == -1) {
                perror("fseek");
                exit(EXIT_FAILURE);
            }
            nread = fread(buf, 1, 2, stream);
            if (nread == 0) {
                if (ferror(stream) != 0) {
                    fprintf(stderr, "fread failed\n");
                    exit(EXIT_FAILURE);
                }
                printf("Reached end of file\n");
                break;
            }

            printf("/%.*s/\n", (int) nread, buf);
        }

        exit(EXIT_SUCCESS);
    }

関連項目
fclose(3), fmemopen(3), fopen(3), fseek(3)

この文書について
この man ページは Linux man-pages プロジェクトのリリース 5.10 の一部である。プロジェクトの説明とバグ報告に関する情報は https://www.kernel.org/doc/man-pages/ に書かれている。