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 int
__bufio_flush_locked(FILE * f)39 __bufio_flush_locked(FILE *f)
40 {
41 	struct __file_bufio *bf = (struct __file_bufio *) f;
42         char *buf;
43         off_t backup;
44 
45         switch (bf->dir) {
46         case __SWR:
47 		/* Flush everything, drop contents if that doesn't work */
48                 buf = bf->buf;
49 		while (bf->len) {
50                         ssize_t this = bufio_write(bf, buf, bf->len);
51 			if (this <= 0) {
52                                 bf->len = 0;
53                                 return _FDEV_ERR;
54                                 break;
55 			}
56 			bf->pos += this;
57 			bf->len -= this;
58 		}
59                 break;
60         case __SRD:
61                 /* Move the FD back to the current read position */
62                 backup = bf->len - bf->off;
63                 if (backup) {
64                         bf->pos -= backup;
65                         (void) bufio_lseek(bf, bf->pos, SEEK_SET);
66                 }
67                 bf->len = 0;
68                 bf->off = 0;
69                 break;
70         default:
71                 break;
72 	}
73 	return 0;
74 }
75 
76 
__bufio_fill_locked(FILE * f)77 int __bufio_fill_locked(FILE *f)
78 {
79 	struct __file_bufio *bf = (struct __file_bufio *) f;
80         ssize_t len;
81 
82         /* Reset read pointer, read some data */
83         bf->off = 0;
84         len = bufio_read (bf, bf->buf, bf->size);
85 
86         if (len <= 0) {
87                 bf->len = 0;
88                 if (len < 0)
89                         return _FDEV_ERR;
90                 else
91                         return _FDEV_EOF;
92         }
93 
94         /* Update FD pos */
95         bf->len = len;
96         bf->pos += len;
97         return 0;
98 }
99 
100 int
__bufio_flush(FILE * f)101 __bufio_flush(FILE *f)
102 {
103         int ret;
104 
105 	__bufio_lock(f);
106         ret = __bufio_flush_locked(f);
107 	__bufio_unlock(f);
108 	return ret;
109 }
110 
111 /* Set I/O direction, flushing when it changes */
112 int
__bufio_setdir_locked(FILE * f,uint8_t dir)113 __bufio_setdir_locked(FILE *f, uint8_t dir)
114 {
115 	struct __file_bufio *bf = (struct __file_bufio *) f;
116         int ret = 0;
117 
118         if (bf->dir != dir) {
119                 ret = __bufio_flush_locked(f);
120                 bf->dir = dir;
121         }
122         return ret;
123 }
124 
125 int
__bufio_put(char c,FILE * f)126 __bufio_put(char c, FILE *f)
127 {
128 	struct __file_bufio *bf = (struct __file_bufio *) f;
129         int ret = (unsigned char) c;
130 
131 	__bufio_lock(f);
132         if (__bufio_setdir_locked(f, __SWR) < 0) {
133                 ret = _FDEV_ERR;
134                 goto bail;
135         }
136 
137 	bf->buf[bf->len++] = c;
138 
139 	/* flush if full, or if sending newline when linebuffered */
140 	if (bf->len >= bf->size || (c == '\n' && (bf->bflags & __BLBF)))
141 		if (__bufio_flush_locked(f) < 0)
142                         ret = _FDEV_ERR;
143 
144 bail:
145 	__bufio_unlock(f);
146 	return ret;
147 }
148 
149 extern FILE *const stdin _ATTRIBUTE((__weak__));
150 extern FILE *const stdout _ATTRIBUTE((__weak__));
151 
152 int
__bufio_get(FILE * f)153 __bufio_get(FILE *f)
154 {
155 	struct __file_bufio *bf = (struct __file_bufio *) f;
156         int ret;
157         bool flushed = false;
158 
159 again:
160 	__bufio_lock(f);
161         if (__bufio_setdir_locked(f, __SRD) < 0) {
162                 ret = _FDEV_ERR;
163                 goto bail;
164         }
165 
166 	if (bf->off >= bf->len) {
167 
168 		/*
169                  * Flush stdout if reading from stdin.
170                  *
171                  * The odd-looking NULL address checks along with the
172                  * weak attributes for stdin and stdout above avoids
173                  * pulling in stdin/stdout definitions just for this
174                  * check.
175                  */
176                 if (!flushed) {
177                         flushed = true;
178                         if (&stdin != NULL && &stdout != NULL && f == stdin) {
179                                 __bufio_unlock(f);
180                                 fflush(stdout);
181                                 goto again;
182                         }
183 		}
184 
185                 ret = __bufio_fill_locked(f);
186                 if (ret)
187                     goto bail;
188 	}
189 
190 	/*
191 	 * Cast to unsigned avoids sign-extending chars with high-bit
192 	 * set
193 	 */
194 	ret = (unsigned char) bf->buf[bf->off++];
195 bail:
196 	__bufio_unlock(f);
197 	return ret;
198 }
199 
200 off_t
__bufio_seek(FILE * f,off_t offset,int whence)201 __bufio_seek(FILE *f, off_t offset, int whence)
202 {
203 	struct __file_bufio *bf = (struct __file_bufio *) f;
204 	off_t ret;
205 
206 	__bufio_lock(f);
207         if (__bufio_setdir_locked(f, __SRD) < 0) {
208                 ret = _FDEV_ERR;
209         } else {
210                 /* compute offset of the first char in the buffer */
211                 __off_t buf_pos = bf->pos - bf->len;
212 
213                 switch (whence) {
214                 case SEEK_CUR:
215                         /* Map CUR -> SET, accounting for position within buffer */
216                         whence = SEEK_SET;
217                         offset += buf_pos + bf->off;
218                         __PICOLIBC_FALLTHROUGH;
219                 case SEEK_SET:
220                         /* Optimize for seeks within buffer or just past buffer */
221                         if (buf_pos <= offset && offset <= buf_pos + bf->len) {
222                                 bf->off = offset - buf_pos;
223                                 ret = offset;
224                                 break;
225                         }
226                         __PICOLIBC_FALLTHROUGH;
227                 default:
228                         ret = bufio_lseek(bf, offset, whence);
229                         if (ret >= 0)
230                                 bf->pos = ret;
231                         /* Flush any buffered data after a real seek */
232                         bf->len = 0;
233                         bf->off = 0;
234                         break;
235                 }
236         }
237         __bufio_unlock(f);
238         return ret;
239 }
240 
241 int
__bufio_setvbuf(FILE * f,char * buf,int mode,size_t size)242 __bufio_setvbuf(FILE *f, char *buf, int mode, size_t size)
243 {
244 	struct __file_bufio *bf = (struct __file_bufio *) f;
245         int ret = -1;
246 
247 	__bufio_lock(f);
248         bf->bflags &= ~__BLBF;
249         switch (mode) {
250         case _IONBF:
251                 buf = NULL;
252                 size = 1;
253                 break;
254         case _IOLBF:
255                 bf->bflags |= __BLBF;
256                 break;
257         case _IOFBF:
258                 break;
259         default:
260                 goto bail;
261         }
262         if (bf->bflags & __BALL) {
263                 if (buf) {
264                         free(bf->buf);
265                         bf->bflags &= ~__BALL;
266                 } else {
267                         /*
268                          * Handling allocation failures here is a bit tricky;
269                          * we don't want to lose the existing buffer. Instead,
270                          * we try to reallocate it
271                          */
272                         buf = realloc(bf->buf, size);
273                         if (!buf)
274                                 goto bail;
275                 }
276         } else if (!buf) {
277                 buf = malloc(size);
278                 if (!buf)
279                         goto bail;
280                 bf->bflags |= __BALL;
281         }
282         bf->buf = buf;
283         bf->size = size;
284         ret = 0;
285 bail:
286         __bufio_unlock(f);
287         return ret;
288 }
289 
290 int
__bufio_close(FILE * f)291 __bufio_close(FILE *f)
292 {
293 	struct __file_bufio *bf = (struct __file_bufio *) f;
294 	int ret = 0;
295 
296 	__bufio_lock(f);
297         ret = __bufio_flush_locked(f);
298 
299         if (bf->bflags & __BALL)
300                 free(bf->buf);
301 
302 	__bufio_lock_close(f);
303 
304         /*
305          * Don't close the fd or free the FILE for things not
306          * generated by fopen or fdopen. These will usually be static
307          * FILE structs defined for stdin/stdout/stderr.
308          */
309         if (bf->bflags & __BFALL) {
310                 ret = bufio_close(bf);
311                 free(f);
312         }
313 	return ret;
314 }
315 
316