diff --git a/musl_src.gni b/musl_src.gni index 5ca170c090a85be309cb7fbc6f0a184c4c729876..5530ce9e0f54b0f880922ec408ee8d38a3aaf966 100644 --- a/musl_src.gni +++ b/musl_src.gni @@ -2175,6 +2175,13 @@ musl_src_porting_file = [ "src/stdio/fmemopen.c", "src/stdio/freopen.c", "src/stdio/stdin.c", + "src/stdio/__fmodeflags.c", + "src/stdio/fopen.c", + "src/stdio/ofl.c", + "src/stdio/fclose.c", + "src/stdio/__toread.c", + "src/stdio/__towrite.c", + "src/stdio/stderr.c", "src/internal/stdio_impl.h", "src/internal/vdso.c", "src/time/clock_gettime.c", diff --git a/porting/linux/user/src/internal/stdio_impl.h b/porting/linux/user/src/internal/stdio_impl.h index fab7bdd5844221c3a2b8b2c8065e3c3007222c4f..3fec9b2c18b89f42e628f5ae47844dadebbd4629 100644 --- a/porting/linux/user/src/internal/stdio_impl.h +++ b/porting/linux/user/src/internal/stdio_impl.h @@ -17,6 +17,7 @@ #define F_ERR 32 #define F_SVB 64 #define F_APP 128 +#define F_NOBUF 256 struct _IO_FILE { unsigned flags; @@ -31,6 +32,14 @@ struct _IO_FILE { off_t (*seek)(FILE *, off_t, int); unsigned char *buf; size_t buf_size; + /* when allocating buffer dynamically, base == buf - UNGET, + * free base when calling fclose. + * otherwise, base == NULL, cases: + * 1. in stdout, stdin, stdout, base is static array. + * 2. call setvbuf to set buffer or non-buffer. + * 3. call fmemopen, base == NULL && buf_size != 0. + */ + unsigned char *base; FILE *prev, *next; int fd; int pipe_pid; @@ -67,6 +76,7 @@ hidden ssize_t __flush_buffer(FILE *f); hidden int __toread(FILE *); hidden int __towrite(FILE *); +hidden int __falloc_buf(FILE *); hidden void __stdio_exit(void); hidden void __stdio_exit_needed(void); @@ -84,11 +94,14 @@ hidden size_t __fwritex(const unsigned char *, size_t, FILE *); hidden int __putc_unlocked(int, FILE *); hidden FILE *__fdopen(int, const char *); -hidden int __fmodeflags(const char *); +hidden FILE *__fdopenx(int, int); +hidden int __fmodeflags(const char *, int *); hidden FILE *__ofl_add(FILE *f); hidden FILE **__ofl_lock(void); hidden void __ofl_unlock(void); +hidden void __ofl_free(FILE *f); +hidden FILE *__ofl_alloc(); struct __pthread; hidden void __register_locked_file(FILE *, struct __pthread *); diff --git a/porting/linux/user/src/stdio/__fdopen.c b/porting/linux/user/src/stdio/__fdopen.c index e6f2edf5572e74bd52f37255bcd2e4f37ba7f8e9..416f7c021172735aa1e6f5dd867cba56837dc7c5 100644 --- a/porting/linux/user/src/stdio/__fdopen.c +++ b/porting/linux/user/src/stdio/__fdopen.c @@ -21,7 +21,7 @@ #include #include "libc.h" -static size_t get_bufsize(int fd) +static size_t __get_bufsize(int fd) { struct stat st; size_t buf_size = 0; @@ -37,50 +37,79 @@ static size_t get_bufsize(int fd) return buf_size; } -FILE *__fdopen(int fd, const char *mode) +int __falloc_buf(FILE *f) { - FILE *f; - struct winsize wsz; - size_t buf_size = 0; - - /* Check for valid initial mode character */ - if (!strchr("rwa", *mode)) { - errno = EINVAL; + /* return if already allocated, or F_NOBUF set */ + if (f->buf != NULL || f->buf_size != 0 || f->flags & F_NOBUF) { return 0; } + /* Default, base and buf are NULL,and buf_size = 0 */ + size_t buf_size = 0; + /* get buffer size via file stat */ - buf_size = get_bufsize(fd); + buf_size = __get_bufsize(f->fd); - /* Allocate FILE+buffer or fail */ - if (!(f = malloc(sizeof *f + UNGET + buf_size))) { - return 0; + /* alloc R/W buffer */ + f->base = (unsigned char *)malloc(UNGET + buf_size * sizeof(unsigned char)); + if (!f->base) { + errno = -ENOMEM; + return errno; } - /* Zero-fill only the struct, not the buffer */ - memset(f, 0, sizeof *f); + /* reserve UNGET buffer */ + f->buf = f->base + UNGET; + f->buf_size = buf_size; + + return 0; +} - /* Impose mode restrictions */ - if (!strchr(mode, '+')) { - f->flags = (*mode == 'r') ? F_NOWR : F_NORD; +FILE *__fdopen(int fd, const char *mode) +{ + FILE *f = NULL; + int file_flags = 0; + int mode_flags = 0; + + /* Compute the flags to pass to open() */ + mode_flags = __fmodeflags(mode, &file_flags); + if (mode_flags < 0) { + return NULL; } - /* Apply close-on-exec flag */ - if (strchr(mode, 'e')) { + if (mode_flags & O_CLOEXEC) { __syscall(SYS_fcntl, fd, F_SETFD, FD_CLOEXEC); } - /* Set append mode on fd if opened for append */ - if (*mode == 'a') { + if (mode_flags & O_APPEND) { int flags = __syscall(SYS_fcntl, fd, F_GETFL); if (!(flags & O_APPEND)) __syscall(SYS_fcntl, fd, F_SETFL, flags | O_APPEND); - f->flags |= F_APP; } + f = __fdopenx(fd, file_flags); + if (f) { + return f; + } + + return NULL; +} +weak_alias(__fdopen, fdopen); + +FILE *__fdopenx(int fd, int flags) +{ + FILE *f = 0; + struct winsize wsz; + + /* Allocate FILE or fail */ + if (!(f = __ofl_alloc())) { + return NULL; + } + + /* Zero-fill only the struct, not the buffer */ + memset(f, 0, sizeof *f); + + f->flags = flags; f->fd = fd; - f->buf = (unsigned char *)f + sizeof *f + UNGET; - f->buf_size = buf_size; /* Activate line buffered mode for terminals */ f->lbf = EOF; @@ -103,4 +132,3 @@ FILE *__fdopen(int fd, const char *mode) return __ofl_add(f); } -weak_alias(__fdopen, fdopen); diff --git a/porting/linux/user/src/stdio/__fmodeflags.c b/porting/linux/user/src/stdio/__fmodeflags.c new file mode 100644 index 0000000000000000000000000000000000000000..f738b270adf1060b7ab680daf1b56d951671a08e --- /dev/null +++ b/porting/linux/user/src/stdio/__fmodeflags.c @@ -0,0 +1,55 @@ +#include "stdio_impl.h" +#include +#include +#include + +int __fmodeflags(const char *mode, int *flags) +{ + int mode_flags = 0; + int options = 0; + + switch (*mode) { + case 'r': + mode_flags = O_RDONLY; + *flags = F_NOWR; + break; + case 'w': + mode_flags = O_WRONLY; + options = O_TRUNC | O_CREAT; + *flags = F_NORD; + break; + case 'a': + mode_flags = O_WRONLY; + options = O_APPEND | O_CREAT; + *flags = F_NORD | F_APP; + break; + default: + errno = EINVAL; + return -EINVAL; + } + + mode++; + while (*mode != '\0') { + switch (*mode) { + case '+': + mode_flags = O_RDWR; + *flags &= ~(F_NORD | F_NOWR); + break; + case 'x': + /* need O_CREAT check */ + options |= O_EXCL; + break; + case 'e': + options |= O_CLOEXEC; + break; + case 'b': + break; + default: + /* only accept "+xeb" */ + break; + } + mode++; + } + + return mode_flags | options; +} diff --git a/porting/linux/user/src/stdio/__toread.c b/porting/linux/user/src/stdio/__toread.c new file mode 100644 index 0000000000000000000000000000000000000000..b03f878d36c676f394a0c0a499873e63d12518d4 --- /dev/null +++ b/porting/linux/user/src/stdio/__toread.c @@ -0,0 +1,27 @@ +#include + +int __toread(FILE *f) +{ + f->mode |= f->mode-1; + if (f->wpos != f->wbase) f->write(f, 0, 0); + f->wpos = f->wbase = f->wend = 0; + if (f->flags & F_NORD) { + f->flags |= F_ERR; + return EOF; + } + + /* Alloc file buffer if needed */ + if (__falloc_buf(f) < 0) { + f->flags |= F_ERR; + return EOF; + } + + f->rpos = f->rend = f->buf + f->buf_size; + + return (f->flags & F_EOF) ? EOF : 0; +} + +hidden void __toread_needs_stdio_exit() +{ + __stdio_exit_needed(); +} diff --git a/porting/linux/user/src/stdio/__towrite.c b/porting/linux/user/src/stdio/__towrite.c new file mode 100644 index 0000000000000000000000000000000000000000..382a3abd5337ea34a843b794d5ea29485c496a4a --- /dev/null +++ b/porting/linux/user/src/stdio/__towrite.c @@ -0,0 +1,28 @@ +#include "stdio_impl.h" + +int __towrite(FILE *f) +{ + f->mode |= f->mode-1; + if (f->flags & F_NOWR) { + f->flags |= F_ERR; + return EOF; + } + /* Clear read buffer (easier than summoning nasal demons) */ + f->rpos = f->rend = 0; + + /* Alloc file buffer if needed */ + if (__falloc_buf(f) < 0) { + f->flags |= F_ERR; + return EOF; + } + + /* Activate write through the buffer. */ + f->wpos = f->wbase = f->buf; + f->wend = f->buf + f->buf_size; + return 0; +} + +hidden void __towrite_needs_stdio_exit() +{ + __stdio_exit_needed(); +} diff --git a/porting/linux/user/src/stdio/fclose.c b/porting/linux/user/src/stdio/fclose.c new file mode 100644 index 0000000000000000000000000000000000000000..38e1273fd5058eeebdfdb7eae1940896f651aa30 --- /dev/null +++ b/porting/linux/user/src/stdio/fclose.c @@ -0,0 +1,35 @@ +#include "stdio_impl.h" +#include + +static void dummy(FILE *f) { } +weak_alias(dummy, __unlist_locked_file); + +int fclose(FILE *f) +{ + int r; + + FLOCK(f); + r = fflush(f); + r |= f->close(f); + FUNLOCK(f); + + /* Past this point, f is closed and any further explict access + * to it is undefined. However, it still exists as an entry in + * the open file list and possibly in the thread's locked files + * list, if it was closed while explicitly locked. Functions + * which process these lists must tolerate dead FILE objects + * (which necessarily have inactive buffer pointers) without + * producing any side effects. */ + + if (f->flags & F_PERM) return r; + + __unlist_locked_file(f); + + free(f->getln_buf); + /* release base instead of buf which may be modified by setvbuf + * or iniitalize by local variable */ + free(f->base); + __ofl_free(f); + + return r; +} diff --git a/porting/linux/user/src/stdio/fopen.c b/porting/linux/user/src/stdio/fopen.c new file mode 100644 index 0000000000000000000000000000000000000000..b0e66aefdb69276cbf77a6ef26375ce7b61d191d --- /dev/null +++ b/porting/linux/user/src/stdio/fopen.c @@ -0,0 +1,33 @@ +#include "stdio_impl.h" +#include +#include +#include + +FILE *fopen(const char *restrict filename, const char *restrict mode) +{ + FILE *f = NULL; + int fd = -1; + int file_flags = 0; + int mode_flags = 0; + + /* Compute the flags to pass to open() */ + mode_flags = __fmodeflags(mode, &file_flags); + if (mode_flags < 0) { + return NULL; + } + + fd = sys_open(filename, mode_flags, 0666); + if (fd < 0) return 0; + if (mode_flags & O_CLOEXEC) + __syscall(SYS_fcntl, fd, F_SETFD, FD_CLOEXEC); + + f = __fdopenx(fd, file_flags); + if (f) { + return f; + } + + __syscall(SYS_close, fd); + return 0; +} + +weak_alias(fopen, fopen64); diff --git a/porting/linux/user/src/stdio/fread.c b/porting/linux/user/src/stdio/fread.c index ac316cce3c8074cc63c61d6e3b76bb507953d08e..6819a1ebafbdc96725f2581a450df603e0b842e1 100644 --- a/porting/linux/user/src/stdio/fread.c +++ b/porting/linux/user/src/stdio/fread.c @@ -9,6 +9,7 @@ int __fill_buffer(FILE *f) if (r != 0) { return r; } + int k = f->readx(f, f->buf, f->buf_size); if (k <= 0) { f->flags |= (k == 0) ? F_EOF : F_ERR; @@ -32,6 +33,12 @@ size_t fread(void *restrict destv, size_t size, size_t nmemb, FILE *restrict f) FLOCK(f); + /* allocate file buffer if needed */ + if (__falloc_buf(f) < 0) { + f->flags |= F_ERR; + goto exit; + } + f->mode |= f->mode-1; while (l > 0) { @@ -47,7 +54,9 @@ size_t fread(void *restrict destv, size_t size, size_t nmemb, FILE *restrict f) if (l == 0) { goto exit; } - /* if user buffer is longer than file buffer, read directly */ + /* if user buffer is longer than file buffer, + * maybe buffer size is 0, non-buffer mode, + * read directly */ if (l > f->buf_size) { break; } diff --git a/porting/linux/user/src/stdio/freopen.c b/porting/linux/user/src/stdio/freopen.c index ace3429f3de02abd6eda4a3db6c46225efb8abee..0f4ea987aa1fe581a9a5ae4bb1e0ab434f189d63 100644 --- a/porting/linux/user/src/stdio/freopen.c +++ b/porting/linux/user/src/stdio/freopen.c @@ -12,7 +12,8 @@ FILE *freopen(const char *restrict filename, const char *restrict mode, FILE *restrict f) { - int fl = __fmodeflags(mode); + int file_flags = 0; + int fl = __fmodeflags(mode, &file_flags); FILE *f2; FLOCK(f); diff --git a/porting/linux/user/src/stdio/ofl.c b/porting/linux/user/src/stdio/ofl.c new file mode 100644 index 0000000000000000000000000000000000000000..adcd89897d746846b9ad19f4dbc599b1d66bd282 --- /dev/null +++ b/porting/linux/user/src/stdio/ofl.c @@ -0,0 +1,97 @@ +#include "stdio_impl.h" +#include "lock.h" +#include "fork_impl.h" +#include + +#define DEFAULT_ALLOC_FILE (8) + +static FILE *ofl_head = NULL; +static FILE *ofl_free = NULL; + +static volatile int ofl_lock[1]; +volatile int *const __stdio_ofl_lockptr = ofl_lock; + +FILE **__ofl_lock() +{ + LOCK(ofl_lock); + return &ofl_head; +} + +void __ofl_unlock() +{ + UNLOCK(ofl_lock); +} + +FILE *__ofl_alloc() +{ + unsigned char *fsb = NULL; + size_t cnt = 0; + FILE *f = NULL; + + LOCK(ofl_lock); + if (ofl_free) { + f = ofl_free; + ofl_free = ofl_free->next; + UNLOCK(ofl_lock); + + return f; + } + UNLOCK(ofl_lock); + + /* alloc new FILEs(8) */ + fsb = (unsigned char *)malloc(DEFAULT_ALLOC_FILE * sizeof(FILE)); + if (!fsb) { + return NULL; + } + + LOCK(ofl_lock); + ofl_free = (FILE*)fsb; + ofl_free->prev = NULL; + f = ofl_free; + + for (cnt = 1; cnt < DEFAULT_ALLOC_FILE; cnt++) { + FILE *tmp = (FILE*)(fsb + cnt * sizeof(FILE)); + tmp->next = NULL; + f->next = tmp; + tmp->prev = f; + f = f->next; + } + + /* reset and move to next free FILE */ + f = ofl_free; + ofl_free = ofl_free->next; + if (ofl_free) { + ofl_free->prev = NULL; + } + + UNLOCK(ofl_lock); + return f; +} + +void __ofl_free(FILE *f) +{ + LOCK(ofl_lock); + if (!f) { + return; + } + + /* remove from head list */ + if (f->prev) { + f->prev->next = f->next; + } + if (f->next) { + f->next->prev = f->prev; + } + if (f == ofl_head) { + ofl_head = f->next; + } + + /* push to free list */ + f->next = ofl_free; + if (ofl_free) { + ofl_free->prev = f; + } + ofl_free = f; + + UNLOCK(ofl_lock); +} diff --git a/porting/linux/user/src/stdio/setvbuf.c b/porting/linux/user/src/stdio/setvbuf.c new file mode 100644 index 0000000000000000000000000000000000000000..2fcd41db39d0467fd9aa4f1b7bc398069f58aedb --- /dev/null +++ b/porting/linux/user/src/stdio/setvbuf.c @@ -0,0 +1,31 @@ +#include "stdio_impl.h" + +/* The behavior of this function is undefined except when it is the first + * operation on the stream, so the presence or absence of locking is not + * observable in a program whose behavior is defined. Thus no locking is + * performed here. No allocation of buffers is performed, but a buffer + * provided by the caller is used as long as it is suitably sized. */ + +int setvbuf(FILE *restrict f, char *restrict buf, int type, size_t size) +{ + f->lbf = EOF; + + if (type == _IONBF) { + f->buf_size = 0; + f->flags |= F_NOBUF; + } else if (type == _IOLBF || type == _IOFBF) { + if (buf && size >= UNGET) { + f->buf = (void *)(buf + UNGET); + f->buf_size = size - UNGET; + } + if (type == _IOLBF && f->buf_size) + f->lbf = '\n'; + f->flags &= ~F_NOBUF; + } else { + return -1; + } + + f->flags |= F_SVB; + + return 0; +} diff --git a/porting/linux/user/src/stdio/stderr.c b/porting/linux/user/src/stdio/stderr.c new file mode 100644 index 0000000000000000000000000000000000000000..4dd73a5bbebc398400af894ccfb66a00d43e73cc --- /dev/null +++ b/porting/linux/user/src/stdio/stderr.c @@ -0,0 +1,18 @@ +#include "stdio_impl.h" + +#undef stderr + +static unsigned char buf[UNGET]; +hidden FILE __stderr_FILE = { + .buf = buf+UNGET, + .buf_size = 0, + .fd = 2, + .flags = F_PERM | F_NORD | F_NOBUF, + .lbf = -1, + .write = __stdio_write, + .seek = __stdio_seek, + .close = __stdio_close, + .lock = -1, +}; +FILE *const stderr = &__stderr_FILE; +FILE *volatile __stderr_used = &__stderr_FILE; diff --git a/porting/linux/user/src/stdio/vfprintf.c b/porting/linux/user/src/stdio/vfprintf.c index 4a01ca1e66db648f340cf8c452f6ce0b9f92528d..4cdc99c046a18b393a85f57eec4ee36c4d77bc7c 100644 --- a/porting/linux/user/src/stdio/vfprintf.c +++ b/porting/linux/user/src/stdio/vfprintf.c @@ -674,6 +674,13 @@ int vfprintf(FILE *restrict f, const char *restrict fmt, va_list ap) FLOCK(f); olderr = f->flags & F_ERR; if (f->mode < 1) f->flags &= ~F_ERR; + + /* allocate file buffer if need */ + if (__falloc_buf(f) < 0) { + f->flags |= F_ERR; + ret = -1; + } + if (!f->buf_size) { saved_buf = f->buf; f->buf = internal_buf;