xref: /illumos-gate/usr/src/lib/libc/port/stdio/fmemopen.c (revision cd62a92d)
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