blob: 4544371225e5f346857651401cc0a31ecf546e61 [file] [log] [blame]
/*
* 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 <autoconf.h>
#include <sel4muslcsys/gen_config.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sel4/sel4.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <bits/syscall.h>
#include <sel4utils/util.h>
#include <muslcsys/io.h>
#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;
}