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/Android.bp b/libc/Android.bp
index 89d33b0..5d0a020 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -14,7 +14,6 @@
     "bionic/siginterrupt.c",
     "bionic/sigsetmask.c",
     "stdio/fmemopen.cpp",
-    "stdio/fread.cpp",
     "stdio/parsefloat.c",
     "stdio/refill.c",
     "stdio/stdio.cpp",
@@ -439,28 +438,22 @@
         "upstream-openbsd/lib/libc/net/ntohl.c",
         "upstream-openbsd/lib/libc/net/ntohs.c",
         "upstream-openbsd/lib/libc/net/res_random.c",
-        "upstream-openbsd/lib/libc/stdio/fflush.c",
         "upstream-openbsd/lib/libc/stdio/fgetln.c",
-        "upstream-openbsd/lib/libc/stdio/fgets.c",
         "upstream-openbsd/lib/libc/stdio/fgetwc.c",
         "upstream-openbsd/lib/libc/stdio/fgetws.c",
         "upstream-openbsd/lib/libc/stdio/flags.c",
         "upstream-openbsd/lib/libc/stdio/fpurge.c",
-        "upstream-openbsd/lib/libc/stdio/fputs.c",
         "upstream-openbsd/lib/libc/stdio/fputwc.c",
         "upstream-openbsd/lib/libc/stdio/fputws.c",
         "upstream-openbsd/lib/libc/stdio/fvwrite.c",
         "upstream-openbsd/lib/libc/stdio/fwalk.c",
         "upstream-openbsd/lib/libc/stdio/fwide.c",
-        "upstream-openbsd/lib/libc/stdio/fwrite.c",
         "upstream-openbsd/lib/libc/stdio/getdelim.c",
         "upstream-openbsd/lib/libc/stdio/gets.c",
         "upstream-openbsd/lib/libc/stdio/makebuf.c",
         "upstream-openbsd/lib/libc/stdio/mktemp.c",
         "upstream-openbsd/lib/libc/stdio/open_memstream.c",
         "upstream-openbsd/lib/libc/stdio/open_wmemstream.c",
-        "upstream-openbsd/lib/libc/stdio/perror.c",
-        "upstream-openbsd/lib/libc/stdio/puts.c",
         "upstream-openbsd/lib/libc/stdio/rget.c",
         "upstream-openbsd/lib/libc/stdio/setvbuf.c",
         "upstream-openbsd/lib/libc/stdio/tempnam.c",
diff --git a/libc/include/stdio.h b/libc/include/stdio.h
index 559e52a..2d45fc5 100644
--- a/libc/include/stdio.h
+++ b/libc/include/stdio.h
@@ -256,7 +256,20 @@
 int fileno_unlocked(FILE* __fp) __INTRODUCED_IN(24);
 #define fropen(cookie, fn) funopen(cookie, fn, 0, 0, 0)
 #define fwopen(cookie, fn) funopen(cookie, 0, fn, 0, 0)
-#endif /* __USE_BSD */
+#endif
+
+#if defined(__USE_BSD)
+int fflush_unlocked(FILE* __fp) __INTRODUCED_IN_FUTURE;
+int fgetc_unlocked(FILE* __fp) __INTRODUCED_IN_FUTURE;
+int fputc_unlocked(int __ch, FILE* __fp) __INTRODUCED_IN_FUTURE;
+size_t fread_unlocked(void* __buf, size_t __size, size_t __count, FILE* __fp) __INTRODUCED_IN_FUTURE;
+size_t fwrite_unlocked(const void* __buf, size_t __size, size_t __count, FILE* __fp) __INTRODUCED_IN_FUTURE;
+#endif
+
+#if defined(__USE_GNU)
+int fputs_unlocked(const char* __s, FILE* __fp) __INTRODUCED_IN_FUTURE;
+char* fgets_unlocked(char* __buf, int __size, FILE* __fp) __INTRODUCED_IN_FUTURE;
+#endif
 
 #if defined(__BIONIC_INCLUDE_FORTIFY_HEADERS)
 #include <bits/fortify/stdio.h>
diff --git a/libc/libc.arm.map b/libc/libc.arm.map
index 981cea6..abab364 100644
--- a/libc/libc.arm.map
+++ b/libc/libc.arm.map
@@ -1326,6 +1326,13 @@
     endnetent;
     endprotoent;
     fexecve;
