1/* 2 * Copyright 2017, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12 13#include <autoconf.h> 14#include <sel4muslcsys/gen_config.h> 15#include <assert.h> 16#include <errno.h> 17#include <fcntl.h> 18#include <limits.h> 19#include <stdarg.h> 20#include <string.h> 21#include <stdio.h> 22#include <stdlib.h> 23#include <unistd.h> 24 25#include <sel4/sel4.h> 26 27#include <sys/resource.h> 28#include <sys/mman.h> 29#include <sys/uio.h> 30 31#include <sys/types.h> 32#include <sys/socket.h> 33#include <bits/syscall.h> 34 35#include <sel4utils/util.h> 36 37#include <muslcsys/io.h> 38#include "arch_stdio.h" 39 40#define FD_TABLE_SIZE(x) (sizeof(muslcsys_fd_t) * (x)) 41/* this implementation does not allow users to close STDOUT or STDERR, so they can't be freed */ 42#define FREE_FD_TABLE_SIZE(x) (sizeof(int) * ((x) - FIRST_USER_FD)) 43 44static void *cpio_archive_symbol; 45static unsigned long cpio_archive_len; 46static muslcsys_cpio_get_file_fn_t cpio_get_file_impl; 47 48/* We need to wrap this in the config to prevent linker errors */ 49#ifdef CONFIG_LIB_SEL4_MUSLC_SYS_CPIO_FS 50extern char _cpio_archive[]; 51#endif 52 53/* file table, indexed by file descriptor */ 54static muslcsys_fd_t *fd_table = NULL; 55/* stack of free file descriptors */ 56static int *free_fd_table = NULL; 57/* head of the stack */ 58static int free_fd_table_index; 59/* total number of fds */ 60static int num_fds = 256; 61 62void add_free_fd(int fd) 63{ 64 get_fd_struct(fd)->filetype = FILE_TYPE_FREE; 65 free_fd_table_index++; 66 assert(free_fd_table_index < num_fds); 67 free_fd_table[free_fd_table_index] = fd; 68} 69 70int get_free_fd(void) 71{ 72 if (free_fd_table_index == -1) { 73 return -EMFILE; 74 } 75 76 free_fd_table_index--; 77 return free_fd_table[free_fd_table_index + 1]; 78} 79 80int valid_fd(int fd) 81{ 82 return fd < num_fds && fd >= FIRST_USER_FD; 83} 84 85static int allocate_file_table(void) 86{ 87 fd_table = malloc(FD_TABLE_SIZE(num_fds)); 88 if (fd_table == NULL) { 89 return -ENOMEM; 90 } 91 92 free_fd_table = malloc(FREE_FD_TABLE_SIZE(num_fds)); 93 if (free_fd_table == NULL) { 94 free(fd_table); 95 return -ENOMEM; 96 } 97 98 free_fd_table_index = -1; 99 100 /* populate free list */ 101 for (int i = FIRST_USER_FD; i < num_fds; i++) { 102 add_free_fd(i); 103 } 104 105 return 0; 106} 107 108int grow_fds(int how_much) 109{ 110 int new_num_fds = num_fds + how_much; 111 112 /* Ensure file table exists */ 113 if (fd_table == NULL) { 114 if (allocate_file_table() == -ENOMEM) { 115 return -ENOMEM; 116 } 117 } 118 119 /* allocate new arrays */ 120 muslcsys_fd_t *new_fd_table = malloc(FD_TABLE_SIZE(new_num_fds)); 121 if (!new_fd_table) { 122 LOG_ERROR("Failed to allocate new_vfds\n"); 123 return -ENOMEM; 124 } 125 126 int *new_free_fd_table = malloc(FREE_FD_TABLE_SIZE(new_num_fds)); 127 if (!new_free_fd_table) { 128 free(new_fd_table); 129 ZF_LOGE("Failed to allocate free fd table\n"); 130 return -ENOMEM; 131 } 132 133 /* copy old contents */ 134 memcpy(new_free_fd_table, free_fd_table, FREE_FD_TABLE_SIZE(num_fds)); 135 memcpy(new_fd_table, fd_table, FD_TABLE_SIZE(num_fds)); 136 137 /* free old tables */ 138 free(fd_table); 139 free(free_fd_table); 140 141 /* update global pointers */ 142 fd_table = new_fd_table; 143 free_fd_table = new_free_fd_table; 144 145 /* Update the size */ 146 num_fds = new_num_fds; 147 148 /* add all of the new available fds to the free list */ 149 for (int i = num_fds; i < new_num_fds; i++) { 150 add_free_fd(i); 151 } 152 return 0; 153} 154 155int allocate_fd() 156{ 157 if (fd_table == NULL) { 158 if (allocate_file_table() == -ENOMEM) { 159 return -ENOMEM; 160 } 161 } 162 163 return get_free_fd(); 164} 165 166muslcsys_fd_t *get_fd_struct(int fd) 167{ 168 assert(fd < num_fds && fd >= FIRST_USER_FD); 169 return &fd_table[fd - FIRST_USER_FD]; 170} 171 172static size_t sys_platform_write(void *data, size_t count) 173{ 174 char *realdata = data; 175 return __arch_write(realdata, count); 176} 177 178static long sys_open_impl(const char *pathname, int flags, mode_t mode) 179{ 180 /* mask out flags we can support */ 181 flags &= ~O_LARGEFILE; 182 /* only support reading in basic modes */ 183 if (flags != O_RDONLY) { 184 ZF_LOGE("Open only supports O_RDONLY, not 0x%x on %s\n", flags, pathname); 185 assert(flags == O_RDONLY); 186 return -EINVAL; 187 } 188 /* as we do not support create, ignore the mode */ 189 long unsigned int size; 190 char *file = NULL; 191 if (cpio_get_file_impl && cpio_archive_symbol) { 192 file = cpio_get_file_impl(cpio_archive_symbol, cpio_archive_len, pathname, &size); 193 if (!file && strncmp(pathname, "./", 2) == 0) { 194 file = cpio_get_file_impl(cpio_archive_symbol, cpio_archive_len, pathname + 2, &size); 195 } 196 } 197 if (!file) { 198 ZF_LOGE("Failed to open file %s\n", pathname); 199 return -ENOENT; 200 } 201 int fd = allocate_fd(); 202 if (fd == -EMFILE) { 203 ZF_LOGE("Out of fds!\n"); 204 return -EMFILE; 205 } 206 207 muslcsys_fd_t *fds = get_fd_struct(fd); 208 fds->filetype = FILE_TYPE_CPIO; 209 fds->data = malloc(sizeof(cpio_file_data_t)); 210 if (!fds->data) { 211 ZF_LOGE("Malloc failed\n"); 212 add_free_fd(fd); 213 return -ENOMEM; 214 } 215 cpio_file_data_t *fd_data = (cpio_file_data_t *)fds->data; 216 fd_data->start = file; 217 fd_data->size = size; 218 fd_data->current = 0; 219 return fd; 220} 221 222long sys_open(va_list ap) 223{ 224 const char *pathname = va_arg(ap, const char *); 225 int flags = va_arg(ap, int); 226 mode_t mode = va_arg(ap, mode_t); 227 228 return sys_open_impl(pathname, flags, mode); 229} 230 231long sys_openat(va_list ap) 232{ 233 int dirfd = va_arg(ap, int); 234 const char *pathname = va_arg(ap, const char *); 235 int flags = va_arg(ap, int); 236 mode_t mode = va_arg(ap, mode_t); 237 238 if (dirfd != AT_FDCWD) { 239 ZF_LOGE("Openat only supports relative path to the current working directory\n"); 240 return -EINVAL; 241 } 242 243 return sys_open_impl(pathname, flags, mode); 244} 245 246long sys_close(va_list ap) 247{ 248 int fd = va_arg(ap, int); 249 if (fd < FIRST_USER_FD) { 250 assert(!"not implemented"); 251 return -EBADF; 252 } 253 254 if (!valid_fd(fd)) { 255 return -EBADF; 256 } 257 258 muslcsys_fd_t *fds = get_fd_struct(fd); 259 260 if (fds->filetype == FILE_TYPE_CPIO) { 261 free(fds->data); 262 } else { 263 assert(!"not implemented"); 264 } 265 add_free_fd(fd); 266 return 0; 267} 268 269 270static write_buf_fn stdio_write = sys_platform_write; 271 272write_buf_fn sel4muslcsys_register_stdio_write_fn(write_buf_fn write_fn) 273{ 274 write_buf_fn old = stdio_write; 275 stdio_write = write_fn; 276 return old; 277} 278 279 280/* Writev syscall implementation for muslc. Only implemented for stdin and stdout. */ 281long sys_writev(va_list ap) 282{ 283 int fildes = va_arg(ap, int); 284 struct iovec *iov = va_arg(ap, struct iovec *); 285 int iovcnt = va_arg(ap, int); 286 287 long long sum = 0; 288 ssize_t ret = 0; 289 290 /* The iovcnt argument is valid if greater than 0 and less than or equal to IOV_MAX. */ 291 if (iovcnt <= 0 || iovcnt > IOV_MAX) { 292 return -EINVAL; 293 } 294 295 /* The sum of iov_len is valid if less than or equal to SSIZE_MAX i.e. cannot overflow 296 a ssize_t. */ 297 for (int i = 0; i < iovcnt; i++) { 298 sum += (long long)iov[i].iov_len; 299 if (sum > SSIZE_MAX) { 300 return -EINVAL; 301 } 302 } 303 304 /* If all the iov_len members in the array are 0, return 0. */ 305 if (!sum) { 306 return 0; 307 } 308 309 /* Write the buffer to console if the fd is for stdout or stderr. */ 310 if (fildes == STDOUT_FILENO || fildes == STDERR_FILENO) { 311 if (stdio_write == NULL) { 312 ZF_LOGD("No standard out function registered"); 313 } 314 for (int i = 0; i < iovcnt; i++) { 315 if (stdio_write == NULL) { 316 ret += iov[i].iov_len; 317 } else { 318 ret += stdio_write(iov[i].iov_base, iov[i].iov_len); 319 } 320 } 321 } else { 322 assert(!"Not implemented"); 323 return -EBADF; 324 } 325 326 return ret; 327} 328 329long sys_write(va_list ap) 330{ 331 332 int fd = va_arg(ap, int); 333 void *buf = va_arg(ap, void *); 334 size_t count = va_arg(ap, size_t); 335 /* construct an iovec and call writev */ 336 struct iovec iov = {.iov_base = buf, .iov_len = count }; 337 return writev(fd, &iov, 1); 338} 339 340long sys_readv(va_list ap) 341{ 342 int fd = va_arg(ap, int); 343 struct iovec *iov = va_arg(ap, struct iovec *); 344 int iovcnt = va_arg(ap, int); 345 int i; 346 long read; 347 if (fd < FIRST_USER_FD) { 348 assert(!"not implemented"); 349 return -EBADF; 350 } 351 352 if (!valid_fd(fd)) { 353 return -EBADF; 354 } 355 356 /* files can only be opened for reading so no need to check any permissions. 357 * just get straight into it 358 */ 359 muslcsys_fd_t *muslc_fd = get_fd_struct(fd); 360 if (muslc_fd->filetype != FILE_TYPE_CPIO) { 361 assert(!"not implemented"); 362 return -EINVAL; 363 } 364 cpio_file_data_t *cpio_fd = muslc_fd->data; 365 read = 0; 366 for (i = 0; i < iovcnt && cpio_fd->current < cpio_fd->size; i++) { 367 long max = cpio_fd->size - cpio_fd->current; 368 long len = max < iov[i].iov_len ? max : iov[i].iov_len; 369 memcpy(iov[i].iov_base, cpio_fd->start + cpio_fd->current, len); 370 cpio_fd->current += len; 371 read += len; 372 } 373 return read; 374} 375 376long sys_read(va_list ap) 377{ 378 int fd = va_arg(ap, int); 379 void *buf = va_arg(ap, void *); 380 size_t count = va_arg(ap, size_t); 381 /* construct an iovec and call readv */ 382 struct iovec iov = {.iov_base = buf, .iov_len = count }; 383 return readv(fd, &iov, 1); 384} 385 386long sys_ioctl(va_list ap) 387{ 388 int fd = va_arg(ap, int); 389 int request = va_arg(ap, int); 390 (void)request; 391 /* muslc does some ioctls to stdout, so just allow these to silently 392 go through */ 393 if (fd == STDOUT_FILENO) { 394 return 0; 395 } 396 assert(!"not implemented"); 397 return 0; 398} 399 400long sys_prlimit64(va_list ap) 401{ 402 pid_t pid = va_arg(ap, pid_t); 403 int resource = va_arg(ap, int); 404 const struct rlimit *new_limit = va_arg(ap, const struct rlimit *); 405 struct rlimit *old_limit = va_arg(ap, struct rlimit *); 406 int result = 0; 407 408 /* we have no concept of pids, so ignore this for now */ 409 (void) pid; 410 411 if (resource == RLIMIT_NOFILE) { 412 if (old_limit) { 413 old_limit->rlim_cur = num_fds; 414 /* pick some arbitrarily big number for max. In practice we are only constrained 415 * by how large an array we can malloc */ 416 old_limit->rlim_max = 65536; 417 } 418 419 if (new_limit) { 420 if (new_limit->rlim_cur < num_fds) { 421 printf("Trying to reduce open file limit. Operation not supported, ignoring\n"); 422 } else { 423 result = grow_fds(new_limit->rlim_cur - num_fds); 424 } 425 } 426 } else { 427 assert(!"not implemented"); 428 } 429 430 return result; 431} 432 433static int safe_addition(int a, int b) 434{ 435 return !(a >= 0 && b > INT_MAX - a) && 436 !(a < 0 && b < INT_MAX - a); 437} 438 439long sys_lseek(va_list ap) 440{ 441 int fd = va_arg(ap, int); 442 off_t offset = va_arg(ap, off_t); 443 int whence = va_arg(ap, int); 444 445 if (!valid_fd(fd)) { 446 return -EBADF; 447 } 448 449 muslcsys_fd_t *muslc_fd = get_fd_struct(fd); 450 if (muslc_fd == NULL) { 451 return -EBADF; 452 } 453 454 if (muslc_fd->filetype != FILE_TYPE_CPIO) { 455 assert(!"Not implemented\n"); 456 return -EBADF; 457 } 458 459 /* if its a valid fd it must be a cpio file, we 460 * don't support anything else */ 461 cpio_file_data_t *cpio_fd = muslc_fd->data; 462 463 int new_offset = 0; 464 switch (whence) { 465 case SEEK_SET: 466 new_offset = offset; 467 break; 468 case SEEK_CUR: 469 if (!safe_addition(cpio_fd->current, offset)) { 470 return -EOVERFLOW; 471 } 472 new_offset = cpio_fd->current + offset; 473 break; 474 case SEEK_END: 475 if (offset > 0) { 476 /* can't seek beyond the end of the cpio file */ 477 return -EINVAL; 478 } 479 new_offset = cpio_fd->size + offset; 480 break; 481 default: 482 return -EINVAL; 483 } 484 485 if (new_offset < 0) { 486 return -EINVAL; 487 /* can't seek past the end of the cpio file */ 488 } else if (new_offset > cpio_fd->size) { 489 return -EINVAL; 490 } 491 492 cpio_fd->current = new_offset; 493 494 return new_offset; 495} 496 497long syscall(long n, ...); 498 499long sys__llseek(va_list ap) 500{ 501 int fd = va_arg(ap, int); 502 uint32_t offset_high = va_arg(ap, uint32_t); 503 uint32_t offset_low = va_arg(ap, uint32_t); 504 off_t *result = va_arg(ap, off_t *); 505 int whence = va_arg(ap, int); 506 /* need to directly call syscall to prevent circular call to this function. the llseek function 507 * is used when off_t is a 64bit type (see the lseek definition in muslc), Underneath the 508 * hood all syscall arguments get cast to a 32bit long before the actual syscall function 509 * gets called. This makes calling the old lseek syscall awkward as it will attempt to pull 510 * a 64bit off_t off its syscall args, but we had all our arguments forced down to 32bits 511 * before they got passed over. Therefore we can actually just pass the high and low 512 * and everything will work. Assumptions on endianess */ 513 long ret = syscall(SYS_lseek, fd, (uint32_t)offset_low, (uint32_t)offset_high, whence); 514 if (ret == -1) { 515 /* propogate error up. see __syscall_ret to understand */ 516 return -errno; 517 } 518 if (result) { 519 *result = (off_t)ret; 520 } 521 return 0; 522} 523 524long sys_access(va_list ap) 525{ 526 const char *pathname = va_arg(ap, const char *); 527 int mode = va_arg(ap, int); 528 /* just try and open. currently we only support reading with the CPIO file system */ 529 if (mode == F_OK || mode == R_OK) { 530 int fd = open(pathname, O_RDONLY, 0); 531 if (fd < 0) { 532 return -EACCES; 533 } 534 close(fd); 535 return 0; 536 } 537 ZF_LOGE("Must pass F_OK or R_OK to %s\n", __FUNCTION__); 538 return -EACCES; 539} 540 541void muslcsys_install_cpio_interface(void *cpio_symbol, unsigned long cpio_len, 542 muslcsys_cpio_get_file_fn_t fn) 543{ 544 cpio_archive_symbol = cpio_symbol; 545 cpio_archive_len = cpio_len; 546 cpio_get_file_impl = fn; 547} 548