1 /* fopencookie.c -- implement GNU-style fopencookie() with BSD-style funopen()
2  *
3  * Todo: handle "a+" mode (probably by keeping read and write file pointers and
4  * calling seek() before calling read or write)
5  *
6  * Todo: Is this solution, calling readfn() which in turn calls
7  * sc->read() with some typecasts, better than ignoring the compiler
8  * warnings and calling sc->read directly?
9  *
10  * Created: 22 August 2011
11  * Author: Bert Bos <bert@w3.org>
12  *
13  * Copyright © 2011 World Wide Web Consortium
14  * See http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
15  */
16 
17 #include "config.h"
18 #if HAVE_LIBCURL		/* We only need this if we're using libcurl */
19 #if !HAVE_FOPENCOOKIE		/* We don't need this on GNU Linux */
20 
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <errno.h>
24 #include <stdlib.h>
25 #include "errexit.e"
26 #include "fopencookie.h"
27 
28 typedef struct {
29   cookie_read_function_t *read;
30   cookie_write_function_t *write;
31   cookie_seek_function_t *seek;
32   cookie_close_function_t *close;
33   void *cookie;
34 } cookiewrapper;
35 
36 
37 /* readfn -- callback that in turn calls sc->read with proper typecasts */
readfn(void * sc,char * buf,int n)38 static int readfn(void *sc, char *buf, int n)
39 {
40   cookiewrapper *c = (cookiewrapper*)sc;
41   return (int)(c->read(c->cookie, buf, (size_t)n));
42 }
43 
44 /* writefn -- callback that in turn calls sc->write with proper typecasts */
writefn(void * sc,const char * buf,int n)45 static int writefn(void *sc, const char *buf, int n)
46 {
47   cookiewrapper *c = (cookiewrapper*)sc;
48   return (int)(c->write(c->cookie, buf, (size_t)n));
49 }
50 
51 /* seekfn -- callback that in turn calls sc->seek with proper typecasts */
seekfn(void * sc,fpos_t offset,int whence)52 static fpos_t seekfn(void *sc, fpos_t offset, int whence)
53 {
54   cookiewrapper *c = (cookiewrapper*)sc;
55   return (fpos_t)(c->seek(c->cookie, (off64_t)offset, whence));
56 }
57 
58 /* closefn -- callback that in turn calls sc->close and then frees memory */
closefn(void * sc)59 static int closefn(void *sc)
60 {
61   cookiewrapper *c = (cookiewrapper*)sc;
62   int r = c->close ? c->close(c->cookie) : 0;
63   free(sc);
64   return r;
65 }
66 
67 
68 /* fopencookie -- open a stream defined by four callback functions */
fopencookie(void * cookie,const char * mode,cookie_io_functions_t funcs)69 FILE *fopencookie(void *cookie, const char *mode,
70 			 cookie_io_functions_t funcs)
71 {
72   cookiewrapper *s;
73   int mask;			/* 1 = read, 2 = write, 4 = append */
74   FILE *f;
75 
76   /* Check that the parameters make sense */
77   if (!mode) {errno = EINVAL; return NULL;}
78 
79   if (mode[0] == 'r') mask = 1;
80   else if (mode[0] == 'w') mask = 2;
81   else if (mode[0] == 'a') mask = 4;
82   else {errno = EINVAL; return NULL;}
83   if (mode[1] == '+' || (mode[1] =='b' && mode[2] == '+')) mask |= 3;
84 
85   if ((mask & 1) && !funcs.read) {errno = EINVAL; return NULL;}
86   if ((mask & 2) && !funcs.write) {errno = EINVAL; return NULL;}
87   if ((mask & 4) && !funcs.seek) {errno = EINVAL; return NULL;}
88 
89   if (mask == 7) errexit("Bug: fopencookie() can't yet handle mode \"a+\"\n");
90 
91   /* Open the "file" */
92   if (!(s = malloc(sizeof(*s)))) {errno = ENOMEM; return NULL;}
93   s->read = funcs.read;
94   s->write = funcs.write;
95   s->seek = funcs.seek;
96   s->close = funcs.close;
97   s->cookie = cookie;
98 
99   f = funopen(s,
100 	      s->read ? readfn : NULL,
101 	      s->write ? writefn : NULL,
102 	      s->seek ? seekfn : NULL,
103 	      closefn);
104   if (!f) {free(s); return NULL;}
105 
106   /* If the mode is "a" (append), position the file pointer at the end */
107   if ((mask & 4) && fseek(f, 0L, SEEK_END) < 0) {fclose(f); return NULL;}
108 
109   return f;
110 }
111 
112 #endif /* !HAVE_FOPENCOOKIE */
113 #endif /* HAVE_LIBCURL */
114