summaryrefslogtreecommitdiff
path: root/libc/stdio/stdio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libc/stdio/stdio.cpp')
-rw-r--r--libc/stdio/stdio.cpp170
1 files changed, 134 insertions, 36 deletions
diff --git a/libc/stdio/stdio.cpp b/libc/stdio/stdio.cpp
index e066e5b40..1f08ea13d 100644
--- a/libc/stdio/stdio.cpp
+++ b/libc/stdio/stdio.cpp
@@ -41,7 +41,9 @@
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
+#include <sys/socket.h>
#include <sys/stat.h>
+#include <sys/wait.h>
#include <unistd.h>
#include <async_safe/log.h>
@@ -64,33 +66,42 @@
va_end(ap); \
return result;
-#define std(flags, file) \
- {0,0,0,flags,file,{0,0},0,__sF+file,__sclose,__sread,nullptr,__swrite, \
- {(unsigned char *)(__sFext+file), 0},nullptr,0,{0},{0},{0,0},0,0}
-
-_THREAD_PRIVATE_MUTEX(__sfp_mutex);
-
-#define SBUF_INIT {}
-#define WCHAR_IO_DATA_INIT {}
+#define MAKE_STD_STREAM(flags, fd) \
+ { \
+ ._flags = flags, ._file = fd, ._cookie = __sF + fd, ._close = __sclose, \
+ ._read = __sread, ._write = __swrite, ._ext = { \
+ ._base = reinterpret_cast<uint8_t*>(__sFext + fd) \
+ } \
+ }
static struct __sfileext __sFext[3] = {
- { SBUF_INIT, WCHAR_IO_DATA_INIT, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, false, __sseek64 },
- { SBUF_INIT, WCHAR_IO_DATA_INIT, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, false, __sseek64 },
- { SBUF_INIT, WCHAR_IO_DATA_INIT, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, false, __sseek64 },
+ {._lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
+ ._caller_handles_locking = false,
+ ._seek64 = __sseek64,
+ ._popen_pid = 0},
+ {._lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
+ ._caller_handles_locking = false,
+ ._seek64 = __sseek64,
+ ._popen_pid = 0},
+ {._lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
+ ._caller_handles_locking = false,
+ ._seek64 = __sseek64,
+ ._popen_pid = 0},
};
// __sF is exported for backwards compatibility. Until M, we didn't have symbols
// for stdin/stdout/stderr; they were macros accessing __sF.
FILE __sF[3] = {
- std(__SRD, STDIN_FILENO),
- std(__SWR, STDOUT_FILENO),
- std(__SWR|__SNBF, STDERR_FILENO),
+ MAKE_STD_STREAM(__SRD, STDIN_FILENO),
+ MAKE_STD_STREAM(__SWR, STDOUT_FILENO),
+ MAKE_STD_STREAM(__SWR|__SNBF, STDERR_FILENO),
};
FILE* stdin = &__sF[0];
FILE* stdout = &__sF[1];
FILE* stderr = &__sF[2];
+static pthread_mutex_t __stdio_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
struct glue __sglue = { nullptr, 3, __sF };
static struct glue* lastglue = &__sglue;
@@ -108,8 +119,6 @@ class ScopedFileLock {
};
static glue* moreglue(int n) {
- static FILE empty;
-
char* data = new char[sizeof(glue) + ALIGNBYTES + n * sizeof(FILE) + n * sizeof(__sfileext)];
if (data == nullptr) return nullptr;
@@ -120,7 +129,7 @@ static glue* moreglue(int n) {
g->niobs = n;
g->iobs = p;
while (--n >= 0) {
- *p = empty;
+ *p = {};
_FILEEXT_SETUP(p, pext);
p++;
pext++;
@@ -143,7 +152,7 @@ FILE* __sfp(void) {
int n;
struct glue *g;
- _THREAD_PRIVATE_MUTEX_LOCK(__sfp_mutex);
+ pthread_mutex_lock(&__stdio_mutex);
for (g = &__sglue; g != nullptr; g = g->next) {
for (fp = g->iobs, n = g->niobs; --n >= 0; fp++)
if (fp->_flags == 0)
@@ -151,15 +160,15 @@ FILE* __sfp(void) {
}
/* release lock while mallocing */
- _THREAD_PRIVATE_MUTEX_UNLOCK(__sfp_mutex);
+ pthread_mutex_unlock(&__stdio_mutex);
if ((g = moreglue(NDYNAMIC)) == nullptr) return nullptr;
- _THREAD_PRIVATE_MUTEX_LOCK(__sfp_mutex);
+ pthread_mutex_lock(&__stdio_mutex);
lastglue->next = g;
lastglue = g;
fp = g->iobs;
found:
fp->_flags = 1; /* reserve this slot; caller sets real flags */
- _THREAD_PRIVATE_MUTEX_UNLOCK(__sfp_mutex);
+ pthread_mutex_unlock(&__stdio_mutex);
fp->_p = nullptr; /* no current pointer */
fp->_w = 0; /* nothing to read or write */
fp->_r = 0;
@@ -183,9 +192,20 @@ found:
return fp;
}
-extern "C" __LIBC_HIDDEN__ void __libc_stdio_cleanup(void) {
- // Equivalent to fflush(nullptr), but without all the locking since we're shutting down anyway.
- _fwalk(__sflush);
+int _fwalk(int (*callback)(FILE*)) {
+ pthread_mutex_lock(&__stdio_mutex);
+ int result = 0;
+ for (glue* g = &__sglue; g != nullptr; g = g->next) {
+ FILE* fp = g->iobs;
+ for (int n = g->niobs; --n >= 0; ++fp) {
+ ScopedFileLock sfl(fp);
+ if (fp->_flags != 0 && (fp->_flags & __SIGN) == 0) {
+ result |= (*callback)(fp);
+ }
+ }
+ }
+ pthread_mutex_unlock(&__stdio_mutex);
+ return result;
}
static FILE* __fopen(int fd, int flags) {
@@ -383,6 +403,16 @@ int fclose(FILE* fp) {
if (HASUB(fp)) FREEUB(fp);
free_fgetln_buffer(fp);
+ // If we were created by popen(3), wait for the child.
+ pid_t pid = _EXT(fp)->_popen_pid;
+ if (pid > 0) {
+ int status;
+ if (TEMP_FAILURE_RETRY(wait4(pid, &status, 0, nullptr)) != -1) {
+ r = status;
+ }
+ }
+ _EXT(fp)->_popen_pid = 0;
+
// Poison this FILE so accesses after fclose will be obvious.
fp->_file = -1;
fp->_r = fp->_w = 0;
@@ -391,6 +421,7 @@ int fclose(FILE* fp) {
fp->_flags = 0;
return r;
}
+__strong_alias(pclose, fclose);
int fileno_unlocked(FILE* fp) {
CHECK_FP(fp);
@@ -465,11 +496,6 @@ int __sflush(FILE* fp) {
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));
@@ -707,18 +733,16 @@ int fgetc_unlocked(FILE* 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) {
CHECK_FP(fp);
ScopedFileLock sfl(fp);
return fgets_unlocked(buf, n, fp);
}
+// Reads at most n-1 characters from the given file.
+// Stops when a newline has been read, or the count runs out.
+// Returns first argument, or nullptr if no characters were read.
+// Does not return nullptr if n == 1.
char* fgets_unlocked(char* buf, int n, FILE* fp) {
if (n <= 0) {
errno = EINVAL;
@@ -1013,7 +1037,7 @@ int wscanf(const wchar_t* fmt, ...) {
}
static int fflush_all() {
- return _fwalk(__sflush_locked);
+ return _fwalk(__sflush);
}
int fflush(FILE* fp) {
@@ -1122,6 +1146,80 @@ size_t fwrite_unlocked(const void* buf, size_t size, size_t count, FILE* fp) {
return (__sfvwrite(fp, &uio) == 0) ? count : ((n - uio.uio_resid) / size);
}
+static int __close_if_popened(FILE* fp) {
+ if (_EXT(fp)->_popen_pid > 0) close(fileno(fp));
+ return 0;
+}
+
+static FILE* __popen_fail(int fds[2]) {
+ ErrnoRestorer errno_restorer;
+ close(fds[0]);
+ close(fds[1]);
+ return nullptr;
+}
+
+FILE* popen(const char* cmd, const char* mode) {
+ bool close_on_exec = (strchr(mode, 'e') != nullptr);
+
+ // Was the request for a socketpair or just a pipe?
+ int fds[2];
+ bool bidirectional = false;
+ if (strchr(mode, '+') != nullptr) {
+ if (socketpair(AF_LOCAL, SOCK_CLOEXEC | SOCK_STREAM, 0, fds) == -1) return nullptr;
+ bidirectional = true;
+ mode = "r+";
+ } else {
+ if (pipe2(fds, O_CLOEXEC) == -1) return nullptr;
+ mode = strrchr(mode, 'r') ? "r" : "w";
+ }
+
+ // If the parent wants to read, the child's fd needs to be stdout.
+ int parent, child, desired_child_fd;
+ if (*mode == 'r') {
+ parent = 0;
+ child = 1;
+ desired_child_fd = STDOUT_FILENO;
+ } else {
+ parent = 1;
+ child = 0;
+ desired_child_fd = STDIN_FILENO;
+ }
+
+ // Ensure that the child fd isn't the desired child fd.
+ if (fds[child] == desired_child_fd) {
+ int new_fd = fcntl(fds[child], F_DUPFD_CLOEXEC, 0);
+ if (new_fd == -1) return __popen_fail(fds);
+ close(fds[child]);
+ fds[child] = new_fd;
+ }
+
+ pid_t pid = vfork();
+ if (pid == -1) return __popen_fail(fds);
+
+ if (pid == 0) {
+ close(fds[parent]);
+ // POSIX says "The popen() function shall ensure that any streams from previous popen() calls
+ // that remain open in the parent process are closed in the new child process."
+ _fwalk(__close_if_popened);
+ // dup2 so that the child fd isn't closed on exec.
+ if (dup2(fds[child], desired_child_fd) == -1) _exit(127);
+ close(fds[child]);
+ if (bidirectional) dup2(STDOUT_FILENO, STDIN_FILENO);
+ execl(_PATH_BSHELL, "sh", "-c", cmd, nullptr);
+ _exit(127);
+ }
+
+ FILE* fp = fdopen(fds[parent], mode);
+ if (fp == nullptr) return __popen_fail(fds);
+
+ // The caller didn't ask for their pipe to be O_CLOEXEC, so flip it back now the child has forked.
+ if (!close_on_exec) fcntl(fds[parent], F_SETFD, 0);
+ close(fds[child]);
+
+ _EXT(fp)->_popen_pid = pid;
+ return fp;
+}
+
namespace {
namespace phony {