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 return errno == EPIPE ? BC_STATUS_EOF : BC_STATUS_ERROR_FATAL;
99
100 bytes += (size_t) written;
101 }
102
103 BC_SIG_TRYUNLOCK(lock);
104
105 return BC_STATUS_SUCCESS;
106 }
107
bc_file_flushErr(BcFile * restrict f,BcFlushType type)108 BcStatus bc_file_flushErr(BcFile *restrict f, BcFlushType type)
109 {
110 BcStatus s;
111
112 // If there is stuff to output...
113 if (f->len) {
114
115 #if BC_ENABLE_HISTORY
116
117 // If history is enabled...
118 if (BC_TTY) {
119
120 // If we have been told to save the extras, and there *are*
121 // extras...
122 if (f->buf[f->len - 1] != '\n' &&
123 (type == BC_FLUSH_SAVE_EXTRAS_CLEAR ||
124 type == BC_FLUSH_SAVE_EXTRAS_NO_CLEAR))
125 {
126 size_t i;
127
128 // Look for the last newline.
129 for (i = f->len - 2; i < f->len && f->buf[i] != '\n'; --i);
130
131 i += 1;
132
133 // Save the extras.
134 bc_vec_string(&vm.history.extras, f->len - i, f->buf + i);
135 }
136 // Else clear the extras if told to.
137 else if (type >= BC_FLUSH_NO_EXTRAS_CLEAR) {
138 bc_vec_popAll(&vm.history.extras);
139 }
140 }
141 #endif // BC_ENABLE_HISTORY
142
143 // Actually output.
144 s = bc_file_output(f->fd, f->buf, f->len);
145 f->len = 0;
146 }
147 else s = BC_STATUS_SUCCESS;
148
149 return s;
150 }
151
bc_file_flush(BcFile * restrict f,BcFlushType type)152 void bc_file_flush(BcFile *restrict f, BcFlushType type) {
153
154 BcStatus s = bc_file_flushErr(f, type);
155
156 // If we have an error...
157 if (BC_ERR(s)) {
158
159 // For EOF, set it and jump.
160 if (s == BC_STATUS_EOF) {
161 vm.status = (sig_atomic_t) s;
162 BC_JMP;
163 }
164 // Blow up on fatal error. Okay, not blow up, just quit.
165 else bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
166 }
167 }
168
bc_file_write(BcFile * restrict f,BcFlushType type,const char * buf,size_t n)169 void bc_file_write(BcFile *restrict f, BcFlushType type,
170 const char *buf, size_t n)
171 {
172 // If we have enough to flush, do it.
173 if (n > f->cap - f->len) {
174 bc_file_flush(f, type);
175 assert(!f->len);
176 }
177
178 // If the output is large enough to flush by itself, just output it.
179 // Otherwise, put it into the buffer.
180 if (BC_UNLIKELY(n > f->cap - f->len)) bc_file_output(f->fd, buf, n);
181 else {
182 memcpy(f->buf + f->len, buf, n);
183 f->len += n;
184 }
185 }
186
bc_file_printf(BcFile * restrict f,const char * fmt,...)187 void bc_file_printf(BcFile *restrict f, const char *fmt, ...)
188 {
189 va_list args;
190
191 va_start(args, fmt);
192 bc_file_vprintf(f, fmt, args);
193 va_end(args);
194 }
195
bc_file_vprintf(BcFile * restrict f,const char * fmt,va_list args)196 void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) {
197
198 char *percent;
199 const char *ptr = fmt;
200 char buf[BC_FILE_ULL_LENGTH];
201
202 // This is a poor man's printf(). While I could look up algorithms to make
203 // it as fast as possible, and should when I write the standard library for
204 // a new language, for bc, outputting is not the bottleneck. So we cheese it
205 // for now.
206
207 // Find each percent sign.
208 while ((percent = strchr(ptr, '%')) != NULL) {
209
210 char c;
211
212 // If the percent sign is not where we are, write what's inbetween to
213 // the buffer.
214 if (percent != ptr) {
215 size_t len = (size_t) (percent - ptr);
216 bc_file_write(f, bc_flush_none, ptr, len);
217 }
218
219 c = percent[1];
220
221 // We only parse some format specifiers, the ones bc uses. If you add
222 // more, you need to make sure to add them here.
223 if (c == 'c') {
224
225 uchar uc = (uchar) va_arg(args, int);
226
227 bc_file_putchar(f, bc_flush_none, uc);
228 }
229 else if (c == 's') {
230
231 char *s = va_arg(args, char*);
232
233 bc_file_puts(f, bc_flush_none, s);
234 }
235 #if BC_DEBUG_CODE
236 // We only print signed integers in debug code.
237 else if (c == 'd') {
238
239 int d = va_arg(args, int);
240
241 // Take care of negative. Let's not worry about overflow.
242 if (d < 0) {
243 bc_file_putchar(f, bc_flush_none, '-');
244 d = -d;
245 }
246
247 // Either print 0 or translate and print.
248 if (!d) bc_file_putchar(f, bc_flush_none, '0');
249 else {
250 bc_file_ultoa((unsigned long long) d, buf);
251 bc_file_puts(f, bc_flush_none, buf);
252 }
253 }
254 #endif // BC_DEBUG_CODE
255 else {
256
257 unsigned long long ull;
258
259 // These are the ones that it expects from here. Fortunately, all of
260 // these are unsigned types, so they can use the same code, more or
261 // less.
262 assert((c == 'l' || c == 'z') && percent[2] == 'u');
263
264 if (c == 'z') ull = (unsigned long long) va_arg(args, size_t);
265 else ull = (unsigned long long) va_arg(args, unsigned long);
266
267 // Either print 0 or translate and print.
268 if (!ull) bc_file_putchar(f, bc_flush_none, '0');
269 else {
270 bc_file_ultoa(ull, buf);
271 bc_file_puts(f, bc_flush_none, buf);
272 }
273 }
274
275 // Increment to the next spot after the specifier.
276 ptr = percent + 2 + (c == 'l' || c == 'z');
277 }
278
279 // If we get here, there are no more percent signs, so we just output
280 // whatever is left.
281 if (ptr[0]) bc_file_puts(f, bc_flush_none, ptr);
282 }
283
bc_file_puts(BcFile * restrict f,BcFlushType type,const char * str)284 void bc_file_puts(BcFile *restrict f, BcFlushType type, const char *str) {
285 bc_file_write(f, type, str, strlen(str));
286 }
287
bc_file_putchar(BcFile * restrict f,BcFlushType type,uchar c)288 void bc_file_putchar(BcFile *restrict f, BcFlushType type, uchar c) {
289
290 if (f->len == f->cap) bc_file_flush(f, type);
291
292 assert(f->len < f->cap);
293
294 f->buf[f->len] = (char) c;
295 f->len += 1;
296 }
297
bc_file_init(BcFile * f,int fd,char * buf,size_t cap)298 void bc_file_init(BcFile *f, int fd, char *buf, size_t cap) {
299
300 BC_SIG_ASSERT_LOCKED;
301
302 f->fd = fd;
303 f->buf = buf;
304 f->len = 0;
305 f->cap = cap;
306 }
307
bc_file_free(BcFile * f)308 void bc_file_free(BcFile *f) {
309 BC_SIG_ASSERT_LOCKED;
310 bc_file_flush(f, bc_flush_none);
311 }
312