+    fflush_unlocked;
+    fgetc_unlocked;
+    fgets_unlocked;
+    fputc_unlocked;
+    fputs_unlocked;
+    fread_unlocked;
+    fwrite_unlocked;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.arm64.map b/libc/libc.arm64.map
index 67fac4e..d464a0c 100644
--- a/libc/libc.arm64.map
+++ b/libc/libc.arm64.map
@@ -1246,6 +1246,13 @@
     endnetent;
     endprotoent;
     fexecve;
+    fflush_unlocked;
+    fgetc_unlocked;
+    fgets_unlocked;
+    fputc_unlocked;
+    fputs_unlocked;
+    fread_unlocked;
+    fwrite_unlocked;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index 443d18c..97965ac 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1351,6 +1351,13 @@
     endnetent;
     endprotoent;
     fexecve;
+    fflush_unlocked;
+    fgetc_unlocked;
+    fgets_unlocked;
+    fputc_unlocked;
+    fputs_unlocked;
+    fread_unlocked;
+    fwrite_unlocked;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.mips.map b/libc/libc.mips.map
index 0f5efff..930a1ca 100644
--- a/libc/libc.mips.map
+++ b/libc/libc.mips.map
@@ -1310,6 +1310,13 @@
     endnetent;
     endprotoent;
     fexecve;
+    fflush_unlocked;
+    fgetc_unlocked;
+    fgets_unlocked;
+    fputc_unlocked;
+    fputs_unlocked;
+    fread_unlocked;
+    fwrite_unlocked;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.mips64.map b/libc/libc.mips64.map
index 67fac4e..d464a0c 100644
--- a/libc/libc.mips64.map
+++ b/libc/libc.mips64.map
@@ -1246,6 +1246,13 @@
     endnetent;
     endprotoent;
     fexecve;
+    fflush_unlocked;
+    fgetc_unlocked;
+    fgets_unlocked;
+    fputc_unlocked;
+    fputs_unlocked;
+    fread_unlocked;
+    fwrite_unlocked;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.x86.map b/libc/libc.x86.map
index a802aa1..27b9743 100644
--- a/libc/libc.x86.map
+++ b/libc/libc.x86.map
@@ -1308,6 +1308,13 @@
     endnetent;
     endprotoent;
     fexecve;
+    fflush_unlocked;
+    fgetc_unlocked;
+    fgets_unlocked;
+    fputc_unlocked;
+    fputs_unlocked;
+    fread_unlocked;
+    fwrite_unlocked;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.x86_64.map b/libc/libc.x86_64.map
index 67fac4e..d464a0c 100644
--- a/libc/libc.x86_64.map
+++ b/libc/libc.x86_64.map
@@ -1246,6 +1246,13 @@
     endnetent;
     endprotoent;
     fexecve;
