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 to handle special I/O for bc. 33 * 34 */ 35 36 #include <assert.h> 37 #include <ctype.h> 38 #include <errno.h> 39 #include <stdlib.h> 40 #include <string.h> 41 42 #include <signal.h> 43 44 #include <fcntl.h> 45 #include <sys/stat.h> 46 47 #ifndef _WIN32 48 #include <unistd.h> 49 #endif // _WIN32 50 51 #include <read.h> 52 #include <history.h> 53 #include <program.h> 54 #include <vm.h> 55 56 /** 57 * A portability file open function. 58 * @param path The path to the file to open. 59 * @param mode The mode to open in. 60 */ 61 static int bc_read_open(const char* path, int mode) { 62 63 int fd; 64 65 #ifndef _WIN32 66 fd = open(path, mode); 67 #else // _WIN32 68 fd = -1; 69 open(&fd, path, mode); 70 #endif 71 72 return fd; 73 } 74 75 /** 76 * Returns true if the buffer data is non-text. 77 * @param buf The buffer to test. 78 * @param size The size of the buffer. 79 */ 80 static bool bc_read_binary(const char *buf, size_t size) { 81 82 size_t i; 83 84 for (i = 0; i < size; ++i) { 85 if (BC_ERR(BC_READ_BIN_CHAR(buf[i]))) return true; 86 } 87 88 return false; 89 } 90 91 bool bc_read_buf(BcVec *vec, char *buf, size_t *buf_len) { 92 93 char *nl; 94 95 // If nothing there, return. 96 if (!*buf_len) return false; 97 98 // Find the newline. 99 nl = strchr(buf, '\n'); 100 101 // If a newline exists... 102 if (nl != NULL) { 103 104 // Get the size of the data up to, and including, the newline. 105 size_t nllen = (size_t) ((nl + 1) - buf); 106 107 nllen = *buf_len >= nllen ? nllen : *buf_len; 108 109 // Move data into the vector, and move the rest of the data in the 110 // buffer up. 111 bc_vec_npush(vec, nllen, buf); 112 *buf_len -= nllen; 113 memmove(buf, nl + 1, *buf_len + 1); 114 115 return true; 116 } 117 118 // Just put the data into the vector. 119 bc_vec_npush(vec, *buf_len, buf); 120 *buf_len = 0; 121 122 return false; 123 } 124 125 BcStatus bc_read_chars(BcVec *vec, const char *prompt) { 126 127 bool done = false; 128 129 assert(vec != NULL && vec->size == sizeof(char)); 130 131 BC_SIG_ASSERT_NOT_LOCKED; 132 133 // Clear the vector. 134 bc_vec_popAll(vec); 135 136 // Handle the prompt, if desired. 137 if (BC_PROMPT) { 138 bc_file_puts(&vm.fout, bc_flush_none, prompt); 139 bc_file_flush(&vm.fout, bc_flush_none); 140 } 141 142 // Try reading from the buffer, and if successful, just return. 143 if (bc_read_buf(vec, vm.buf, &vm.buf_len)) { 144 bc_vec_pushByte(vec, '\0'); 145 return BC_STATUS_SUCCESS; 146 } 147 148 // Loop until we have something. 149 while (!done) { 150 151 ssize_t r; 152 153 BC_SIG_LOCK; 154 155 // Read data from stdin. 156 r = read(STDIN_FILENO, vm.buf + vm.buf_len, 157 BC_VM_STDIN_BUF_SIZE - vm.buf_len); 158 159 // If there was an error... 160 if (BC_UNLIKELY(r < 0)) { 161 162 // If interupted... 163 if (errno == EINTR) { 164 165 // Jump out if we are supposed to quit, which certain signals 166 // will require. 167 if (vm.status == (sig_atomic_t) BC_STATUS_QUIT) BC_JMP; 168 169 assert(vm.sig); 170 171 // Clear the signal and status. 172 vm.sig = 0; 173 vm.status = (sig_atomic_t) BC_STATUS_SUCCESS; 174 175 // Print the ready message and prompt again. 176 bc_file_puts(&vm.fout, bc_flush_none, bc_program_ready_msg); 177 if (BC_PROMPT) bc_file_puts(&vm.fout, bc_flush_none, prompt); 178 bc_file_flush(&vm.fout, bc_flush_none); 179 180 BC_SIG_UNLOCK; 181 182 continue; 183 } 184 185 BC_SIG_UNLOCK; 186 187 // If we get here, it's bad. Barf. 188 bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); 189 } 190 191 BC_SIG_UNLOCK; 192 193 // If we read nothing, make sure to terminate the string and return EOF. 194 if (r == 0) { 195 bc_vec_pushByte(vec, '\0'); 196 return BC_STATUS_EOF; 197 } 198 199 // Add to the buffer. 200 vm.buf_len += (size_t) r; 201 vm.buf[vm.buf_len] = '\0'; 202 203 // Read from the buffer. 204 done = bc_read_buf(vec, vm.buf, &vm.buf_len); 205 } 206 207 // Terminate the string. 208 bc_vec_pushByte(vec, '\0'); 209 210 return BC_STATUS_SUCCESS; 211 } 212 213 BcStatus bc_read_line(BcVec *vec, const char *prompt) { 214 215 BcStatus s; 216 217 #if BC_ENABLE_HISTORY 218 // Get a line from either history or manual reading. 219 if (BC_TTY && !vm.history.badTerm) 220 s = bc_history_line(&vm.history, vec, prompt); 221 else s = bc_read_chars(vec, prompt); 222 #else // BC_ENABLE_HISTORY 223 s = bc_read_chars(vec, prompt); 224 #endif // BC_ENABLE_HISTORY 225 226 if (BC_ERR(bc_read_binary(vec->v, vec->len - 1))) 227 bc_verr(BC_ERR_FATAL_BIN_FILE, bc_program_stdin_name); 228 229 return s; 230 } 231 232 char* bc_read_file(const char *path) { 233 234 BcErr e = BC_ERR_FATAL_IO_ERR; 235 size_t size, r; 236 struct stat pstat; 237 int fd; 238 char* buf; 239 240 BC_SIG_ASSERT_LOCKED; 241 242 assert(path != NULL); 243 244 #ifndef NDEBUG 245 // Need this to quiet MSan. 246 memset(&pstat, 0, sizeof(struct stat)); 247 #endif // NDEBUG 248 249 fd = bc_read_open(path, O_RDONLY); 250 251 // If we can't read a file, we just barf. 252 if (BC_ERR(fd < 0)) bc_verr(BC_ERR_FATAL_FILE_ERR, path); 253 254 // The reason we call fstat is to eliminate TOCTOU race conditions. This 255 // way, we have an open file, so it's not going anywhere. 256 if (BC_ERR(fstat(fd, &pstat) == -1)) goto malloc_err; 257 258 // Make sure it's not a directory. 259 if (BC_ERR(S_ISDIR(pstat.st_mode))) { 260 e = BC_ERR_FATAL_PATH_DIR; 261 goto malloc_err; 262 } 263 264 // Get the size of the file and allocate that much. 265 size = (size_t) pstat.st_size; 266 buf = bc_vm_malloc(size + 1); 267 268 // Read the file. We just bail if a signal interrupts. This is so that users 269 // can interrupt the reading of big files if they want. 270 r = (size_t) read(fd, buf, size); 271 if (BC_ERR(r != size)) goto read_err; 272 273 // Got to have a nul byte. 274 buf[size] = '\0'; 275 276 if (BC_ERR(bc_read_binary(buf, size))) { 277 e = BC_ERR_FATAL_BIN_FILE; 278 goto read_err; 279 } 280 281 close(fd); 282 283 return buf; 284 285 read_err: 286 free(buf); 287 malloc_err: 288 close(fd); 289 bc_verr(e, path); 290 return NULL; 291 } 292