More missing _unlocked <stdio.h> 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
diff --git a/libc/stdio/stdio.cpp b/libc/stdio/stdio.cpp
index cf97a3f..c10bc00 100644
--- a/libc/stdio/stdio.cpp
+++ b/libc/stdio/stdio.cpp
@@ -91,7 +91,7 @@
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 @@
glue* g = reinterpret_cast<glue*>(data);
FILE* p = reinterpret_cast<FILE*>(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 @@
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 @@
/* 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 @@
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 @@
// 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 @@
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 @@
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 @@
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<char*>(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<FILE*>(cookie);
return TEMP_FAILURE_RETRY(read(fp->_file, buf, n));
@@ -495,7 +524,7 @@
// 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 @@
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 @@
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<size_t>(n)) len = n;
+ unsigned char* t = static_cast<unsigned char*>(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 @@
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 @@
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 @@
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<char*>(buf);
+
+ while (total > 0) {
+ // Copy data out of the buffer.
+ size_t buffered_bytes = MIN(static_cast<size_t>(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<size_t>(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<void*>(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 {