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-bufio.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <stdbool.h>
40 
41 /* Buffered I/O routines for tiny stdio */
42 
43 static int
__bufio_flush_locked(FILE * f)44 __bufio_flush_locked(FILE *f)
45 {
46 	struct __file_bufio *bf = (struct __file_bufio *) f;
47         char *buf;
48         int ret = 0;
49         off_t backup;
50 
51         switch (bf->dir) {
52         case __SWR:
53 		/* Flush everything, drop contents if that doesn't work */
54                 buf = bf->buf;
55 		while (bf->len) {
56                         ssize_t this = (bf->write) (bf->fd, buf, bf->len);
57 			if (this <= 0) {
58                                 bf->len = 0;
59                                 ret = -1;
60                                 break;
61 			}
62 			bf->pos += this;
63 			bf->len -= this;
64 		}
65                 break;
66         case __SRD:
67                 /* Move the FD back to the current read position */
68                 backup = bf->len - bf->off;
69                 if (backup) {
70                         bf->pos -= backup;
71                         if (bf->lseek)
72                                 (void) (bf->lseek)(bf->fd, bf->pos, SEEK_SET);
73                 }
74                 bf->len = 0;
75                 bf->off = 0;
76                 break;
77         default:
78                 break;
79 	}
80 	return ret;
81 }
82 
83 int
__bufio_flush(FILE * f)84 __bufio_flush(FILE *f)
85 {
86         int ret;
87 
88 	__bufio_lock(f);
89         ret = __bufio_flush_locked(f);
90 	__bufio_unlock(f);
91 	return ret;
92 }
93 
94 /* Set I/O direction, flushing when it changes */
95 static int
__bufio_setdir_locked(FILE * f,uint8_t dir)96 __bufio_setdir_locked(FILE *f, uint8_t dir)
97 {
98 	struct __file_bufio *bf = (struct __file_bufio *) f;
99         int ret = 0;
100 
101         if (bf->dir != dir) {
102                 ret = __bufio_flush_locked(f);
103                 bf->dir = dir;
104         }
105         return ret;
106 }
107 
108 int
__bufio_put(char c,FILE * f)109 __bufio_put(char c, FILE *f)
110 {
111 	struct __file_bufio *bf = (struct __file_bufio *) f;
112         int ret = (unsigned char) c;
113 
114 	__bufio_lock(f);
115         if (__bufio_setdir_locked(f, __SWR) < 0) {
116                 ret = _FDEV_ERR;
117                 goto bail;
118         }
119 
120 	bf->buf[bf->len++] = c;
121 
122 	/* flush if full, or if sending newline when linebuffered */
123 	if (bf->len >= bf->size || (c == '\n' && (bf->bflags & __BLBF)))
124 		if (__bufio_flush_locked(f) < 0)
125                         ret = _FDEV_ERR;
126 
127 bail:
128 	__bufio_unlock(f);
129 	return ret;
130 }
131 
132 int
__bufio_get(FILE * f)133 __bufio_get(FILE *f)
134 {
135 	struct __file_bufio *bf = (struct __file_bufio *) f;
136         int ret;
137         bool flushed = false;
138 
139 again:
140 	__bufio_lock(f);
141         if (__bufio_setdir_locked(f, __SRD) < 0) {
142                 ret = _FDEV_ERR;
143                 goto bail;
144         }
145 
146 	if (bf->off >= bf->len) {
147 
148 		/* Flush stdout if reading from stdin */
149 		if (f == stdin && !flushed) {
150                         flushed = true;
151 			__bufio_unlock(f);
152 			fflush(stdout);
153                         goto again;
154 		}
155 
156 		/* Reset read pointer, read some data */
157 		bf->off = 0;
158 		bf->len = (bf->read)(bf->fd, bf->buf, bf->size);
159 
160 		if (bf->len <= 0) {
161 			bf->len = 0;
162                         ret = _FDEV_EOF;
163                         goto bail;
164 		}
165 
166                 /* Update FD pos */
167                 bf->pos += bf->len;
168 	}
169 
170 	/*
171 	 * Cast to unsigned avoids sign-extending chars with high-bit
172 	 * set
173 	 */
174 	ret = (unsigned char) bf->buf[bf->off++];
175 bail:
176 	__bufio_unlock(f);
177 	return ret;
178 }
179 
180 off_t
__bufio_seek(FILE * f,off_t offset,int whence)181 __bufio_seek(FILE *f, off_t offset, int whence)
182 {
183 	struct __file_bufio *bf = (struct __file_bufio *) f;
184 	off_t ret;
185 
186 	__bufio_lock(f);
187         if (__bufio_setdir_locked(f, 0) < 0)
188                 return _FDEV_ERR;
189         if (bf->lseek) {
190                 if (whence == SEEK_CUR) {
191                         whence = SEEK_SET;
192                         offset += bf->pos;
193                 }
194                 ret = (bf->lseek)(bf->fd, offset, whence);
195         } else
196                 ret = _FDEV_ERR;
197         if (ret >= 0)
198                 bf->pos = ret;
199         __bufio_unlock(f);
200         return ret;
201 }
202 
203 int
__bufio_setvbuf(FILE * f,char * buf,int mode,size_t size)204 __bufio_setvbuf(FILE *f, char *buf, int mode, size_t size)
205 {
206 	struct __file_bufio *bf = (struct __file_bufio *) f;
207         int ret = 0;
208 
209 	__bufio_lock(f);
210         bf->bflags &= ~__BLBF;
211         switch (mode) {
212         case _IONBF:
213                 buf = NULL;
214                 size = 1;
215                 break;
216         case _IOLBF:
217                 bf->bflags |= __BLBF;
218                 break;
219         case _IOFBF:
220                 break;
221         default:
222                 ret = -1;
223                 goto bail;
224         }
225         if (bf->bflags & __BALL) {
226                 /*
227                  * Handling allocation failures here is a bit tricky;
228                  * we don't want to lose the existing buffer. Instead,
229                  * we try to reallocate it
230                  */
231                 if (!buf) {
232                         buf = realloc(bf->buf, size);
233                         if (!buf)
234                                 ret = -1;
235                         else
236                                 bf->size = size;
237                         goto bail;
238                 }
239                 free(bf->buf);
240                 bf->bflags &= ~__BALL;
241         }
242         if (!buf) {
243                 buf = malloc(size);
244                 if (!buf) {
245                         ret = -1;
246                         goto bail;
247                 }
248                 bf->bflags |= __BALL;
249         }
250         bf->buf = buf;
251         bf->size = size;
252 bail:
253         __bufio_unlock(f);
254         return ret;
255 }
256 
257 int
__bufio_close(FILE * f)258 __bufio_close(FILE *f)
259 {
260 	struct __file_bufio *bf = (struct __file_bufio *) f;
261 	int ret = 0;
262 
263 	__bufio_lock(f);
264         ret = __bufio_flush_locked(f);
265 
266         if (bf->bflags & __BALL)
267                 free(bf->buf);
268 
269 	__bufio_lock_close(f);
270 	/* Don't close stdin/stdout/stderr fds */
271 	if (bf->fd > 2)
272 		(bf->close)(bf->fd);
273 	free(f);
274 	return ret;
275 }
276 
277