/*1* *****************************************************************************2*3* SPDX-License-Identifier: BSD-2-Clause4*5* Copyright (c) 2018-2025 Gavin D. Howard and contributors.6*7* Redistribution and use in source and binary forms, with or without8* modification, are permitted provided that the following conditions are met:9*10* * Redistributions of source code must retain the above copyright notice, this11* list of conditions and the following disclaimer.12*13* * Redistributions in binary form must reproduce the above copyright notice,14* this list of conditions and the following disclaimer in the documentation15* and/or other materials provided with the distribution.16*17* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"18* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE19* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE20* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE21* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR22* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF23* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS24* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN25* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)26* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE27* POSSIBILITY OF SUCH DAMAGE.28*29* *****************************************************************************30*31* Code for implementing buffered I/O on my own terms.32*33*/3435#include <assert.h>36#include <errno.h>37#include <string.h>3839#ifndef _WIN3240#include <unistd.h>41#endif // _WIN324243#include <file.h>44#include <vm.h>4546#if !BC_ENABLE_LINE_LIB4748/**49* Translates an integer into a string.50* @param val The value to translate.51* @param buf The return parameter.52*/53static void54bc_file_ultoa(unsigned long long val, char buf[BC_FILE_ULL_LENGTH])55{56char buf2[BC_FILE_ULL_LENGTH];57size_t i, len;5859// We need to make sure the entire thing is zeroed.60// NOLINTNEXTLINE61memset(buf2, 0, BC_FILE_ULL_LENGTH);6263// The i = 1 is to ensure that there is a null byte at the end.64for (i = 1; val; ++i)65{66unsigned long long mod = val % 10;6768buf2[i] = ((char) mod) + '0';69val /= 10;70}7172len = i;7374// Since buf2 is reversed, reverse it into buf.75for (i = 0; i < len; ++i)76{77buf[i] = buf2[len - i - 1];78}79}8081/**82* Output to the file directly.83* @param fd The file descriptor.84* @param buf The buffer of data to output.85* @param n The number of bytes to output.86* @return A status indicating error or success. We could have a fatal I/O87* error or EOF.88*/89static BcStatus90bc_file_output(int fd, const char* buf, size_t n)91{92size_t bytes = 0;93sig_atomic_t lock;9495BC_SIG_TRYLOCK(lock);9697// While the number of bytes written is less than intended...98while (bytes < n)99{100// Write.101ssize_t written = write(fd, buf + bytes, n - bytes);102103// Check for error and return, if any.104if (BC_ERR(written == -1))105{106BC_SIG_TRYUNLOCK(lock);107108return errno == EPIPE ? BC_STATUS_EOF : BC_STATUS_ERROR_FATAL;109}110111bytes += (size_t) written;112}113114BC_SIG_TRYUNLOCK(lock);115116return BC_STATUS_SUCCESS;117}118119#endif // !BC_ENABLE_LINE_LIB120121BcStatus122bc_file_flushErr(BcFile* restrict f, BcFlushType type)123{124BcStatus s;125126BC_SIG_ASSERT_LOCKED;127128#if BC_ENABLE_LINE_LIB129130// Just flush and propagate the error.131if (fflush(f->f) == EOF) s = BC_STATUS_ERROR_FATAL;132else s = BC_STATUS_SUCCESS;133134#else // BC_ENABLE_LINE_LIB135136// If there is stuff to output...137if (f->len)138{139#if BC_ENABLE_HISTORY140141// If history is enabled...142if (BC_TTY)143{144// If we have been told to save the extras, and there *are*145// extras...146if (f->buf[f->len - 1] != '\n' &&147(type == BC_FLUSH_SAVE_EXTRAS_CLEAR ||148type == BC_FLUSH_SAVE_EXTRAS_NO_CLEAR))149{150size_t i;151152// Look for the last newline.153for (i = f->len - 2; i < f->len && f->buf[i] != '\n'; --i)154{155continue;156}157158i += 1;159160// Save the extras.161bc_vec_string(&vm->history.extras, f->len - i, f->buf + i);162}163// Else clear the extras if told to.164else if (type >= BC_FLUSH_NO_EXTRAS_CLEAR)165{166bc_vec_popAll(&vm->history.extras);167}168}169#endif // BC_ENABLE_HISTORY170171// Actually output.172s = bc_file_output(f->fd, f->buf, f->len);173f->len = 0;174}175else s = BC_STATUS_SUCCESS;176177#endif // BC_ENABLE_LINE_LIB178179return s;180}181182void183bc_file_flush(BcFile* restrict f, BcFlushType type)184{185BcStatus s;186sig_atomic_t lock;187188BC_SIG_TRYLOCK(lock);189190s = bc_file_flushErr(f, type);191192// If we have an error...193if (BC_ERR(s))194{195// For EOF, set it and jump.196if (s == BC_STATUS_EOF)197{198vm->status = (sig_atomic_t) s;199BC_SIG_TRYUNLOCK(lock);200BC_JMP;201}202// Make sure to handle non-fatal I/O properly.203else if (!f->errors_fatal)204{205bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);206}207// Blow up on fatal error. Okay, not blow up, just quit.208else exit(BC_STATUS_ERROR_FATAL);209}210211BC_SIG_TRYUNLOCK(lock);212}213214#if !BC_ENABLE_LINE_LIB215216void217bc_file_write(BcFile* restrict f, BcFlushType type, const char* buf, size_t n)218{219sig_atomic_t lock;220221BC_SIG_TRYLOCK(lock);222223// If we have enough to flush, do it.224if (n > f->cap - f->len)225{226bc_file_flush(f, type);227assert(!f->len);228}229230// If the output is large enough to flush by itself, just output it.231// Otherwise, put it into the buffer.232if (BC_UNLIKELY(n > f->cap - f->len))233{234BcStatus s = bc_file_output(f->fd, buf, n);235236if (BC_ERR(s))237{238// For EOF, set it and jump.239if (s == BC_STATUS_EOF)240{241vm->status = (sig_atomic_t) s;242BC_SIG_TRYUNLOCK(lock);243BC_JMP;244}245// Make sure to handle non-fatal I/O properly.246else if (!f->errors_fatal)247{248bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);249}250// Blow up on fatal error. Okay, not blow up, just quit.251else exit(BC_STATUS_ERROR_FATAL);252}253}254else255{256// NOLINTNEXTLINE257memcpy(f->buf + f->len, buf, n);258f->len += n;259}260261BC_SIG_TRYUNLOCK(lock);262}263264#endif // BC_ENABLE_LINE_LIB265266void267bc_file_printf(BcFile* restrict f, const char* fmt, ...)268{269va_list args;270sig_atomic_t lock;271272BC_SIG_TRYLOCK(lock);273274va_start(args, fmt);275bc_file_vprintf(f, fmt, args);276va_end(args);277278BC_SIG_TRYUNLOCK(lock);279}280281void282bc_file_vprintf(BcFile* restrict f, const char* fmt, va_list args)283{284BC_SIG_ASSERT_LOCKED;285286#if BC_ENABLE_LINE_LIB287288{289int r;290291// This mess is to silence a warning.292#if BC_CLANG293#pragma clang diagnostic push294#pragma clang diagnostic ignored "-Wformat-nonliteral"295#endif // BC_CLANG296r = vfprintf(f->f, fmt, args);297#if BC_CLANG298#pragma clang diagnostic pop299#endif // BC_CLANG300301// Just print and propagate the error.302if (BC_ERR(r < 0))303{304// Make sure to handle non-fatal I/O properly.305if (!f->errors_fatal)306{307bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);308}309else310{311exit(BC_STATUS_ERROR_FATAL);312}313}314}315316#else // BC_ENABLE_LINE_LIB317318{319char* percent;320const char* ptr = fmt;321char buf[BC_FILE_ULL_LENGTH];322323// This is a poor man's printf(). While I could look up algorithms to324// make it as fast as possible, and should when I write the standard325// library for a new language, for bc, outputting is not the bottleneck.326// So we cheese it for now.327328// Find each percent sign.329while ((percent = strchr(ptr, '%')) != NULL)330{331char c;332333// If the percent sign is not where we are, write what's inbetween334// to the buffer.335if (percent != ptr)336{337size_t len = (size_t) (percent - ptr);338bc_file_write(f, bc_flush_none, ptr, len);339}340341c = percent[1];342343// We only parse some format specifiers, the ones bc uses. If you344// add more, you need to make sure to add them here.345if (c == 'c')346{347uchar uc = (uchar) va_arg(args, int);348349bc_file_putchar(f, bc_flush_none, uc);350}351else if (c == 's')352{353char* s = va_arg(args, char*);354355bc_file_puts(f, bc_flush_none, s);356}357#if BC_DEBUG358// We only print signed integers in debug code.359else if (c == 'd')360{361int d = va_arg(args, int);362363// Take care of negative. Let's not worry about overflow.364if (d < 0)365{366bc_file_putchar(f, bc_flush_none, '-');367d = -d;368}369370// Either print 0 or translate and print.371if (!d) bc_file_putchar(f, bc_flush_none, '0');372else373{374bc_file_ultoa((unsigned long long) d, buf);375bc_file_puts(f, bc_flush_none, buf);376}377}378#endif // BC_DEBUG379else380{381unsigned long long ull;382383// These are the ones that it expects from here. Fortunately,384// all of these are unsigned types, so they can use the same385// code, more or less.386assert((c == 'l' || c == 'z') && percent[2] == 'u');387388if (c == 'z') ull = (unsigned long long) va_arg(args, size_t);389else ull = (unsigned long long) va_arg(args, unsigned long);390391// Either print 0 or translate and print.392if (!ull) bc_file_putchar(f, bc_flush_none, '0');393else394{395bc_file_ultoa(ull, buf);396bc_file_puts(f, bc_flush_none, buf);397}398}399400// Increment to the next spot after the specifier.401ptr = percent + 2 + (c == 'l' || c == 'z');402}403404// If we get here, there are no more percent signs, so we just output405// whatever is left.406if (ptr[0]) bc_file_puts(f, bc_flush_none, ptr);407}408409#endif // BC_ENABLE_LINE_LIB410}411412void413bc_file_puts(BcFile* restrict f, BcFlushType type, const char* str)414{415#if BC_ENABLE_LINE_LIB416// This is used because of flushing issues with using bc_file_write() when417// bc is using a line library. It's also using printf() because puts()418// writes a newline.419bc_file_printf(f, "%s", str);420#else // BC_ENABLE_LINE_LIB421bc_file_write(f, type, str, strlen(str));422#endif // BC_ENABLE_LINE_LIB423}424425void426bc_file_putchar(BcFile* restrict f, BcFlushType type, uchar c)427{428sig_atomic_t lock;429430BC_SIG_TRYLOCK(lock);431432#if BC_ENABLE_LINE_LIB433434if (BC_ERR(fputc(c, f->f) == EOF))435{436// This is here to prevent a stack overflow from unbounded recursion.437if (f->f == stderr) exit(BC_STATUS_ERROR_FATAL);438439bc_err(BC_ERR_FATAL_IO_ERR);440}441442#else // BC_ENABLE_LINE_LIB443444if (f->len == f->cap) bc_file_flush(f, type);445446assert(f->len < f->cap);447448f->buf[f->len] = (char) c;449f->len += 1;450451#endif // BC_ENABLE_LINE_LIB452453BC_SIG_TRYUNLOCK(lock);454}455456#if BC_ENABLE_LINE_LIB457458void459bc_file_init(BcFile* f, FILE* file, bool errors_fatal)460{461BC_SIG_ASSERT_LOCKED;462f->f = file;463f->errors_fatal = errors_fatal;464}465466#else // BC_ENABLE_LINE_LIB467468void469bc_file_init(BcFile* f, int fd, char* buf, size_t cap, bool errors_fatal)470{471BC_SIG_ASSERT_LOCKED;472473f->fd = fd;474f->buf = buf;475f->len = 0;476f->cap = cap;477f->errors_fatal = errors_fatal;478}479480#endif // BC_ENABLE_LINE_LIB481482void483bc_file_free(BcFile* f)484{485BC_SIG_ASSERT_LOCKED;486bc_file_flush(f, bc_flush_none);487}488489490