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