1 /*
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright © 2019 Keith Packard
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 *
18 * 3. Neither the name of the copyright holder nor the names of its
19 * contributors may be used to endorse or promote products derived
20 * from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33 * OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36 #include "stdio_private.h"
37
38 /* Buffered I/O routines for tiny stdio */
39
40 int
__bufio_flush_locked(FILE * f)41 __bufio_flush_locked(FILE *f)
42 {
43 struct __file_bufio *bf = (struct __file_bufio *) f;
44 char *buf;
45 off_t backup;
46
47 switch (bf->dir) {
48 case __SWR:
49 /* Flush everything, drop contents if that doesn't work */
50 buf = bf->buf;
51 while (bf->len) {
52 ssize_t this = (bf->write) (bf->fd, buf, bf->len);
53 if (this <= 0) {
54 bf->len = 0;
55 return _FDEV_ERR;
56 break;
57 }
58 bf->pos += this;
59 bf->len -= this;
60 }
61 break;
62 case __SRD:
63 /* Move the FD back to the current read position */
64 backup = bf->len - bf->off;
65 if (backup) {
66 bf->pos -= backup;
67 if (bf->lseek)
68 (void) (bf->lseek)(bf->fd, bf->pos, SEEK_SET);
69 }
70 bf->len = 0;
71 bf->off = 0;
72 break;
73 default:
74 break;
75 }
76 return 0;
77 }
78
79
__bufio_fill_locked(FILE * f)80 int __bufio_fill_locked(FILE *f)
81 {
82 struct __file_bufio *bf = (struct __file_bufio *) f;
83 ssize_t len;
84
85 /* Reset read pointer, read some data */
86 bf->off = 0;
87 len = (bf->read)(bf->fd, bf->buf, bf->size);
88
89 if (len <= 0) {
90 bf->len = 0;
91 if (len < 0)
92 return _FDEV_ERR;
93 else
94 return _FDEV_EOF;
95 }
96
97 /* Update FD pos */
98 bf->len = len;
99 bf->pos += len;
100 return 0;
101 }
102
103 int
__bufio_flush(FILE * f)104 __bufio_flush(FILE *f)
105 {
106 int ret;
107
108 __bufio_lock(f);
109 ret = __bufio_flush_locked(f);
110 __bufio_unlock(f);
111 return ret;
112 }
113
114 /* Set I/O direction, flushing when it changes */
115 int
__bufio_setdir_locked(FILE * f,uint8_t dir)116 __bufio_setdir_locked(FILE *f, uint8_t dir)
117 {
118 struct __file_bufio *bf = (struct __file_bufio *) f;
119 int ret = 0;
120
121 if (bf->dir != dir) {
122 ret = __bufio_flush_locked(f);
123 bf->dir = dir;
124 }
125 return ret;
126 }
127
128 int
__bufio_put(char c,FILE * f)129 __bufio_put(char c, FILE *f)
130 {
131 struct __file_bufio *bf = (struct __file_bufio *) f;
132 int ret = (unsigned char) c;
133
134 __bufio_lock(f);
135 if (__bufio_setdir_locked(f, __SWR) < 0) {
136 ret = _FDEV_ERR;
137 goto bail;
138 }
139
140 bf->buf[bf->len++] = c;
141
142 /* flush if full, or if sending newline when linebuffered */
143 if (bf->len >= bf->size || (c == '\n' && (bf->bflags & __BLBF)))
144 if (__bufio_flush_locked(f) < 0)
145 ret = _FDEV_ERR;
146
147 bail:
148 __bufio_unlock(f);
149 return ret;
150 }
151
152 extern FILE *const stdin _ATTRIBUTE((__weak__));
153 extern FILE *const stdout _ATTRIBUTE((__weak__));
154
155 int
__bufio_get(FILE * f)156 __bufio_get(FILE *f)
157 {
158 struct __file_bufio *bf = (struct __file_bufio *) f;
159 int ret;
160 bool flushed = false;
161
162 again:
163 __bufio_lock(f);
164 if (__bufio_setdir_locked(f, __SRD) < 0) {
165 ret = _FDEV_ERR;
166 goto bail;
167 }
168
169 if (bf->off >= bf->len) {
170
171 /* Flush stdout if reading from stdin */
172 if (f == stdin && !flushed && stdout != NULL) {
173 flushed = true;
174 __bufio_unlock(f);
175 fflush(stdout);
176 goto again;
177 }
178
179 ret = __bufio_fill_locked(f);
180 if (ret)
181 goto bail;
182 }
183
184 /*
185 * Cast to unsigned avoids sign-extending chars with high-bit
186 * set
187 */
188 ret = (unsigned char) bf->buf[bf->off++];
189 bail:
190 __bufio_unlock(f);
191 return ret;
192 }
193
194 off_t
__bufio_seek(FILE * f,off_t offset,int whence)195 __bufio_seek(FILE *f, off_t offset, int whence)
196 {
197 struct __file_bufio *bf = (struct __file_bufio *) f;
198 off_t ret;
199
200 if (!bf->lseek)
201 return _FDEV_ERR;
202
203 __bufio_lock(f);
204 if (__bufio_setdir_locked(f, __SRD) < 0) {
205 ret = _FDEV_ERR;
206 } else {
207 /* compute offset of the first char in the buffer */
208 __off_t buf_pos = bf->pos - bf->len;
209
210 switch (whence) {
211 case SEEK_CUR:
212 /* Map CUR -> SET, accounting for position within buffer */
213 whence = SEEK_SET;
214 offset += buf_pos + bf->off;
215 __PICOLIBC_FALLTHROUGH;
216 case SEEK_SET:
217 /* Optimize for seeks within buffer or just past buffer */
218 if (buf_pos <= offset && offset <= buf_pos + bf->len) {
219 bf->off = offset - buf_pos;
220 ret = offset;
221 break;
222 }
223 __PICOLIBC_FALLTHROUGH;
224 default:
225 ret = (bf->lseek)(bf->fd, offset, whence);
226 if (ret >= 0)
227 bf->pos = ret;
228 /* Flush any buffered data after a real seek */
229 bf->len = 0;
230 bf->off = 0;
231 break;
232 }
233 }
234 __bufio_unlock(f);
235 return ret;
236 }
237
238 int
__bufio_setvbuf(FILE * f,char * buf,int mode,size_t size)239 __bufio_setvbuf(FILE *f, char *buf, int mode, size_t size)
240 {
241 struct __file_bufio *bf = (struct __file_bufio *) f;
242 int ret = -1;
243
244 __bufio_lock(f);
245 bf->bflags &= ~__BLBF;
246 switch (mode) {
247 case _IONBF:
248 buf = NULL;
249 size = 1;
250 break;
251 case _IOLBF:
252 bf->bflags |= __BLBF;
253 break;
254 case _IOFBF:
255 break;
256 default:
257 goto bail;
258 }
259 if (bf->bflags & __BALL) {
260 if (buf) {
261 free(bf->buf);
262 bf->bflags &= ~__BALL;
263 } else {
264 /*
265 * Handling allocation failures here is a bit tricky;
266 * we don't want to lose the existing buffer. Instead,
267 * we try to reallocate it
268 */
269 buf = realloc(bf->buf, size);
270 if (!buf)
271 goto bail;
272 }
273 } else if (!buf) {
274 buf = malloc(size);
275 if (!buf)
276 goto bail;
277 bf->bflags |= __BALL;
278 }
279 bf->buf = buf;
280 bf->size = size;
281 ret = 0;
282 bail:
283 __bufio_unlock(f);
284 return ret;
285 }
286
287 int
__bufio_close(FILE * f)288 __bufio_close(FILE *f)
289 {
290 struct __file_bufio *bf = (struct __file_bufio *) f;
291 int ret = 0;
292
293 __bufio_lock(f);
294 ret = __bufio_flush_locked(f);
295
296 if (bf->bflags & __BALL)
297 free(bf->buf);
298
299 __bufio_lock_close(f);
300 /* Don't close stdin/stdout/stderr fds */
301 if (bf->fd > 2)
302 (bf->close)(bf->fd);
303 free(f);
304 return ret;
305 }
306
307