diff options
Diffstat (limited to 'src/file.c')
-rw-r--r-- | src/file.c | 354 |
1 files changed, 255 insertions, 99 deletions
@@ -3,7 +3,7 @@ * * SPDX-License-Identifier: BSD-2-Clause * - * Copyright (c) 2018-2021 Gavin D. Howard and contributors. + * Copyright (c) 2018-2023 Gavin D. Howard and contributors. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -44,22 +44,26 @@ #include <file.h> #include <vm.h> +#if !BC_ENABLE_LINE_LIB + /** * Translates an integer into a string. * @param val The value to translate. * @param buf The return parameter. */ -static void bc_file_ultoa(unsigned long long val, char buf[BC_FILE_ULL_LENGTH]) +static void +bc_file_ultoa(unsigned long long val, char buf[BC_FILE_ULL_LENGTH]) { char buf2[BC_FILE_ULL_LENGTH]; size_t i, len; // We need to make sure the entire thing is zeroed. + // NOLINTNEXTLINE memset(buf2, 0, BC_FILE_ULL_LENGTH); // The i = 1 is to ensure that there is a null byte at the end. - for (i = 1; val; ++i) { - + for (i = 1; val; ++i) + { unsigned long long mod = val % 10; buf2[i] = ((char) mod) + '0'; @@ -69,7 +73,10 @@ static void bc_file_ultoa(unsigned long long val, char buf[BC_FILE_ULL_LENGTH]) len = i; // Since buf2 is reversed, reverse it into buf. - for (i = 0; i < len; ++i) buf[i] = buf2[len - i - 1]; + for (i = 0; i < len; ++i) + { + buf[i] = buf2[len - i - 1]; + } } /** @@ -80,22 +87,27 @@ static void bc_file_ultoa(unsigned long long val, char buf[BC_FILE_ULL_LENGTH]) * @return A status indicating error or success. We could have a fatal I/O * error or EOF. */ -static BcStatus bc_file_output(int fd, const char *buf, size_t n) { - +static BcStatus +bc_file_output(int fd, const char* buf, size_t n) +{ size_t bytes = 0; sig_atomic_t lock; BC_SIG_TRYLOCK(lock); // While the number of bytes written is less than intended... - while (bytes < n) { - + while (bytes < n) + { // Write. ssize_t written = write(fd, buf + bytes, n - bytes); // Check for error and return, if any. if (BC_ERR(written == -1)) + { + BC_SIG_TRYUNLOCK(lock); + return errno == EPIPE ? BC_STATUS_EOF : BC_STATUS_ERROR_FATAL; + } bytes += (size_t) written; } @@ -105,18 +117,31 @@ static BcStatus bc_file_output(int fd, const char *buf, size_t n) { return BC_STATUS_SUCCESS; } -BcStatus bc_file_flushErr(BcFile *restrict f, BcFlushType type) +#endif // !BC_ENABLE_LINE_LIB + +BcStatus +bc_file_flushErr(BcFile* restrict f, BcFlushType type) { BcStatus s; - // If there is stuff to output... - if (f->len) { + BC_SIG_ASSERT_LOCKED; +#if BC_ENABLE_LINE_LIB + + // Just flush and propagate the error. + if (fflush(f->f) == EOF) s = BC_STATUS_ERROR_FATAL; + else s = BC_STATUS_SUCCESS; + +#else // BC_ENABLE_LINE_LIB + + // If there is stuff to output... + if (f->len) + { #if BC_ENABLE_HISTORY // If history is enabled... - if (BC_TTY) { - + if (BC_TTY) + { // If we have been told to save the extras, and there *are* // extras... if (f->buf[f->len - 1] != '\n' && @@ -126,16 +151,20 @@ BcStatus bc_file_flushErr(BcFile *restrict f, BcFlushType type) size_t i; // Look for the last newline. - for (i = f->len - 2; i < f->len && f->buf[i] != '\n'; --i); + for (i = f->len - 2; i < f->len && f->buf[i] != '\n'; --i) + { + continue; + } i += 1; // Save the extras. - bc_vec_string(&vm.history.extras, f->len - i, f->buf + i); + bc_vec_string(&vm->history.extras, f->len - i, f->buf + i); } // Else clear the extras if told to. - else if (type >= BC_FLUSH_NO_EXTRAS_CLEAR) { - bc_vec_popAll(&vm.history.extras); + else if (type >= BC_FLUSH_NO_EXTRAS_CLEAR) + { + bc_vec_popAll(&vm->history.extras); } } #endif // BC_ENABLE_HISTORY @@ -146,146 +175,253 @@ BcStatus bc_file_flushErr(BcFile *restrict f, BcFlushType type) } else s = BC_STATUS_SUCCESS; +#endif // BC_ENABLE_LINE_LIB + return s; } -void bc_file_flush(BcFile *restrict f, BcFlushType type) { +void +bc_file_flush(BcFile* restrict f, BcFlushType type) +{ + BcStatus s; + sig_atomic_t lock; + + BC_SIG_TRYLOCK(lock); - BcStatus s = bc_file_flushErr(f, type); + s = bc_file_flushErr(f, type); // If we have an error... - if (BC_ERR(s)) { - + if (BC_ERR(s)) + { // For EOF, set it and jump. - if (s == BC_STATUS_EOF) { - vm.status = (sig_atomic_t) s; + if (s == BC_STATUS_EOF) + { + vm->status = (sig_atomic_t) s; + BC_SIG_TRYUNLOCK(lock); BC_JMP; } // Blow up on fatal error. Okay, not blow up, just quit. else bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); } + + BC_SIG_TRYUNLOCK(lock); } -void bc_file_write(BcFile *restrict f, BcFlushType type, - const char *buf, size_t n) +#if !BC_ENABLE_LINE_LIB + +void +bc_file_write(BcFile* restrict f, BcFlushType type, const char* buf, size_t n) { + sig_atomic_t lock; + + BC_SIG_TRYLOCK(lock); + // If we have enough to flush, do it. - if (n > f->cap - f->len) { + if (n > f->cap - f->len) + { bc_file_flush(f, type); assert(!f->len); } // If the output is large enough to flush by itself, just output it. // Otherwise, put it into the buffer. - if (BC_UNLIKELY(n > f->cap - f->len)) bc_file_output(f->fd, buf, n); - else { + if (BC_UNLIKELY(n > f->cap - f->len)) + { + BcStatus s = bc_file_output(f->fd, buf, n); + + if (BC_ERR(s)) + { + // For EOF, set it and jump. + if (s == BC_STATUS_EOF) + { + vm->status = (sig_atomic_t) s; + BC_SIG_TRYUNLOCK(lock); + BC_JMP; + } + // Blow up on fatal error. Okay, not blow up, just quit. + else bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); + } + } + else + { + // NOLINTNEXTLINE memcpy(f->buf + f->len, buf, n); f->len += n; } + + BC_SIG_TRYUNLOCK(lock); } -void bc_file_printf(BcFile *restrict f, const char *fmt, ...) +#endif // BC_ENABLE_LINE_LIB + +void +bc_file_printf(BcFile* restrict f, const char* fmt, ...) { va_list args; + sig_atomic_t lock; + + BC_SIG_TRYLOCK(lock); va_start(args, fmt); bc_file_vprintf(f, fmt, args); va_end(args); -} -void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) { + BC_SIG_TRYUNLOCK(lock); +} - char *percent; - const char *ptr = fmt; - char buf[BC_FILE_ULL_LENGTH]; +void +bc_file_vprintf(BcFile* restrict f, const char* fmt, va_list args) +{ + BC_SIG_ASSERT_LOCKED; - // This is a poor man's printf(). While I could look up algorithms to make - // it as fast as possible, and should when I write the standard library for - // a new language, for bc, outputting is not the bottleneck. So we cheese it - // for now. +#if BC_ENABLE_LINE_LIB - // Find each percent sign. - while ((percent = strchr(ptr, '%')) != NULL) { + { + int r; - char c; + // This mess is to silence a warning. +#if BC_CLANG +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif // BC_CLANG + r = vfprintf(f->f, fmt, args); +#if BC_CLANG +#pragma clang diagnostic warning "-Wformat-nonliteral" +#endif // BC_CLANG - // If the percent sign is not where we are, write what's inbetween to - // the buffer. - if (percent != ptr) { - size_t len = (size_t) (percent - ptr); - bc_file_write(f, bc_flush_none, ptr, len); + // Just print and propagate the error. + if (BC_ERR(r < 0)) + { + bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); } + } - c = percent[1]; +#else // BC_ENABLE_LINE_LIB - // We only parse some format specifiers, the ones bc uses. If you add - // more, you need to make sure to add them here. - if (c == 'c') { + { + char* percent; + const char* ptr = fmt; + char buf[BC_FILE_ULL_LENGTH]; - uchar uc = (uchar) va_arg(args, int); + // This is a poor man's printf(). While I could look up algorithms to + // make it as fast as possible, and should when I write the standard + // library for a new language, for bc, outputting is not the bottleneck. + // So we cheese it for now. - bc_file_putchar(f, bc_flush_none, uc); - } - else if (c == 's') { + // Find each percent sign. + while ((percent = strchr(ptr, '%')) != NULL) + { + char c; - char *s = va_arg(args, char*); + // If the percent sign is not where we are, write what's inbetween + // to the buffer. + if (percent != ptr) + { + size_t len = (size_t) (percent - ptr); + bc_file_write(f, bc_flush_none, ptr, len); + } - bc_file_puts(f, bc_flush_none, s); - } -#if BC_DEBUG_CODE - // We only print signed integers in debug code. - else if (c == 'd') { + c = percent[1]; - int d = va_arg(args, int); + // We only parse some format specifiers, the ones bc uses. If you + // add more, you need to make sure to add them here. + if (c == 'c') + { + uchar uc = (uchar) va_arg(args, int); - // Take care of negative. Let's not worry about overflow. - if (d < 0) { - bc_file_putchar(f, bc_flush_none, '-'); - d = -d; + bc_file_putchar(f, bc_flush_none, uc); } + else if (c == 's') + { + char* s = va_arg(args, char*); - // Either print 0 or translate and print. - if (!d) bc_file_putchar(f, bc_flush_none, '0'); - else { - bc_file_ultoa((unsigned long long) d, buf); - bc_file_puts(f, bc_flush_none, buf); + bc_file_puts(f, bc_flush_none, s); } - } -#endif // BC_DEBUG_CODE - else { - - unsigned long long ull; - - // These are the ones that it expects from here. Fortunately, all of - // these are unsigned types, so they can use the same code, more or - // less. - assert((c == 'l' || c == 'z') && percent[2] == 'u'); - - if (c == 'z') ull = (unsigned long long) va_arg(args, size_t); - else ull = (unsigned long long) va_arg(args, unsigned long); - - // Either print 0 or translate and print. - if (!ull) bc_file_putchar(f, bc_flush_none, '0'); - else { - bc_file_ultoa(ull, buf); - bc_file_puts(f, bc_flush_none, buf); +#if BC_DEBUG + // We only print signed integers in debug code. + else if (c == 'd') + { + int d = va_arg(args, int); + + // Take care of negative. Let's not worry about overflow. + if (d < 0) + { + bc_file_putchar(f, bc_flush_none, '-'); + d = -d; + } + + // Either print 0 or translate and print. + if (!d) bc_file_putchar(f, bc_flush_none, '0'); + else + { + bc_file_ultoa((unsigned long long) d, buf); + bc_file_puts(f, bc_flush_none, buf); + } } +#endif // BC_DEBUG + else + { + unsigned long long ull; + + // These are the ones that it expects from here. Fortunately, + // all of these are unsigned types, so they can use the same + // code, more or less. + assert((c == 'l' || c == 'z') && percent[2] == 'u'); + + if (c == 'z') ull = (unsigned long long) va_arg(args, size_t); + else ull = (unsigned long long) va_arg(args, unsigned long); + + // Either print 0 or translate and print. + if (!ull) bc_file_putchar(f, bc_flush_none, '0'); + else + { + bc_file_ultoa(ull, buf); + bc_file_puts(f, bc_flush_none, buf); + } + } + + // Increment to the next spot after the specifier. + ptr = percent + 2 + (c == 'l' || c == 'z'); } - // Increment to the next spot after the specifier. - ptr = percent + 2 + (c == 'l' || c == 'z'); + // If we get here, there are no more percent signs, so we just output + // whatever is left. + if (ptr[0]) bc_file_puts(f, bc_flush_none, ptr); } - // If we get here, there are no more percent signs, so we just output - // whatever is left. - if (ptr[0]) bc_file_puts(f, bc_flush_none, ptr); +#endif // BC_ENABLE_LINE_LIB } -void bc_file_puts(BcFile *restrict f, BcFlushType type, const char *str) { +void +bc_file_puts(BcFile* restrict f, BcFlushType type, const char* str) +{ +#if BC_ENABLE_LINE_LIB + // This is used because of flushing issues with using bc_file_write() when + // bc is using a line library. It's also using printf() because puts() + // writes a newline. + bc_file_printf(f, "%s", str); +#else // BC_ENABLE_LINE_LIB bc_file_write(f, type, str, strlen(str)); +#endif // BC_ENABLE_LINE_LIB } -void bc_file_putchar(BcFile *restrict f, BcFlushType type, uchar c) { +void +bc_file_putchar(BcFile* restrict f, BcFlushType type, uchar c) +{ + sig_atomic_t lock; + + BC_SIG_TRYLOCK(lock); + +#if BC_ENABLE_LINE_LIB + + if (BC_ERR(fputc(c, f->f) == EOF)) + { + // This is here to prevent a stack overflow from unbounded recursion. + if (f->f == stderr) exit(BC_STATUS_ERROR_FATAL); + + bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); + } + +#else // BC_ENABLE_LINE_LIB if (f->len == f->cap) bc_file_flush(f, type); @@ -293,10 +429,26 @@ void bc_file_putchar(BcFile *restrict f, BcFlushType type, uchar c) { f->buf[f->len] = (char) c; f->len += 1; + +#endif // BC_ENABLE_LINE_LIB + + BC_SIG_TRYUNLOCK(lock); +} + +#if BC_ENABLE_LINE_LIB + +void +bc_file_init(BcFile* f, FILE* file) +{ + BC_SIG_ASSERT_LOCKED; + f->f = file; } -void bc_file_init(BcFile *f, int fd, char *buf, size_t cap) { +#else // BC_ENABLE_LINE_LIB +void +bc_file_init(BcFile* f, int fd, char* buf, size_t cap) +{ BC_SIG_ASSERT_LOCKED; f->fd = fd; @@ -305,7 +457,11 @@ void bc_file_init(BcFile *f, int fd, char *buf, size_t cap) { f->cap = cap; } -void bc_file_free(BcFile *f) { +#endif // BC_ENABLE_LINE_LIB + +void +bc_file_free(BcFile* f) +{ BC_SIG_ASSERT_LOCKED; bc_file_flush(f, bc_flush_none); } |