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