1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2020 Robert Mustacchi
14 */
15
16 /*
17 * Implements fmemopen(3C).
18 */
19
20 #include "mtlib.h"
21 #include "file64.h"
22 #include <stdio.h>
23 #include "stdiom.h"
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <fcntl.h>
27 #include <sys/sysmacros.h>
28 #include <limits.h>
29
30 typedef enum fmemopen_flags {
31 /*
32 * Indicates that the user gave us the buffer and so we shouldn't free
33 * it.
34 */
35 FMO_F_USER_BUFFER = 1 << 0,
36 /*
37 * When the stream is open for update (a, a+) then we have to have
38 * slightly different behavior on write and zeroing the buffer.
39 */
40 FMO_F_APPEND = 1 << 1
41 } fmemopen_flags_t;
42
43 typedef struct fmemopen {
44 /*
45 * Pointer to the underlying memory stream.
46 */
47 char *fmo_buf;
48 /*
49 * Allocated length of the buffer.
50 */
51 size_t fmo_alloc;
52 /*
53 * Current position of the buffer.
54 */
55 size_t fmo_pos;
56 /*
57 * Current 'size' of the buffer. POSIX describes a size that the buffer
58 * has which is separate from the allocated size, but cannot exceed it.
59 */
60 size_t fmo_lsize;
61 fmemopen_flags_t fmo_flags;
62 } fmemopen_t;
63
64 static ssize_t
fmemopen_read(FILE * iop,char * buf,size_t nbytes)65 fmemopen_read(FILE *iop, char *buf, size_t nbytes)
66 {
67 fmemopen_t *fmp = _xdata(iop);
68
69 nbytes = MIN(nbytes, fmp->fmo_lsize - fmp->fmo_pos);
70 if (nbytes == 0) {
71 return (0);
72 }
73
74 (void) memcpy(buf, fmp->fmo_buf, nbytes);
75 fmp->fmo_pos += nbytes;
76
77 return (nbytes);
78 }
79
80 static ssize_t
fmemopen_write(FILE * iop,const char * buf,size_t nbytes)81 fmemopen_write(FILE *iop, const char *buf, size_t nbytes)
82 {
83 size_t npos;
84 fmemopen_t *fmp = _xdata(iop);
85
86 if ((fmp->fmo_flags & FMO_F_APPEND) != 0) {
87 /*
88 * POSIX says that if append mode is in effect, we must always
89 * seek to the logical size. This effectively is mimicking the
90 * O_APPEND behavior.
91 */
92 fmp->fmo_pos = fmp->fmo_lsize;
93 }
94
95 if (nbytes == 0) {
96 return (0);
97 } else if (nbytes >= SSIZE_MAX) {
98 errno = EINVAL;
99 return (-1);
100 }
101
102 npos = fmp->fmo_pos + nbytes;
103 if (npos < nbytes) {
104 errno = EOVERFLOW;
105 return (-1);
106 } else if (npos > fmp->fmo_alloc) {
107 nbytes = fmp->fmo_alloc - fmp->fmo_pos;
108 }
109
110 (void) memcpy(&fmp->fmo_buf[fmp->fmo_pos], buf, nbytes);
111 fmp->fmo_pos += nbytes;
112
113 if (fmp->fmo_pos > fmp->fmo_lsize) {
114 fmp->fmo_lsize = fmp->fmo_pos;
115
116 /*
117 * POSIX distinguishes behavior for writing a NUL in these
118 * streams. Basically if we are open for update and we are at
119 * the end of the buffer, we don't place a NUL. Otherwise, we
120 * always place one at the current position (or the end if we
121 * were over the edge).
122 */
123 if (fmp->fmo_lsize < fmp->fmo_alloc) {
124 fmp->fmo_buf[fmp->fmo_lsize] = '\0';
125 } else if ((fmp->fmo_flags & FMO_F_APPEND) == 0) {
126 fmp->fmo_buf[fmp->fmo_alloc - 1] = '\0';
127 }
128 }
129
130 return (nbytes);
131 }
132
133 static off_t
fmemopen_seek(FILE * iop,off_t off,int whence)134 fmemopen_seek(FILE *iop, off_t off, int whence)
135 {
136 fmemopen_t *fmp = _xdata(iop);
137 size_t base, npos;
138
139 switch (whence) {
140 case SEEK_SET:
141 base = 0;
142 break;
143 case SEEK_CUR:
144 base = fmp->fmo_pos;
145 break;
146 case SEEK_END:
147 base = fmp->fmo_lsize;
148 break;
149 default:
150 errno = EINVAL;
151 return (-1);
152 }
153
154 if (!memstream_seek(base, off, fmp->fmo_alloc, &npos)) {
155 errno = EINVAL;
156 return (-1);
157 }
158 fmp->fmo_pos = npos;
159
160 return ((off_t)fmp->fmo_pos);
161 }
162
163 static void
fmemopen_free(fmemopen_t * fmp)164 fmemopen_free(fmemopen_t *fmp)
165 {
166 if (fmp->fmo_buf != NULL &&
167 (fmp->fmo_flags & FMO_F_USER_BUFFER) == 0) {
168 free(fmp->fmo_buf);
169 }
170
171 free(fmp);
172 }
173
174 static int
fmemopen_close(FILE * iop)175 fmemopen_close(FILE *iop)
176 {
177 fmemopen_t *fmp = _xdata(iop);
178 fmemopen_free(fmp);
179 _xunassoc(iop);
180 return (0);
181 }
182
183 FILE *
fmemopen(void * _RESTRICT_KYWD buf,size_t size,const char * _RESTRICT_KYWD mode)184 fmemopen(void *_RESTRICT_KYWD buf, size_t size,
185 const char *_RESTRICT_KYWD mode)
186 {
187 int oflags, fflags, err;
188 fmemopen_t *fmp;
189 FILE *iop;
190
191 if (size == 0 || mode == NULL) {
192 errno = EINVAL;
193 return (NULL);
194 }
195
196 if (_stdio_flags(mode, &oflags, &fflags) != 0) {
197 /* errno set for us */
198 return (NULL);
199 }
200
201 /*
202 * buf is only allowed to be NULL if the '+' is specified. If the '+'
203 * mode was specified, then we'll have fflags set to _IORW.
204 */
205 if (buf == NULL && fflags != _IORW) {
206 errno = EINVAL;
207 return (NULL);
208 }
209
210 if ((fmp = calloc(1, sizeof (fmemopen_t))) == NULL) {
211 errno = ENOMEM;
212 return (NULL);
213 }
214
215 if (buf == NULL) {
216 fmp->fmo_buf = calloc(size, sizeof (uint8_t));
217 if (fmp->fmo_buf == NULL) {
218 errno = ENOMEM;
219 goto cleanup;
220 }
221 } else {
222 fmp->fmo_buf = buf;
223 fmp->fmo_flags |= FMO_F_USER_BUFFER;
224 }
225
226 fmp->fmo_alloc = size;
227
228 /*
229 * Set the initial logical size and position depending on whether we're
230 * using r(+), w(+), and a(+). The latter two are identified by O_TRUNC
231 * and O_APPEND in oflags.
232 */
233 if ((oflags & O_APPEND) != 0) {
234 fmp->fmo_pos = strnlen(fmp->fmo_buf, fmp->fmo_alloc);
235 fmp->fmo_lsize = fmp->fmo_pos;
236 fmp->fmo_flags |= FMO_F_APPEND;
237 } else if ((oflags & O_TRUNC) != 0) {
238 fmp->fmo_buf[0] = '\0';
239 fmp->fmo_pos = 0;
240 fmp->fmo_lsize = 0;
241 } else {
242 fmp->fmo_pos = 0;
243 fmp->fmo_lsize = size;
244 }
245
246 iop = _findiop();
247 if (iop == NULL) {
248 goto cleanup;
249 }
250
251 #ifdef _LP64
252 iop->_flag = (iop->_flag & ~_DEF_FLAG_MASK) | fflags;
253 #else
254 iop->_flag = fflags;
255 #endif
256 if (_xassoc(iop, fmemopen_read, fmemopen_write, fmemopen_seek,
257 fmemopen_close, fmp) != 0) {
258 goto cleanup;
259 }
260
261 SET_SEEKABLE(iop);
262
263 return (iop);
264
265 cleanup:
266 err = errno;
267 fmemopen_free(fmp);
268 errno = err;
269 return (NULL);
270 }
271