+    fflush_unlocked;
+    fgetc_unlocked;
+    fgets_unlocked;
+    fputc_unlocked;
+    fputs_unlocked;
+    fread_unlocked;
+    fwrite_unlocked;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/stdio/fread.cpp b/libc/stdio/fread.cpp
deleted file mode 100644
index 073f71a..0000000
--- a/libc/stdio/fread.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-/*	$OpenBSD: fread.c,v 1.12 2014/05/01 16:40:36 deraadt Exp $ */
-/*-
- * Copyright (c) 1990, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Chris Torek.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-#include <errno.h>
-#include <sys/param.h>
-#include "local.h"
-
-#define MUL_NO_OVERFLOW	(1UL << (sizeof(size_t) * 4))
-
-size_t
-fread(void *buf, size_t size, size_t count, FILE *fp) __overloadable
-{
-	CHECK_FP(fp);
-
-	/*
-	 * Extension:  Catch integer overflow.
-	 */
-	if ((size >= MUL_NO_OVERFLOW || count >= MUL_NO_OVERFLOW) &&
-	    size > 0 && SIZE_MAX / size < count) {
-		errno = EOVERFLOW;
-		fp->_flags |= __SERR;
-		return (0);
-	}
-
-	const size_t desired_total = count * size;
-	size_t total = desired_total;
-
-	/*
-	 * ANSI and SUSv2 require a return value of 0 if size or count are 0.
-	 */
-	if (total == 0) {
-		return (0);
-	}
-
-	FLOCKFILE(fp);
-	_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 == NULL) {
-		__smakebuf(fp);
-	}
-
-	char* dst = static_cast<char*>(buf);
-
-	while (total > 0) {
-		/*
-		 * Copy data out of the buffer.
-		 */
-		size_t buffered_bytes = MIN((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 > (size_t) fp->_bf._size) {
-			/*
-			 * Make sure that fseek doesn't think it can
-			 * reuse the buffer since we are going to read
-			 * directly from the file descriptor.
-			 */
-			fp->_flags |= __SMOD;
-			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:
-	FUNLOCKFILE(fp);
-	return ((desired_total - total) / size);
-}
diff --git a/libc/stdio/local.h b/libc/stdio/local.h
index c728eec..a86e924 100644
--- a/libc/stdio/local.h
+++ b/libc/stdio/local.h
@@ -145,11 +145,12 @@
 // #define __SOPT 0x0400 --- historical (do fseek() optimization).
 // #define __SNPT 0x0800 --- historical (do not do fseek() optimization).
 // #define __SOFF 0x1000 --- historical (set iff _offset is in fact correct).
-#define __SMOD 0x2000  // true => fgetln modified _p text.
+// #define __SMOD 0x2000 --- historical (set iff fgetln modified _p text).
 #define __SALC 0x4000  // Allocate string space dynamically.
 #define __SIGN 0x8000  // Ignore this file in _fwalk.
 
 // TODO: remove remaining references to these obsolete flags (see above).
+#define __SMOD 0
 #define __SNPT 0
 #define __SOPT 0
 
@@ -243,10 +244,18 @@
 #define __sclearerr(p) ((void)((p)->_flags &= ~(__SERR|__SEOF)))
 #define __sgetc(p) (--(p)->_r < 0 ? __srget(p) : (int)(*(p)->_p++))
 
-/* OpenBSD declares these in fvwrite.h but we want to ensure they're hidden. */
-struct __suio;
-extern int __sfvwrite(FILE *, struct __suio *);
-wint_t __fputwc_unlock(wchar_t wc, FILE *fp);
+/* OpenBSD declares these in fvwrite.h, but we share them with C++ parts of the implementation. */
+struct __siov {
+  void* iov_base;
+  size_t iov_len;
+};
+struct __suio {
+  struct __siov* uio_iov;
+  int uio_iovcnt;
+  size_t uio_resid;
+};
+int __sfvwrite(FILE*, struct __suio*);
+wint_t __fputwc_unlock(wchar_t wc, FILE* fp);
 
 /* Remove the if (!__sdidinit) __sinit() idiom from untouched upstream stdio code. */
 extern void __sinit(void); // Not actually implemented.
diff --git a/libc/stdio/refill.c b/libc/stdio/refill.c
index a7c4bff..1df4191 100644
--- a/libc/stdio/refill.c
+++ b/libc/stdio/refill.c
@@ -111,7 +111,6 @@
 	}
 	fp->_p = fp->_bf._base;
 	fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);
