/* * Copyright 2017, Data61 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) * ABN 41 687 119 230. * * This software may be distributed and modified according to the terms of * the BSD 2-Clause license. Note that NO WARRANTY is provided. * See "LICENSE_BSD2.txt" for details. * * @TAG(DATA61_BSD) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "arch_stdio.h" #define FD_TABLE_SIZE(x) (sizeof(muslcsys_fd_t) * (x)) /* this implementation does not allow users to close STDOUT or STDERR, so they can't be freed */ #define FREE_FD_TABLE_SIZE(x) (sizeof(int) * ((x) - FIRST_USER_FD)) static void *cpio_archive_symbol; static unsigned long cpio_archive_len; static muslcsys_cpio_get_file_fn_t cpio_get_file_impl; /* We need to wrap this in the config to prevent linker errors */ #ifdef CONFIG_LIB_SEL4_MUSLC_SYS_CPIO_FS extern char _cpio_archive[]; #endif /* file table, indexed by file descriptor */ static muslcsys_fd_t *fd_table = NULL; /* stack of free file descriptors */ static int *free_fd_table = NULL; /* head of the stack */ static int free_fd_table_index; /* total number of fds */ static int num_fds = 256; void add_free_fd(int fd) { get_fd_struct(fd)->filetype = FILE_TYPE_FREE; free_fd_table_index++; assert(free_fd_table_index < num_fds); free_fd_table[free_fd_table_index] = fd; } int get_free_fd(void) { if (free_fd_table_index == -1) { return -EMFILE; } free_fd_table_index--; return free_fd_table[free_fd_table_index + 1]; } int valid_fd(int fd) { return fd < num_fds && fd >= FIRST_USER_FD; } static int allocate_file_table(void) { fd_table = malloc(FD_TABLE_SIZE(num_fds)); if (fd_table == NULL) { return -ENOMEM; } free_fd_table = malloc(FREE_FD_TABLE_SIZE(num_fds)); if (free_fd_table == NULL) { free(fd_table); return -ENOMEM; } free_fd_table_index = -1; /* populate free list */ for (int i = FIRST_USER_FD; i < num_fds; i++) { add_free_fd(i); } return 0; } int grow_fds(int how_much) { int new_num_fds = num_fds + how_much; /* Ensure file table exists */ if (fd_table == NULL) { if (allocate_file_table() == -ENOMEM) { return -ENOMEM; } } /* allocate new arrays */ muslcsys_fd_t *new_fd_table = malloc(FD_TABLE_SIZE(new_num_fds)); if (!new_fd_table) { LOG_ERROR("Failed to allocate new_vfds\n"); return -ENOMEM; } int *new_free_fd_table = malloc(FREE_FD_TABLE_SIZE(new_num_fds)); if (!new_free_fd_table) { free(new_fd_table); ZF_LOGE("Failed to allocate free fd table\n"); return -ENOMEM; } /* copy old contents */ memcpy(new_free_fd_table, free_fd_table, FREE_FD_TABLE_SIZE(num_fds)); memcpy(new_fd_table, fd_table, FD_TABLE_SIZE(num_fds)); /* free old tables */ free(fd_table); free(free_fd_table); /* update global pointers */ fd_table = new_fd_table; free_fd_table = new_free_fd_table; /* Update the size */ num_fds = new_num_fds; /* add all of the new available fds to the free list */ for (int i = num_fds; i < new_num_fds; i++) { add_free_fd(i); } return 0; } int allocate_fd() { if (fd_table == NULL) { if (allocate_file_table() == -ENOMEM) { return -ENOMEM; } } return get_free_fd(); } muslcsys_fd_t *get_fd_struct(int fd) { assert(fd < num_fds && fd >= FIRST_USER_FD); return &fd_table[fd - FIRST_USER_FD]; } static size_t sys_platform_write(void *data, size_t count) { char *realdata = data; return __arch_write(realdata, count); } static long sys_open_impl(const char *pathname, int flags, mode_t mode) { /* mask out flags we can support */ flags &= ~O_LARGEFILE; /* only support reading in basic modes */ if (flags != O_RDONLY) { ZF_LOGE("Open only supports O_RDONLY, not 0x%x on %s\n", flags, pathname); assert(flags == O_RDONLY); return -EINVAL; } /* as we do not support create, ignore the mode */ long unsigned int size; char *file = NULL; if (cpio_get_file_impl && cpio_archive_symbol) { file = cpio_get_file_impl(cpio_archive_symbol, cpio_archive_len, pathname, &size); if (!file && strncmp(pathname, "./", 2) == 0) { file = cpio_get_file_impl(cpio_archive_symbol, cpio_archive_len, pathname + 2, &size); } } if (!file) { ZF_LOGE("Failed to open file %s\n", pathname); return -ENOENT; } int fd = allocate_fd(); if (fd == -EMFILE) { ZF_LOGE("Out of fds!\n"); return -EMFILE; } muslcsys_fd_t *fds = get_fd_struct(fd); fds->filetype = FILE_TYPE_CPIO; fds->data = malloc(sizeof(cpio_file_data_t)); if (!fds->data) { ZF_LOGE("Malloc failed\n"); add_free_fd(fd); return -ENOMEM; } cpio_file_data_t *fd_data = (cpio_file_data_t *)fds->data; fd_data->start = file; fd_data->size = size; fd_data->current = 0; return fd; } long sys_open(va_list ap) { const char *pathname = va_arg(ap, const char *); int flags = va_arg(ap, int); mode_t mode = va_arg(ap, mode_t); return sys_open_impl(pathname, flags, mode); } long sys_openat(va_list ap) { int dirfd = va_arg(ap, int); const char *pathname = va_arg(ap, const char *); int flags = va_arg(ap, int); mode_t mode = va_arg(ap, mode_t); if (dirfd != AT_FDCWD) { ZF_LOGE("Openat only supports relative path to the current working directory\n"); return -EINVAL; } return sys_open_impl(pathname, flags, mode); } long sys_close(va_list ap) { int fd = va_arg(ap, int); if (fd < FIRST_USER_FD) { assert(!"not implemented"); return -EBADF; } if (!valid_fd(fd)) { return -EBADF; } muslcsys_fd_t *fds = get_fd_struct(fd); if (fds->filetype == FILE_TYPE_CPIO) { free(fds->data); } else { assert(!"not implemented"); } add_free_fd(fd); return 0; } static write_buf_fn stdio_write = sys_platform_write; write_buf_fn sel4muslcsys_register_stdio_write_fn(write_buf_fn write_fn) { write_buf_fn old = stdio_write; stdio_write = write_fn; return old; } /* Writev syscall implementation for muslc. Only implemented for stdin and stdout. */ long sys_writev(va_list ap) { int fildes = va_arg(ap, int); struct iovec *iov = va_arg(ap, struct iovec *); int iovcnt = va_arg(ap, int); long long sum = 0; ssize_t ret = 0; /* The iovcnt argument is valid if greater than 0 and less than or equal to IOV_MAX. */ if (iovcnt <= 0 || iovcnt > IOV_MAX) { return -EINVAL; } /* The sum of iov_len is valid if less than or equal to SSIZE_MAX i.e. cannot overflow a ssize_t. */ for (int i = 0; i < iovcnt; i++) { sum += (long long)iov[i].iov_len; if (sum > SSIZE_MAX) { return -EINVAL; } } /* If all the iov_len members in the array are 0, return 0. */ if (!sum) { return 0; } /* Write the buffer to console if the fd is for stdout or stderr. */ if (fildes == STDOUT_FILENO || fildes == STDERR_FILENO) { if (stdio_write == NULL) { ZF_LOGD("No standard out function registered"); } for (int i = 0; i < iovcnt; i++) { if (stdio_write == NULL) { ret += iov[i].iov_len; } else { ret += stdio_write(iov[i].iov_base, iov[i].iov_len); } } } else { assert(!"Not implemented"); return -EBADF; } return ret; } long sys_write(va_list ap) { int fd = va_arg(ap, int); void *buf = va_arg(ap, void *); size_t count = va_arg(ap, size_t); /* construct an iovec and call writev */ struct iovec iov = {.iov_base = buf, .iov_len = count }; return writev(fd, &iov, 1); } long sys_readv(va_list ap) { int fd = va_arg(ap, int); struct iovec *iov = va_arg(ap, struct iovec *); int iovcnt = va_arg(ap, int); int i; long read; if (fd < FIRST_USER_FD) { assert(!"not implemented"); return -EBADF; } if (!valid_fd(fd)) { return -EBADF; } /* files can only be opened for reading so no need to check any permissions. * just get straight into it */ muslcsys_fd_t *muslc_fd = get_fd_struct(fd); if (muslc_fd->filetype != FILE_TYPE_CPIO) { assert(!"not implemented"); return -EINVAL; } cpio_file_data_t *cpio_fd = muslc_fd->data; read = 0; for (i = 0; i < iovcnt && cpio_fd->current < cpio_fd->size; i++) { long max = cpio_fd->size - cpio_fd->current; long len = max < iov[i].iov_len ? max : iov[i].iov_len; memcpy(iov[i].iov_base, cpio_fd->start + cpio_fd->current, len); cpio_fd->current += len; read += len; } return read; } long sys_read(va_list ap) { int fd = va_arg(ap, int); void *buf = va_arg(ap, void *); size_t count = va_arg(ap, size_t); /* construct an iovec and call readv */ struct iovec iov = {.iov_base = buf, .iov_len = count }; return readv(fd, &iov, 1); } long sys_ioctl(va_list ap) { int fd = va_arg(ap, int); int request = va_arg(ap, int); (void)request; /* muslc does some ioctls to stdout, so just allow these to silently go through */ if (fd == STDOUT_FILENO) { return 0; } assert(!"not implemented"); return 0; } long sys_prlimit64(va_list ap) { pid_t pid = va_arg(ap, pid_t); int resource = va_arg(ap, int); const struct rlimit *new_limit = va_arg(ap, const struct rlimit *); struct rlimit *old_limit = va_arg(ap, struct rlimit *); int result = 0; /* we have no concept of pids, so ignore this for now */ (void) pid; if (resource == RLIMIT_NOFILE) { if (old_limit) { old_limit->rlim_cur = num_fds; /* pick some arbitrarily big number for max. In practice we are only constrained * by how large an array we can malloc */ old_limit->rlim_max = 65536; } if (new_limit) { if (new_limit->rlim_cur < num_fds) { printf("Trying to reduce open file limit. Operation not supported, ignoring\n"); } else { result = grow_fds(new_limit->rlim_cur - num_fds); } } } else { assert(!"not implemented"); } return result; } static int safe_addition(int a, int b) { return !(a >= 0 && b > INT_MAX - a) && !(a < 0 && b < INT_MAX - a); } long sys_lseek(va_list ap) { int fd = va_arg(ap, int); off_t offset = va_arg(ap, off_t); int whence = va_arg(ap, int); if (!valid_fd(fd)) { return -EBADF; } muslcsys_fd_t *muslc_fd = get_fd_struct(fd); if (muslc_fd == NULL) { return -EBADF; } if (muslc_fd->filetype != FILE_TYPE_CPIO) { assert(!"Not implemented\n"); return -EBADF; } /* if its a valid fd it must be a cpio file, we * don't support anything else */ cpio_file_data_t *cpio_fd = muslc_fd->data; int new_offset = 0; switch (whence) { case SEEK_SET: new_offset = offset; break; case SEEK_CUR: if (!safe_addition(cpio_fd->current, offset)) { return -EOVERFLOW; } new_offset = cpio_fd->current + offset; break; case SEEK_END: if (offset > 0) { /* can't seek beyond the end of the cpio file */ return -EINVAL; } new_offset = cpio_fd->size + offset; break; default: return -EINVAL; } if (new_offset < 0) { return -EINVAL; /* can't seek past the end of the cpio file */ } else if (new_offset > cpio_fd->size) { return -EINVAL; } cpio_fd->current = new_offset; return new_offset; } long syscall(long n, ...); long sys__llseek(va_list ap) { int fd = va_arg(ap, int); uint32_t offset_high = va_arg(ap, uint32_t); uint32_t offset_low = va_arg(ap, uint32_t); off_t *result = va_arg(ap, off_t *); int whence = va_arg(ap, int); /* need to directly call syscall to prevent circular call to this function. the llseek function * is used when off_t is a 64bit type (see the lseek definition in muslc), Underneath the * hood all syscall arguments get cast to a 32bit long before the actual syscall function * gets called. This makes calling the old lseek syscall awkward as it will attempt to pull * a 64bit off_t off its syscall args, but we had all our arguments forced down to 32bits * before they got passed over. Therefore we can actually just pass the high and low * and everything will work. Assumptions on endianess */ long ret = syscall(SYS_lseek, fd, (uint32_t)offset_low, (uint32_t)offset_high, whence); if (ret == -1) { /* propogate error up. see __syscall_ret to understand */ return -errno; } if (result) { *result = (off_t)ret; } return 0; } long sys_access(va_list ap) { const char *pathname = va_arg(ap, const char *); int mode = va_arg(ap, int); /* just try and open. currently we only support reading with the CPIO file system */ if (mode == F_OK || mode == R_OK) { int fd = open(pathname, O_RDONLY, 0); if (fd < 0) { return -EACCES; } close(fd); return 0; } ZF_LOGE("Must pass F_OK or R_OK to %s\n", __FUNCTION__); return -EACCES; } void muslcsys_install_cpio_interface(void *cpio_symbol, unsigned long cpio_len, muslcsys_cpio_get_file_fn_t fn) { cpio_archive_symbol = cpio_symbol; cpio_archive_len = cpio_len; cpio_get_file_impl = fn; }