| .\" Copyright (c) 2008, Linux Foundation, written by Michael Kerrisk |
| .\" <mtk.manpages@gmail.com> |
| .\" |
| .\" %%%LICENSE_START(VERBATIM) |
| .\" Permission is granted to make and distribute verbatim copies of this |
| .\" manual provided the copyright notice and this permission notice are |
| .\" preserved on all copies. |
| .\" |
| .\" Permission is granted to copy and distribute modified versions of this |
| .\" manual under the conditions for verbatim copying, provided that the |
| .\" entire resulting derived work is distributed under the terms of a |
| .\" permission notice identical to this one. |
| .\" |
| .\" Since the Linux kernel and libraries are constantly changing, this |
| .\" manual page may be incorrect or out-of-date. The author(s) assume no |
| .\" responsibility for errors or omissions, or for damages resulting from |
| .\" the use of the information contained herein. The author(s) may not |
| .\" have taken the same level of care in the production of this manual, |
| .\" which is licensed free of charge, as they might when working |
| .\" professionally. |
| .\" |
| .\" Formatted or processed versions of this manual, if unaccompanied by |
| .\" the source, must acknowledge the copyright and authors of this work. |
| .\" %%%LICENSE_END |
| .\" |
| .TH FOPENCOOKIE 3 2021-03-22 "Linux" "Linux Programmer's Manual" |
| .SH NAME |
| fopencookie \- opening a custom stream |
| .SH SYNOPSIS |
| .nf |
| .BR "#define _GNU_SOURCE" " /* See feature_test_macros(7) */" |
| .B #include <stdio.h> |
| .PP |
| .BI "FILE *fopencookie(void *restrict " cookie ", const char *restrict " mode , |
| .BI " cookie_io_functions_t " io_funcs ); |
| .fi |
| .SH DESCRIPTION |
| The |
| .BR fopencookie () |
| function allows the programmer to create a custom implementation |
| for a standard I/O stream. |
| This implementation can store the stream's data at a location of |
| its own choosing; for example, |
| .BR fopencookie () |
| is used to implement |
| .BR fmemopen (3), |
| which provides a stream interface to data that is stored in a |
| buffer in memory. |
| .PP |
| In order to create a custom stream the programmer must: |
| .IP * 3 |
| Implement four "hook" functions that are used internally by the |
| standard I/O library when performing I/O on the stream. |
| .IP * |
| Define a "cookie" data type, |
| a structure that provides bookkeeping information |
| (e.g., where to store data) used by the aforementioned hook functions. |
| The standard I/O package knows nothing about the contents of this cookie |
| (thus it is typed as |
| .IR "void\ *" |
| when passed to |
| .BR fopencookie ()), |
| but automatically supplies the cookie |
| as the first argument when calling the hook functions. |
| .IP * |
| Call |
| .BR fopencookie () |
| to open a new stream and associate the cookie and hook functions |
| with that stream. |
| .PP |
| The |
| .BR fopencookie () |
| function serves a purpose similar to |
| .BR fopen (3): |
| it opens a new stream and returns a pointer to a |
| .I FILE |
| object that is used to operate on that stream. |
| .PP |
| The |
| .I cookie |
| argument is a pointer to the caller's cookie structure |
| that is to be associated with the new stream. |
| This pointer is supplied as the first argument when the standard I/O |
| library invokes any of the hook functions described below. |
| .PP |
| The |
| .I mode |
| argument serves the same purpose as for |
| .BR fopen (3). |
| The following modes are supported: |
| .IR r , |
| .IR w , |
| .IR a , |
| .IR r+ , |
| .IR w+ , |
| and |
| .IR a+ . |
| See |
| .BR fopen (3) |
| for details. |
| .PP |
| The |
| .I io_funcs |
| argument is a structure that contains four fields pointing to the |
| programmer-defined hook functions that are used to implement this stream. |
| The structure is defined as follows |
| .PP |
| .in +4n |
| .EX |
| 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; |
| .EE |
| .in |
| .PP |
| The four fields are as follows: |
| .TP |
| .I cookie_read_function_t *read |
| This function implements read operations for the stream. |
| When called, it receives three arguments: |
| .IP |
| ssize_t read(void *cookie, char *buf, size_t size); |
| .IP |
| The |
| .I buf |
| and |
| .I size |
| arguments are, respectively, |
| a buffer into which input data can be placed and the size of that buffer. |
| As its function result, the |
| .I read |
| function should return the number of bytes copied into |
| .IR buf , |
| 0 on end of file, or \-1 on error. |
| The |
| .I read |
| function should update the stream offset appropriately. |
| .IP |
| If |
| .I *read |
| is a null pointer, |
| then reads from the custom stream always return end of file. |
| .TP |
| .I cookie_write_function_t *write |
| This function implements write operations for the stream. |
| When called, it receives three arguments: |
| .IP |
| ssize_t write(void *cookie, const char *buf, size_t size); |
| .IP |
| The |
| .I buf |
| and |
| .I size |
| arguments are, respectively, |
| a buffer of data to be output to the stream and the size of that buffer. |
| As its function result, the |
| .I write |
| function should return the number of bytes copied from |
| .IR buf , |
| or 0 on error. |
| (The function must not return a negative value.) |
| The |
| .I write |
| function should update the stream offset appropriately. |
| .IP |
| If |
| .I *write |
| is a null pointer, |
| then output to the stream is discarded. |
| .TP |
| .I cookie_seek_function_t *seek |
| This function implements seek operations on the stream. |
| When called, it receives three arguments: |
| .IP |
| int seek(void *cookie, off64_t *offset, int whence); |
| .IP |
| The |
| .I *offset |
| argument specifies the new file offset depending on which |
| of the following three values is supplied in |
| .IR whence : |
| .RS |
| .TP |
| .B SEEK_SET |
| The stream offset should be set |
| .I *offset |
| bytes from the start of the stream. |
| .TP |
| .B SEEK_CUR |
| .I *offset |
| should be added to the current stream offset. |
| .TP |
| .B SEEK_END |
| The stream offset should be set to the size of the stream plus |
| .IR *offset . |
| .RE |
| .IP |
| Before returning, the |
| .I seek |
| function should update |
| .I *offset |
| to indicate the new stream offset. |
| .IP |
| As its function result, the |
| .I seek |
| function should return 0 on success, and \-1 on error. |
| .IP |
| If |
| .I *seek |
| is a null pointer, |
| then it is not possible to perform seek operations on the stream. |
| .TP |
| .I cookie_close_function_t *close |
| This function closes the stream. |
| The hook function can do things such as freeing buffers allocated |
| for the stream. |
| When called, it receives one argument: |
| .IP |
| int close(void *cookie); |
| .IP |
| The |
| .I cookie |
| argument is the cookie that the programmer supplied when calling |
| .BR fopencookie (). |
| .IP |
| As its function result, the |
| .I close |
| function should return 0 on success, and |
| .B EOF |
| on error. |
| .IP |
| If |
| .I *close |
| is NULL, then no special action is performed when the stream is closed. |
| .SH RETURN VALUE |
| On success |
| .BR fopencookie () |
| returns a pointer to the new stream. |
| On error, NULL is returned. |
| .\" .SH ERRORS |
| .\" It's not clear if errno ever gets set... |
| .SH ATTRIBUTES |
| For an explanation of the terms used in this section, see |
| .BR attributes (7). |
| .ad l |
| .nh |
| .TS |
| allbox; |
| lbx lb lb |
| l l l. |
| Interface Attribute Value |
| T{ |
| .BR fopencookie () |
| T} Thread safety MT-Safe |
| .TE |
| .hy |
| .ad |
| .sp 1 |
| .SH CONFORMING TO |
| This function is a nonstandard GNU extension. |
| .SH EXAMPLES |
| The program below implements a custom stream whose functionality |
| is similar (but not identical) to that available via |
| .BR fmemopen (3). |
| It implements a stream whose data is stored in a memory buffer. |
| The program writes its command-line arguments to the stream, |
| and then seeks through the stream reading two out of every |
| five characters and writing them to standard output. |
| The following shell session demonstrates the use of the program: |
| .PP |
| .in +4n |
| .EX |
| .RB "$" " ./a.out \(aqhello world\(aq" |
| /he/ |
| / w/ |
| /d/ |
| Reached end of file |
| .EE |
| .in |
| .PP |
| Note that a more general version of the program below |
| could be improved to more robustly handle various error situations |
| (e.g., opening a stream with a cookie that already has an open stream; |
| closing a stream that has already been closed). |
| .SS Program source |
| \& |
| .EX |
| #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\en"); |
| exit(EXIT_FAILURE); |
| } |
| printf("Reached end of file\en"); |
| break; |
| } |
| |
| printf("/%.*s/\en", (int) nread, buf); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| .EE |
| .SH SEE ALSO |
| .BR fclose (3), |
| .BR fmemopen (3), |
| .BR fopen (3), |
| .BR fseek (3) |