-	fp->_flags &= ~__SMOD;	/* buffer contents are again pristine */
 	if (fp->_r <= 0) {
 		if (fp->_r == 0)
 			fp->_flags |= __SEOF;
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 {
diff --git a/libc/upstream-openbsd/lib/libc/stdio/fflush.c b/libc/upstream-openbsd/lib/libc/stdio/fflush.c
deleted file mode 100644
index fd1a4b3..0000000
--- a/libc/upstream-openbsd/lib/libc/stdio/fflush.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/*	$OpenBSD: fflush.c,v 1.9 2015/08/31 02:53:57 guenther Exp $ */
-/*-
- * Copyright (c) 1990, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Chris Torek.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <errno.h>
-#include <stdio.h>
-#include "local.h"
-
-/* Flush a single file, or (if fp is NULL) all files.  */
-int
-fflush(FILE *fp)
-{
-	int	r;
-
-	if (fp == NULL)
-		return (_fwalk(__sflush_locked));
-	FLOCKFILE(fp);
-	if ((fp->_flags & (__SWR | __SRW)) == 0) {
-		errno = EBADF;
-		r = EOF;
-	} else
-		r = __sflush(fp);
-	FUNLOCKFILE(fp);
-	return (r);
-}
-DEF_STRONG(fflush);
-
-int
-__sflush(FILE *fp)
-{
-	unsigned char *p;
-	int n, t;
-
-	t = fp->_flags;
-	if ((t & __SWR) == 0)
-		return (0);
-
-	if ((p = fp->_bf._base) == NULL)
-		return (0);
-
-	n = fp->_p - p;		/* write this much */
-
-	/*
-	 * Set these immediately to avoid problems with longjmp and to allow
-	 * exchange buffering (via setvbuf) in user write function.
-	 */
-	fp->_p = p;
-	fp->_w = t & (__SLBF|__SNBF) ? 0 : fp->_bf._size;
-
-	for (; n > 0; n -= t, p += t) {
-		t = (*fp->_write)(fp->_cookie, (char *)p, n);
-		if (t <= 0) {
-			fp->_flags |= __SERR;
-			return (EOF);
-		}
-	}
-	return (0);
-}
-
-int
-__sflush_locked(FILE *fp)
-{
-	int	r;
-
-	FLOCKFILE(fp);
-	r = __sflush(fp);
-	FUNLOCKFILE(fp);
-	return (r);
-}
diff --git a/libc/upstream-openbsd/lib/libc/stdio/fgets.c b/libc/upstream-openbsd/lib/libc/stdio/fgets.c
deleted file mode 100644
index 3cea8f7..0000000
--- a/libc/upstream-openbsd/lib/libc/stdio/fgets.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*	$OpenBSD: fgets.c,v 1.16 2016/09/21 04:38:56 guenther Exp $ */
-/*-
- * Copyright (c) 1990, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Chris Torek.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include "local.h"
-
-/*
- * 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
-{
-	size_t len;
-	char *s;
-	unsigned char *p, *t;
-
-	if (n <= 0) {		/* sanity check */
-		errno = EINVAL;
-		return (NULL);
-	}
-
-	FLOCKFILE(fp);
-	_SET_ORIENTATION(fp, -1);
-	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) {
-					FUNLOCKFILE(fp);
-					return (NULL);
-				}
-				break;
-			}
-		}
-		len = fp->_r;
-		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 > n)
-			len = n;
-		t = memchr(p, '\n', len);
-		if (t != NULL) {
-			len = ++t - p;
-			fp->_r -= len;
-			fp->_p = t;
-			(void)memcpy(s, p, len);
-			s[len] = '\0';
-			FUNLOCKFILE(fp);
-			return (buf);
-		}
-		fp->_r -= len;
-		fp->_p += len;
-		(void)memcpy(s, p, len);
-		s += len;
-		n -= len;
-	}
-	*s = '\0';
-	FUNLOCKFILE(fp);
-	return (buf);
-}
-DEF_STRONG(fgets);
diff --git a/libc/upstream-openbsd/lib/libc/stdio/fputs.c b/libc/upstream-openbsd/lib/libc/stdio/fputs.c
deleted file mode 100644
index 05ead5c..0000000
--- a/libc/upstream-openbsd/lib/libc/stdio/fputs.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*	$OpenBSD: fputs.c,v 1.11 2015/08/31 02:53:57 guenther Exp $ */
-/*-
- * Copyright (c) 1990, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Chris Torek.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include "local.h"
-#include "fvwrite.h"
-
-/*
- * Write the given string to the given file.
- */
-int
-fputs(const char *s, FILE *fp)
-{
-	struct __suio uio;
-	struct __siov iov;
-	int ret;
-
-	iov.iov_base = (void *)s;
-	iov.iov_len = uio.uio_resid = strlen(s);
-	uio.uio_iov = &iov;
-	uio.uio_iovcnt = 1;
-	FLOCKFILE(fp);
-	_SET_ORIENTATION(fp, -1);
-	ret = __sfvwrite(fp, &uio);
-	FUNLOCKFILE(fp);
-	return (ret);
-}
-DEF_STRONG(fputs);
diff --git a/libc/upstream-openbsd/lib/libc/stdio/fvwrite.h b/libc/upstream-openbsd/lib/libc/stdio/fvwrite.h
index f04565b..d2257cc 100644
--- a/libc/upstream-openbsd/lib/libc/stdio/fvwrite.h
+++ b/libc/upstream-openbsd/lib/libc/stdio/fvwrite.h
@@ -32,20 +32,4 @@
  * SUCH DAMAGE.
  */
 
