/* * ***************************************************************************** * * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018-2024 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: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * ***************************************************************************** * * Code to handle special I/O for bc. * */ #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #endif // _WIN32 #include #include #include #include /** * A portability file open function. This is copied to gen/strgen.c. Make sure * to update that if this changes. * @param path The path to the file to open. * @param mode The mode to open in. */ static int bc_read_open(const char* path, int mode) { int fd; #ifndef _WIN32 fd = open(path, mode); #else // _WIN32 fd = -1; open(&fd, path, mode); #endif return fd; } /** * Returns true if the buffer data is non-text. * @param buf The buffer to test. * @param size The size of the buffer. */ static bool bc_read_binary(const char* buf, size_t size) { size_t i; for (i = 0; i < size; ++i) { if (BC_ERR(BC_READ_BIN_CHAR(buf[i]))) return true; } return false; } bool bc_read_buf(BcVec* vec, char* buf, size_t* buf_len) { char* nl; // If nothing there, return. if (!*buf_len) return false; // Find the newline. nl = strchr(buf, '\n'); // If a newline exists... if (nl != NULL) { // Get the size of the data up to, and including, the newline. size_t nllen = (size_t) ((nl + 1) - buf); nllen = *buf_len >= nllen ? nllen : *buf_len; // Move data into the vector, and move the rest of the data in the // buffer up. bc_vec_npush(vec, nllen, buf); *buf_len -= nllen; // NOLINTNEXTLINE memmove(buf, nl + 1, *buf_len + 1); return true; } // Just put the data into the vector. bc_vec_npush(vec, *buf_len, buf); *buf_len = 0; return false; } BcStatus bc_read_chars(BcVec* vec, const char* prompt) { bool done = false; assert(vec != NULL && vec->size == sizeof(char)); BC_SIG_ASSERT_NOT_LOCKED; // Clear the vector. bc_vec_popAll(vec); // Handle the prompt, if desired. if (BC_PROMPT) { bc_file_puts(&vm->fout, bc_flush_none, prompt); bc_file_flush(&vm->fout, bc_flush_none); } // Try reading from the buffer, and if successful, just return. if (bc_read_buf(vec, vm->buf, &vm->buf_len)) { bc_vec_pushByte(vec, '\0'); return BC_STATUS_SUCCESS; } // Loop until we have something. while (!done) { ssize_t r; BC_SIG_LOCK; // Read data from stdin. r = read(STDIN_FILENO, vm->buf + vm->buf_len, BC_VM_STDIN_BUF_SIZE - vm->buf_len); // If there was an error... if (BC_UNLIKELY(r < 0)) { // If interupted... if (errno == EINTR) { int sig; // Jump out if we are supposed to quit, which certain signals // will require. if (vm->status == (sig_atomic_t) BC_STATUS_QUIT) BC_JMP; assert(vm->sig != 0); sig = (int) vm->sig; // Clear the signal and status. vm->sig = 0; vm->status = (sig_atomic_t) BC_STATUS_SUCCESS; #ifndef _WIN32 // We don't want to print anything on a SIGWINCH. if (sig != SIGWINCH) #endif // _WIN32 { // Print the ready message and prompt again. bc_file_puts(&vm->fout, bc_flush_none, bc_program_ready_msg); if (BC_PROMPT) { bc_file_puts(&vm->fout, bc_flush_none, prompt); } bc_file_flush(&vm->fout, bc_flush_none); } BC_SIG_UNLOCK; continue; } BC_SIG_UNLOCK; // If we get here, it's bad. Barf. bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); } BC_SIG_UNLOCK; // If we read nothing, make sure to terminate the string and return EOF. if (r == 0) { bc_vec_pushByte(vec, '\0'); return BC_STATUS_EOF; } BC_SIG_LOCK; // Add to the buffer. vm->buf_len += (size_t) r; vm->buf[vm->buf_len] = '\0'; // Read from the buffer. done = bc_read_buf(vec, vm->buf, &vm->buf_len); BC_SIG_UNLOCK; } // Terminate the string. bc_vec_pushByte(vec, '\0'); return BC_STATUS_SUCCESS; } BcStatus bc_read_line(BcVec* vec, const char* prompt) { BcStatus s; #if BC_ENABLE_HISTORY // Get a line from either history or manual reading. if (BC_TTY && !vm->history.badTerm) { s = bc_history_line(&vm->history, vec, prompt); } else s = bc_read_chars(vec, prompt); #else // BC_ENABLE_HISTORY s = bc_read_chars(vec, prompt); #endif // BC_ENABLE_HISTORY if (BC_ERR(bc_read_binary(vec->v, vec->len - 1))) { bc_verr(BC_ERR_FATAL_BIN_FILE, bc_program_stdin_name); } return s; } char* bc_read_file(const char* path) { BcErr e = BC_ERR_FATAL_IO_ERR; size_t size, to_read; struct stat pstat; int fd; char* buf; char* buf2; // This has been copied to gen/strgen.c. Make sure to change that if this // changes. BC_SIG_ASSERT_LOCKED; assert(path != NULL); #if BC_DEBUG // Need this to quiet MSan. // NOLINTNEXTLINE memset(&pstat, 0, sizeof(struct stat)); #endif // BC_DEBUG fd = bc_read_open(path, O_RDONLY); // If we can't read a file, we just barf. if (BC_ERR(fd < 0)) bc_verr(BC_ERR_FATAL_FILE_ERR, path); // The reason we call fstat is to eliminate TOCTOU race conditions. This // way, we have an open file, so it's not going anywhere. if (BC_ERR(fstat(fd, &pstat) == -1)) goto malloc_err; // Make sure it's not a directory. if (BC_ERR(S_ISDIR(pstat.st_mode))) { e = BC_ERR_FATAL_PATH_DIR; goto malloc_err; } // Get the size of the file and allocate that much. size = (size_t) pstat.st_size; buf = bc_vm_malloc(size + 1); buf2 = buf; to_read = size; do { // Read the file. We just bail if a signal interrupts. This is so that // users can interrupt the reading of big files if they want. ssize_t r = read(fd, buf2, to_read); if (BC_ERR(r < 0)) goto read_err; to_read -= (size_t) r; buf2 += (size_t) r; } while (to_read); // Got to have a nul byte. buf[size] = '\0'; if (BC_ERR(bc_read_binary(buf, size))) { e = BC_ERR_FATAL_BIN_FILE; goto read_err; } close(fd); return buf; read_err: free(buf); malloc_err: close(fd); bc_verr(e, path); return NULL; }