1 /*
2 * *****************************************************************************
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 *
6 * Copyright (c) 2018-2021 Gavin D. Howard and contributors.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
10 *
11 * * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 *
30 * *****************************************************************************
31 *
32 * Code for implementing buffered I/O on my own terms.
33 *
34 */
35
36 #include <assert.h>
37 #include <errno.h>
38 #include <string.h>
39
40 #ifndef _WIN32
41 #include <unistd.h>
42 #endif // _WIN32
43
44 #include <file.h>
45 #include <vm.h>
46
47 /**
48 * Translates an integer into a string.
49 * @param val The value to translate.
50 * @param buf The return parameter.
51 */
bc_file_ultoa(unsigned long long val,char buf[BC_FILE_ULL_LENGTH])52 static void bc_file_ultoa(unsigned long long val, char buf[BC_FILE_ULL_LENGTH])
53 {
54 char buf2[BC_FILE_ULL_LENGTH];
55 size_t i, len;
56
57 // We need to make sure the entire thing is zeroed.
58 memset(buf2, 0, BC_FILE_ULL_LENGTH);
59
60 // The i = 1 is to ensure that there is a null byte at the end.
61 for (i = 1; val; ++i) {
62
63 unsigned long long mod = val % 10;
64
65 buf2[i] = ((char) mod) + '0';
66 val /= 10;
67 }
68
69 len = i;
70
71 // Since buf2 is reversed, reverse it into buf.
72 for (i = 0; i < len; ++i) buf[i] = buf2[len - i - 1];
73 }
74
75 /**
76 * Output to the file directly.
77 * @param fd The file descriptor.
78 * @param buf The buffer of data to output.
79 * @param n The number of bytes to output.
80 * @return A status indicating error or success. We could have a fatal I/O
81 * error or EOF.
82 */
bc_file_output(int fd,const char * buf,size_t n)83 static BcStatus bc_file_output(int fd, const char *buf, size_t n) {
84
85 size_t bytes = 0;
86 sig_atomic_t lock;
87
88 BC_SIG_TRYLOCK(lock);
89
90 // While the number of bytes written is less than intended...
91 while (bytes < n) {
92
93 // Write.
94 ssize_t written = write(fd, buf + bytes, n - bytes);
95
96 // Check for error and return, if any.
97 if (BC_ERR(written == -1)) {
98
99 BC_SIG_TRYUNLOCK(lock);
100
101 return errno == EPIPE ? BC_STATUS_EOF : BC_STATUS_ERROR_FATAL;
102 }
103
104 bytes += (size_t) written;
105 }
106
107 BC_SIG_TRYUNLOCK(lock);
108
109 return BC_STATUS_SUCCESS;
110 }
111
bc_file_flushErr(BcFile * restrict f,BcFlushType type)112 BcStatus bc_file_flushErr(BcFile *restrict f, BcFlushType type)
113 {
114 BcStatus s;
115
116 BC_SIG_ASSERT_LOCKED;
117
118 // If there is stuff to output...
119 if (f->len) {
120
121 #if BC_ENABLE_HISTORY
122
123 // If history is enabled...
124 if (BC_TTY) {
125
126 // If we have been told to save the extras, and there *are*
127 // extras...
128 if (f->buf[f->len - 1] != '\n' &&
129 (type == BC_FLUSH_SAVE_EXTRAS_CLEAR ||
130 type == BC_FLUSH_SAVE_EXTRAS_NO_CLEAR))
131 {
132 size_t i;
133
134 // Look for the last newline.
135 for (i = f->len - 2; i < f->len && f->buf[i] != '\n'; --i);
136
137 i += 1;
138
139 // Save the extras.
140 bc_vec_string(&vm.history.extras, f->len - i, f->buf + i);
141 }
142 // Else clear the extras if told to.
143 else if (type >= BC_FLUSH_NO_EXTRAS_CLEAR) {
144 bc_vec_popAll(&vm.history.extras);
145 }
146 }
147 #endif // BC_ENABLE_HISTORY
148
149 // Actually output.
150 s = bc_file_output(f->fd, f->buf, f->len);
151 f->len = 0;
152 }
153 else s = BC_STATUS_SUCCESS;
154
155 return s;
156 }
157
bc_file_flush(BcFile * restrict f,BcFlushType type)158 void bc_file_flush(BcFile *restrict f, BcFlushType type) {
159
160 BcStatus s;
161 sig_atomic_t lock;
162
163 BC_SIG_TRYLOCK(lock);
164
165 s = bc_file_flushErr(f, type);
166
167 // If we have an error...
168 if (BC_ERR(s)) {
169
170 // For EOF, set it and jump.
171 if (s == BC_STATUS_EOF) {
172 vm.status = (sig_atomic_t) s;
173 BC_SIG_TRYUNLOCK(lock);
174 BC_JMP;
175 }
176 // Blow up on fatal error. Okay, not blow up, just quit.
177 else bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
178 }
179
180 BC_SIG_TRYUNLOCK(lock);
181 }
182
bc_file_write(BcFile * restrict f,BcFlushType type,const char * buf,size_t n)183 void bc_file_write(BcFile *restrict f, BcFlushType type,
184 const char *buf, size_t n)
185 {
186 sig_atomic_t lock;
187
188 BC_SIG_TRYLOCK(lock);
189
190 // If we have enough to flush, do it.
191 if (n > f->cap - f->len) {
192 bc_file_flush(f, type);
193 assert(!f->len);
194 }
195
196 // If the output is large enough to flush by itself, just output it.
197 // Otherwise, put it into the buffer.
198 if (BC_UNLIKELY(n > f->cap - f->len)) bc_file_output(f->fd, buf, n);
199 else {
200 memcpy(f->buf + f->len, buf, n);
201 f->len += n;
202 }
203
204 BC_SIG_TRYUNLOCK(lock);
205 }
206
bc_file_printf(BcFile * restrict f,const char * fmt,...)207 void bc_file_printf(BcFile *restrict f, const char *fmt, ...)
208 {
209 va_list args;
210 sig_atomic_t lock;
211
212 BC_SIG_TRYLOCK(lock);
213
214 va_start(args, fmt);
215 bc_file_vprintf(f, fmt, args);
216 va_end(args);
217
218 BC_SIG_TRYUNLOCK(lock);
219 }
220
bc_file_vprintf(BcFile * restrict f,const char * fmt,va_list args)221 void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) {
222
223 char *percent;
224 const char *ptr = fmt;
225 char buf[BC_FILE_ULL_LENGTH];
226
227 BC_SIG_ASSERT_LOCKED;
228
229 // This is a poor man's printf(). While I could look up algorithms to make
230 // it as fast as possible, and should when I write the standard library for
231 // a new language, for bc, outputting is not the bottleneck. So we cheese it
232 // for now.
233
234 // Find each percent sign.
235 while ((percent = strchr(ptr, '%')) != NULL) {
236
237 char c;
238
239 // If the percent sign is not where we are, write what's inbetween to
240 // the buffer.
241 if (percent != ptr) {
242 size_t len = (size_t) (percent - ptr);
243 bc_file_write(f, bc_flush_none, ptr, len);
244 }
245
246 c = percent[1];
247
248 // We only parse some format specifiers, the ones bc uses. If you add
249 // more, you need to make sure to add them here.
250 if (c == 'c') {
251
252 uchar uc = (uchar) va_arg(args, int);
253
254 bc_file_putchar(f, bc_flush_none, uc);
255 }
256 else if (c == 's') {
257
258 char *s = va_arg(args, char*);
259
260 bc_file_puts(f, bc_flush_none, s);
261 }
262 #if BC_DEBUG_CODE
263 // We only print signed integers in debug code.
264 else if (c == 'd') {
265
266 int d = va_arg(args, int);
267
268 // Take care of negative. Let's not worry about overflow.
269 if (d < 0) {
270 bc_file_putchar(f, bc_flush_none, '-');
271 d = -d;
272 }
273
274 // Either print 0 or translate and print.
275 if (!d) bc_file_putchar(f, bc_flush_none, '0');
276 else {
277 bc_file_ultoa((unsigned long long) d, buf);
278 bc_file_puts(f, bc_flush_none, buf);
279 }
280 }
281 #endif // BC_DEBUG_CODE
282 else {
283
284 unsigned long long ull;
285
286 // These are the ones that it expects from here. Fortunately, all of
287 // these are unsigned types, so they can use the same code, more or
288 // less.
289 assert((c == 'l' || c == 'z') && percent[2] == 'u');
290
291 if (c == 'z') ull = (unsigned long long) va_arg(args, size_t);
292 else ull = (unsigned long long) va_arg(args, unsigned long);
293
294 // Either print 0 or translate and print.
295 if (!ull) bc_file_putchar(f, bc_flush_none, '0');
296 else {
297 bc_file_ultoa(ull, buf);
298 bc_file_puts(f, bc_flush_none, buf);
299 }
300 }
301
302 // Increment to the next spot after the specifier.
303 ptr = percent + 2 + (c == 'l' || c == 'z');
304 }
305
306 // If we get here, there are no more percent signs, so we just output
307 // whatever is left.
308 if (ptr[0]) bc_file_puts(f, bc_flush_none, ptr);
309 }
310
bc_file_puts(BcFile * restrict f,BcFlushType type,const char * str)311 void bc_file_puts(BcFile *restrict f, BcFlushType type, const char *str) {
312 bc_file_write(f, type, str, strlen(str));
313 }
314
bc_file_putchar(BcFile * restrict f,BcFlushType type,uchar c)315 void bc_file_putchar(BcFile *restrict f, BcFlushType type, uchar c) {
316
317 sig_atomic_t lock;
318
319 BC_SIG_TRYLOCK(lock);
320
321 if (f->len == f->cap) bc_file_flush(f, type);
322
323 assert(f->len < f->cap);
324
325 f->buf[f->len] = (char) c;
326 f->len += 1;
327
328 BC_SIG_TRYUNLOCK(lock);
329 }
330
bc_file_init(BcFile * f,int fd,char * buf,size_t cap)331 void bc_file_init(BcFile *f, int fd, char *buf, size_t cap) {
332
333 BC_SIG_ASSERT_LOCKED;
334
335 f->fd = fd;
336 f->buf = buf;
337 f->len = 0;
338 f->cap = cap;
339 }
340
bc_file_free(BcFile * f)341 void bc_file_free(BcFile *f) {
342 BC_SIG_ASSERT_LOCKED;
343 bc_file_flush(f, bc_flush_none);
344 }
345