-/*
- * I/O descriptors for __sfvwrite().
- */
-struct __siov {
-	void	*iov_base;
-	size_t	iov_len;
-};
-struct __suio {
-	struct	__siov *uio_iov;
-	int	uio_iovcnt;
-	int	uio_resid;
-};
-
-__BEGIN_HIDDEN_DECLS
-extern int __sfvwrite(FILE *, struct __suio *);
-wint_t __fputwc_unlock(wchar_t wc, FILE *fp);
-__END_HIDDEN_DECLS
+/* Moved to "local.h". */
diff --git a/libc/upstream-openbsd/lib/libc/stdio/fwrite.c b/libc/upstream-openbsd/lib/libc/stdio/fwrite.c
deleted file mode 100644
index f829398..0000000
--- a/libc/upstream-openbsd/lib/libc/stdio/fwrite.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*	$OpenBSD: fwrite.c,v 1.12 2015/08/31 02:53:57 guenther Exp $ */
-/*-
- * Copyright (c) 1990, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Chris Torek.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <errno.h>
-#include "local.h"
-#include "fvwrite.h"
-
-#define MUL_NO_OVERFLOW	(1UL << (sizeof(size_t) * 4))
-
-/*
- * Write `count' objects (each size `size') from memory to the given file.
- * Return the number of whole objects written.
- */
-size_t
-fwrite(const void *buf, size_t size, size_t count, FILE *fp) __overloadable
-{
-	size_t n;
-	struct __suio uio;
-	struct __siov iov;
-	int ret;
-
-	/*
-	 * Extension:  Catch integer overflow
-	 */
-	if ((size >= MUL_NO_OVERFLOW || count >= MUL_NO_OVERFLOW) &&
-	    size > 0 && SIZE_MAX / size < count) {
-		errno = EOVERFLOW;
-		fp->_flags |= __SERR;
-		return (0);
-	}
-
-	/*
-	 * ANSI and SUSv2 require a return value of 0 if size or count are 0.
-	 */
-	if ((n = count * size) == 0)
-		return (0);
-
-	iov.iov_base = (void *)buf;
-	uio.uio_resid = iov.iov_len = n;
-	uio.uio_iov = &iov;
-	uio.uio_iovcnt = 1;
-
-	/*
-	 * The usual case is success (__sfvwrite returns 0);
-	 * skip the divide if this happens, since divides are
-	 * generally slow and since this occurs whenever size==0.
-	 */
-	FLOCKFILE(fp);
-	_SET_ORIENTATION(fp, -1);
-	ret = __sfvwrite(fp, &uio);
-	FUNLOCKFILE(fp);
-	if (ret == 0)
-		return (count);
-	return ((n - uio.uio_resid) / size);
-}
-DEF_STRONG(fwrite);
diff --git a/libc/upstream-openbsd/lib/libc/stdio/perror.c b/libc/upstream-openbsd/lib/libc/stdio/perror.c
deleted file mode 100644
index fdd6120..0000000
--- a/libc/upstream-openbsd/lib/libc/stdio/perror.c
+++ /dev/null
@@ -1,63 +0,0 @@
-/*	$OpenBSD: perror.c,v 1.9 2015/08/31 02:53:57 guenther Exp $ */
-/*
- * Copyright (c) 1988, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <unistd.h>
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include <limits.h>
-
-void
-perror(const char *s)
-{
-	struct iovec *v;
-	struct iovec iov[4];
-	char buf[NL_TEXTMAX];
-
-	v = iov;
-	if (s && *s) {
-		v->iov_base = (char *)s;
-		v->iov_len = strlen(s);
-		v++;
-		v->iov_base = ": ";
-		v->iov_len = 2;
-		v++;
-	}
-	(void)strerror_r(errno, buf, sizeof(buf));
-	v->iov_base = buf;
-	v->iov_len = strlen(v->iov_base);
-	v++;
-	v->iov_base = "\n";
-	v->iov_len = 1;
-	(void)writev(STDERR_FILENO, iov, (v - iov) + 1);
-}
-DEF_STRONG(perror);
diff --git a/libc/upstream-openbsd/lib/libc/stdio/puts.c b/libc/upstream-openbsd/lib/libc/stdio/puts.c
deleted file mode 100644
index 57d4b78..0000000
--- a/libc/upstream-openbsd/lib/libc/stdio/puts.c
+++ /dev/null
@@ -1,63 +0,0 @@
-/*	$OpenBSD: puts.c,v 1.12 2015/08/31 02:53:57 guenther Exp $ */
-/*-
- * Copyright (c) 1990, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Chris Torek.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include "local.h"
-#include "fvwrite.h"
-
-/*
- * Write the given string to stdout, appending a newline.
- */
-int
-puts(const char *s)
-{
-	size_t c = strlen(s);
-	struct __suio uio;
-	struct __siov iov[2];
-	int ret;
-
-	iov[0].iov_base = (void *)s;
-	iov[0].iov_len = c;
-	iov[1].iov_base = "\n";
-	iov[1].iov_len = 1;
-	uio.uio_resid = c + 1;
-	uio.uio_iov = &iov[0];
-	uio.uio_iovcnt = 2;
-	FLOCKFILE(stdout);
-	_SET_ORIENTATION(stdout, -1);
-	ret = __sfvwrite(stdout, &uio);
-	FUNLOCKFILE(stdout);
-	return (ret ? EOF : '\n');
-}
-DEF_STRONG(puts);