From 37ad9597839c70a7ec79578e5072df9c189fc830 Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Mon, 30 Oct 2017 17:47:12 -0700 Subject: More missing _unlocked functions. Also simplify trivial one-liners like perror/puts/fputs, and clean up fread/fwrite slightly. Fix perror to match POSIX. Add basic perror and *_unlocked tests. Bug: N/A Test: ran tests Change-Id: I63f83c8e0c15c3c4096509d17421ac331b6fc23d --- libc/stdio/stdio.cpp | 260 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 245 insertions(+), 15 deletions(-) (limited to 'libc/stdio/stdio.cpp') diff --git a/libc/stdio/stdio.cpp b/libc/stdio/stdio.cpp index cf97a3f8d..c10bc00a4 100644 --- a/libc/stdio/stdio.cpp +++ b/libc/stdio/stdio.cpp @@ -91,7 +91,7 @@ FILE* stdin = &__sF[0]; FILE* stdout = &__sF[1]; FILE* stderr = &__sF[2]; -struct glue __sglue = { NULL, 3, __sF }; +struct glue __sglue = { nullptr, 3, __sF }; static struct glue* lastglue = &__sglue; class ScopedFileLock { @@ -116,7 +116,7 @@ static glue* moreglue(int n) { glue* g = reinterpret_cast(data); FILE* p = reinterpret_cast(ALIGN(data + sizeof(*g))); __sfileext* pext = reinterpret_cast<__sfileext*>(ALIGN(data + sizeof(*g)) + n * sizeof(FILE)); - g->next = NULL; + g->next = nullptr; g->niobs = n; g->iobs = p; while (--n >= 0) { @@ -144,7 +144,7 @@ FILE* __sfp(void) { struct glue *g; _THREAD_PRIVATE_MUTEX_LOCK(__sfp_mutex); - for (g = &__sglue; g != NULL; g = g->next) { + for (g = &__sglue; g != nullptr; g = g->next) { for (fp = g->iobs, n = g->niobs; --n >= 0; fp++) if (fp->_flags == 0) goto found; @@ -152,8 +152,7 @@ FILE* __sfp(void) { /* release lock while mallocing */ _THREAD_PRIVATE_MUTEX_UNLOCK(__sfp_mutex); - if ((g = moreglue(NDYNAMIC)) == NULL) - return (NULL); + if ((g = moreglue(NDYNAMIC)) == nullptr) return nullptr; _THREAD_PRIVATE_MUTEX_LOCK(__sfp_mutex); lastglue->next = g; lastglue = g; @@ -161,15 +160,15 @@ FILE* __sfp(void) { found: fp->_flags = 1; /* reserve this slot; caller sets real flags */ _THREAD_PRIVATE_MUTEX_UNLOCK(__sfp_mutex); - fp->_p = NULL; /* no current pointer */ + fp->_p = nullptr; /* no current pointer */ fp->_w = 0; /* nothing to read or write */ fp->_r = 0; - fp->_bf._base = NULL; /* no buffer */ + fp->_bf._base = nullptr; /* no buffer */ fp->_bf._size = 0; fp->_lbfsize = 0; /* not line buffered */ fp->_file = -1; /* no file */ - fp->_lb._base = NULL; /* no line buffer */ + fp->_lb._base = nullptr; /* no line buffer */ fp->_lb._size = 0; _FILEEXT_INIT(fp); @@ -288,8 +287,8 @@ FILE* freopen(const char* file, const char* mode, FILE* fp) { // Flush the stream; ANSI doesn't require this. if (fp->_flags & __SWR) __sflush(fp); - // If close is NULL, closing is a no-op, hence pointless. - isopen = fp->_close != NULL; + // If close is null, closing is a no-op, hence pointless. + isopen = (fp->_close != nullptr); if ((wantfd = fp->_file) < 0 && isopen) { (*fp->_close)(fp->_cookie); isopen = 0; @@ -316,8 +315,8 @@ FILE* freopen(const char* file, const char* mode, FILE* fp) { if (fp->_flags & __SMBF) free(fp->_bf._base); fp->_w = 0; fp->_r = 0; - fp->_p = NULL; - fp->_bf._base = NULL; + fp->_p = nullptr; + fp->_bf._base = nullptr; fp->_bf._size = 0; fp->_lbfsize = 0; if (HASUB(fp)) FREEUB(fp); @@ -374,7 +373,7 @@ int fclose(FILE* fp) { ScopedFileLock sfl(fp); WCIO_FREE(fp); int r = fp->_flags & __SWR ? __sflush(fp) : 0; - if (fp->_close != NULL && (*fp->_close)(fp->_cookie) < 0) { + if (fp->_close != nullptr && (*fp->_close)(fp->_cookie) < 0) { r = EOF; } if (fp->_flags & __SMBF) free(fp->_bf._base); @@ -438,6 +437,36 @@ int ferror(FILE* fp) { return ferror_unlocked(fp); } +int __sflush(FILE* fp) { + // Flushing a read-only file is a no-op. + if ((fp->_flags & __SWR) == 0) return 0; + + // Flushing a file without a buffer is a no-op. + unsigned char* p = fp->_bf._base; + if (p == nullptr) return 0; + + // Set these immediately to avoid problems with longjmp and to allow + // exchange buffering (via setvbuf) in user write function. + int n = fp->_p - p; + fp->_p = p; + fp->_w = (fp->_flags & (__SLBF|__SNBF)) ? 0 : fp->_bf._size; + + while (n > 0) { + int written = (*fp->_write)(fp->_cookie, reinterpret_cast(p), n); + if (written <= 0) { + fp->_flags |= __SERR; + return EOF; + } + n -= written, p += written; + } + return 0; +} + +int __sflush_locked(FILE* fp) { + ScopedFileLock sfl(fp); + return __sflush(fp); +} + int __sread(void* cookie, char* buf, int n) { FILE* fp = reinterpret_cast(cookie); return TEMP_FAILURE_RETRY(read(fp->_file, buf, n)); @@ -495,7 +524,7 @@ static off64_t __ftello64_unlocked(FILE* fp) { // smaller than that in the underlying object. result -= fp->_r; if (HASUB(fp)) result -= fp->_ur; - } else if (fp->_flags & __SWR && fp->_p != NULL) { + } else if (fp->_flags & __SWR && fp->_p != nullptr) { // Writing. Any buffered characters cause the // position to be greater than that in the // underlying object. @@ -527,7 +556,7 @@ int __fseeko64(FILE* fp, off64_t offset, int whence, int off_t_bits) { return -1; } - if (fp->_bf._base == NULL) __smakebuf(fp); + if (fp->_bf._base == nullptr) __smakebuf(fp); // Flush unwritten data and attempt the seek. if (__sflush(fp) || __seek_unlocked(fp, offset, whence) == -1) { @@ -670,11 +699,90 @@ int fgetc(FILE* fp) { return getc(fp); } +int fgetc_unlocked(FILE* fp) { + CHECK_FP(fp); + return getc_unlocked(fp); +} + +/* + * Read at most n-1 characters from the given file. + * Stop when a newline has been read, or the count runs out. + * Return first argument, or NULL if no characters were read. + * Do not return NULL if n == 1. + */ +char* fgets(char* buf, int n, FILE* fp) __overloadable { + CHECK_FP(fp); + ScopedFileLock sfl(fp); + return fgets_unlocked(buf, n, fp); +} + +char* fgets_unlocked(char* buf, int n, FILE* fp) { + if (n <= 0) { + errno = EINVAL; + return nullptr; + } + + _SET_ORIENTATION(fp, -1); + + char* s = buf; + n--; // Leave space for NUL. + while (n != 0) { + // If the buffer is empty, refill it. + if (fp->_r <= 0) { + if (__srefill(fp)) { + // EOF/error: stop with partial or no line. + if (s == buf) return nullptr; + break; + } + } + size_t len = fp->_r; + unsigned char* p = fp->_p; + + // Scan through at most n bytes of the current buffer, + // looking for '\n'. If found, copy up to and including + // newline, and stop. Otherwise, copy entire chunk and loop. + if (len > static_cast(n)) len = n; + unsigned char* t = static_cast(memchr(p, '\n', len)); + if (t != nullptr) { + len = ++t - p; + fp->_r -= len; + fp->_p = t; + memcpy(s, p, len); + s[len] = '\0'; + return buf; + } + fp->_r -= len; + fp->_p += len; + memcpy(s, p, len); + s += len; + n -= len; + } + *s = '\0'; + return buf; +} + int fputc(int c, FILE* fp) { CHECK_FP(fp); return putc(c, fp); } +int fputc_unlocked(int c, FILE* fp) { + CHECK_FP(fp); + return putc_unlocked(c, fp); +} + +int fputs(const char* s, FILE* fp) { + CHECK_FP(fp); + ScopedFileLock sfl(fp); + return fputs_unlocked(s, fp); +} + +int fputs_unlocked(const char* s, FILE* fp) { + CHECK_FP(fp); + size_t length = strlen(s); + return (fwrite_unlocked(s, 1, length, fp) == length) ? 0 : EOF; +} + int fscanf(FILE* fp, const char* fmt, ...) { CHECK_FP(fp); PRINTF_IMPL(vfscanf(fp, fmt, ap)); @@ -723,6 +831,11 @@ wint_t getwchar() { return fgetwc(stdin); } +void perror(const char* msg) { + if (msg == nullptr) msg = ""; + fprintf(stderr, "%s%s%s\n", msg, (*msg == '\0') ? "" : ": ", strerror(errno)); +} + int printf(const char* fmt, ...) { PRINTF_IMPL(vfprintf(stdout, fmt, ap)); } @@ -754,6 +867,13 @@ int putchar_unlocked(int c) { return putc_unlocked(c, stdout); } +int puts(const char* s) { + size_t length = strlen(s); + ScopedFileLock sfl(stdout); + return (fwrite_unlocked(s, 1, length, stdout) == length && + putc_unlocked('\n', stdout) != EOF) ? 0 : EOF; +} + wint_t putwc(wchar_t wc, FILE* fp) { CHECK_FP(fp); return fputwc(wc, fp); @@ -869,6 +989,116 @@ int wscanf(const wchar_t* fmt, ...) { PRINTF_IMPL(vfwscanf(stdin, fmt, ap)); } +static int fflush_all() { + return _fwalk(__sflush_locked); +} + +int fflush(FILE* fp) { + if (fp == nullptr) return fflush_all(); + ScopedFileLock sfl(fp); + return fflush_unlocked(fp); +} + +int fflush_unlocked(FILE* fp) { + if (fp == nullptr) return fflush_all(); + if ((fp->_flags & (__SWR | __SRW)) == 0) { + errno = EBADF; + return EOF; + } + return __sflush(fp); +} + +size_t fread(void* buf, size_t size, size_t count, FILE* fp) __overloadable { + CHECK_FP(fp); + ScopedFileLock sfl(fp); + return fread_unlocked(buf, size, count, fp); +} + +size_t fread_unlocked(void* buf, size_t size, size_t count, FILE* fp) { + CHECK_FP(fp); + + size_t desired_total; + if (__builtin_mul_overflow(size, count, &desired_total)) { + errno = EOVERFLOW; + fp->_flags |= __SERR; + return 0; + } + + size_t total = desired_total; + if (total == 0) return 0; + + _SET_ORIENTATION(fp, -1); + + // TODO: how can this ever happen?! + if (fp->_r < 0) fp->_r = 0; + + // Ensure _bf._size is valid. + if (fp->_bf._base == nullptr) __smakebuf(fp); + + char* dst = static_cast(buf); + + while (total > 0) { + // Copy data out of the buffer. + size_t buffered_bytes = MIN(static_cast(fp->_r), total); + memcpy(dst, fp->_p, buffered_bytes); + fp->_p += buffered_bytes; + fp->_r -= buffered_bytes; + dst += buffered_bytes; + total -= buffered_bytes; + + // Are we done? + if (total == 0) goto out; + + // Do we have so much more to read that we should avoid copying it through the buffer? + if (total > static_cast(fp->_bf._size)) break; + + // Less than a buffer to go, so refill the buffer and go around the loop again. + if (__srefill(fp)) goto out; + } + + // Read directly into the caller's buffer. + while (total > 0) { + ssize_t bytes_read = (*fp->_read)(fp->_cookie, dst, total); + if (bytes_read <= 0) { + fp->_flags |= (bytes_read == 0) ? __SEOF : __SERR; + break; + } + dst += bytes_read; + total -= bytes_read; + } + +out: + return ((desired_total - total) / size); +} + +size_t fwrite(const void* buf, size_t size, size_t count, FILE* fp) { + CHECK_FP(fp); + ScopedFileLock sfl(fp); + return fwrite_unlocked(buf, size, count, fp); +} + +size_t fwrite_unlocked(const void* buf, size_t size, size_t count, FILE* fp) { + CHECK_FP(fp); + + size_t n; + if (__builtin_mul_overflow(size, count, &n)) { + errno = EOVERFLOW; + fp->_flags |= __SERR; + return 0; + } + + if (n == 0) return 0; + + __siov iov = { .iov_base = const_cast(buf), .iov_len = n }; + __suio uio = { .uio_iov = &iov, .uio_iovcnt = 1, .uio_resid = n }; + + _SET_ORIENTATION(fp, -1); + + // The usual case is success (__sfvwrite returns 0); skip the divide if this happens, + // since divides are generally slow. + return (__sfvwrite(fp, &uio) == 0) ? count : ((n - uio.uio_resid) / size); +} + namespace { namespace phony { -- cgit v1.2.3