[libc-commits] [libc] [llvm] [libc] implement popen/pclose (PR #232846)
Michael Jones via libc-commits
libc-commits at lists.llvm.org
Fri Oct 16 09:59:57 PDT 2026
https://github.com/michaelrj-google created https://github.com/llvm/llvm-project/pull/232846
This is a deceptively complex set of functions. They need to have
their own FILE subclass to track the PID of the child process and a list
of other open files specifically for popen. I also ended up refactoring
`system` since it shares the fork-and-shell mechanism. The POSIX
definition is here: https://pubs.opengroup.org/onlinepubs/9799919799/functions/popen.html
Assisted-by: Automated tooling, human reviewed.
>From abe85752215433ab1e26c59986b7ab5076b4719e Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Fri, 16 Oct 2026 16:56:49 +0000
Subject: [PATCH] [libc] implement popen/pclose
This is a deceptively complex set of functions. They need to have
their own FILE subclass to track the PID of the child process and a list
of other open files specifically for popen. I also ended up refactoring
`system` since it shares the fork-and-shell mechanism. The POSIX
definition is here: https://pubs.opengroup.org/onlinepubs/9799919799/functions/popen.html
Assisted-by: Automated tooling, human reviewed.
---
libc/config/linux/aarch64/entrypoints.txt | 2 +
libc/config/linux/riscv/entrypoints.txt | 2 +
libc/config/linux/x86_64/entrypoints.txt | 2 +
libc/include/stdio.yaml | 13 ++
libc/src/__support/File/file_mode.h | 4 +
libc/src/__support/File/linux/CMakeLists.txt | 17 ++
libc/src/__support/File/linux/file.cpp | 69 ++++----
libc/src/__support/File/linux/file.h | 60 ++++++-
libc/src/__support/File/linux/popen_file.cpp | 58 +++++++
libc/src/__support/File/linux/popen_file.h | 66 ++++++++
.../src/__support/OSUtil/linux/CMakeLists.txt | 19 +++
libc/src/__support/OSUtil/linux/shell_fork.h | 79 +++++++++
.../linux/syscall_wrappers/CMakeLists.txt | 13 ++
.../OSUtil/linux/syscall_wrappers/pipe2.h | 37 ++++
libc/src/stdio/CMakeLists.txt | 14 ++
libc/src/stdio/linux/CMakeLists.txt | 43 +++++
libc/src/stdio/linux/pclose.cpp | 53 ++++++
libc/src/stdio/linux/popen.cpp | 131 +++++++++++++++
libc/src/stdio/pclose.h | 26 +++
libc/src/stdio/popen.h | 26 +++
libc/src/stdlib/linux/CMakeLists.txt | 8 +-
libc/src/stdlib/linux/system.cpp | 62 ++-----
libc/src/unistd/linux/CMakeLists.txt | 7 +-
libc/src/unistd/linux/pipe2.cpp | 20 ++-
libc/test/src/stdio/CMakeLists.txt | 23 +++
libc/test/src/stdio/popen_pclose_test.cpp | 158 ++++++++++++++++++
.../llvm-project-overlay/libc/BUILD.bazel | 1 +
27 files changed, 906 insertions(+), 107 deletions(-)
create mode 100644 libc/src/__support/File/linux/popen_file.cpp
create mode 100644 libc/src/__support/File/linux/popen_file.h
create mode 100644 libc/src/__support/OSUtil/linux/shell_fork.h
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/pipe2.h
create mode 100644 libc/src/stdio/linux/pclose.cpp
create mode 100644 libc/src/stdio/linux/popen.cpp
create mode 100644 libc/src/stdio/pclose.h
create mode 100644 libc/src/stdio/popen.h
create mode 100644 libc/test/src/stdio/popen_pclose_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 13130efdf79bb3..3186fbe0f14a8e 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1332,7 +1332,9 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.getc_unlocked
libc.src.stdio.getchar
libc.src.stdio.getchar_unlocked
+ libc.src.stdio.pclose
libc.src.stdio.perror
+ libc.src.stdio.popen
libc.src.stdio.putc
libc.src.stdio.putchar
libc.src.stdio.puts
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index e53f61dd43e90a..f9085437e0b2a7 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1513,7 +1513,9 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.getc_unlocked
libc.src.stdio.getchar
libc.src.stdio.getchar_unlocked
+ libc.src.stdio.pclose
libc.src.stdio.perror
+ libc.src.stdio.popen
libc.src.stdio.putc
libc.src.stdio.putchar
libc.src.stdio.puts
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index dcfa2ef81338e7..325e4e73c0fb03 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1539,7 +1539,9 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.getc_unlocked
libc.src.stdio.getchar
libc.src.stdio.getchar_unlocked
+ libc.src.stdio.pclose
libc.src.stdio.perror
+ libc.src.stdio.popen
libc.src.stdio.putc
libc.src.stdio.putchar
libc.src.stdio.puts
diff --git a/libc/include/stdio.yaml b/libc/include/stdio.yaml
index c910c53d5f3a79..251840d8670a63 100644
--- a/libc/include/stdio.yaml
+++ b/libc/include/stdio.yaml
@@ -329,12 +329,25 @@ functions:
- posix
return_type: int
arguments: []
+ - name: pclose
+ standards:
+ - posix
+ return_type: int
+ arguments:
+ - type: FILE *
- name: perror
standards:
- stdc
return_type: void
arguments:
- type: const char *
+ - name: popen
+ standards:
+ - posix
+ return_type: FILE *
+ arguments:
+ - type: const char *
+ - type: const char *
- name: printf
standards:
- stdc
diff --git a/libc/src/__support/File/file_mode.h b/libc/src/__support/File/file_mode.h
index 8049516da261f0..1c4c8b7b7e3986 100644
--- a/libc/src/__support/File/file_mode.h
+++ b/libc/src/__support/File/file_mode.h
@@ -86,6 +86,10 @@ class FileMode {
constexpr bool is_valid() const { return file_mode_ != 0; }
+ constexpr bool operator==(const FileMode &other) const {
+ return file_mode_ == other.file_mode_;
+ }
+
constexpr bool is_append() const {
return (file_mode_ & static_cast<Mode>(OpenMode::APPEND)) != 0;
}
diff --git a/libc/src/__support/File/linux/CMakeLists.txt b/libc/src/__support/File/linux/CMakeLists.txt
index 05efc0f6f61395..57d3d5c025e7b7 100644
--- a/libc/src/__support/File/linux/CMakeLists.txt
+++ b/libc/src/__support/File/linux/CMakeLists.txt
@@ -12,7 +12,9 @@ add_object_library(
libc.hdr.sys_stat_macros
libc.hdr.types.FILE
libc.hdr.types.off_t
+ libc.src.__support.alloc_checker
libc.src.__support.CPP.new
+ libc.src.__support.CPP.type_traits
libc.src.__support.OSUtil.linux.syscall_wrappers.close
libc.src.__support.OSUtil.linux.syscall_wrappers.dup3
libc.src.__support.OSUtil.linux.syscall_wrappers.fcntl
@@ -25,6 +27,21 @@ add_object_library(
libc.src.errno.errno
)
+add_object_library(
+ popen_file
+ SRCS
+ popen_file.cpp
+ HDRS
+ popen_file.h
+ DEPENDS
+ .file
+ libc.hdr.types.pid_t
+ libc.src.__support.CPP.new
+ libc.src.__support.File.file
+ libc.src.__support.macros.config
+ libc.src.__support.threads.mutex
+)
+
add_object_library(
linux_dir
SRCS
diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index 1d360c15bfa834..0bc3ba37a4a91d 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -1,10 +1,15 @@
-//===--- Implementation of the Linux specialization of File ---------------===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the Linux specialization of File.
+///
+//===----------------------------------------------------------------------===//
#include "file.h"
#include "file_flags.h"
@@ -21,7 +26,6 @@
#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/write.h"
-#include "src/__support/alloc-checker.h"
#include "src/__support/libc_errno.h" // For error macros
#include "src/__support/macros/config.h"
@@ -50,20 +54,22 @@ ErrorOr<off_t> linux_file_seek(File *f, off_t offset, int whence) {
return linux_syscalls::lseek(lf->get_fd(), offset, whence);
}
+int LinuxFile::close_fd() {
+ if (fd < 0)
+ return EBADF;
+ // Linux closes the file descriptor early in the syscall, so we assume it's
+ // always closed after the call. That means callers should also delete the
+ // file object on error.
+ auto result = linux_syscalls::close(fd);
+ if (!result)
+ return result.error();
+ return 0;
+}
+
int linux_file_close(File *f) {
File::remove_file(f);
auto *lf = reinterpret_cast<LinuxFile *>(f);
- int retval = 0;
- if (lf->get_fd() >= 0) {
- // Linux closes the file descriptor early in the syscall, so we assume it's
- // always closed after the call. That means we should also delete the
- // LinuxFile on error.
- auto result = linux_syscalls::close(lf->get_fd());
- if (!result)
- retval = result.error();
- } else {
- retval = EBADF;
- }
+ int retval = lf->close_fd();
delete lf;
return retval;
}
@@ -111,20 +117,11 @@ ErrorOr<File *> openfile(const char *path, const char *mode) {
if (!fd)
return Error(fd.error());
- uint8_t *buffer;
- {
- AllocChecker ac;
- buffer = new (ac) uint8_t[File::DEFAULT_BUFFER_SIZE];
- if (!ac)
- return Error(ENOMEM);
- }
- AllocChecker ac;
- auto *file = new (ac) LinuxFile(fd.value(), buffer, File::DEFAULT_BUFFER_SIZE,
- _IOFBF, true, file_mode);
- if (!ac)
- return Error(ENOMEM);
- File::add_file(file);
- return file;
+ ErrorOr<LinuxFile *> file = LinuxFile::create_buffered(fd.value(), file_mode);
+ if (!file)
+ return Error(file.error());
+ File::add_file(file.value());
+ return file.value();
}
static ErrorOr<int> set_close_on_exec(int fd) {
@@ -172,20 +169,10 @@ ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode) {
}
}
- uint8_t *buffer;
- {
- AllocChecker ac;
- buffer = new (ac) uint8_t[File::DEFAULT_BUFFER_SIZE];
- if (!ac) {
- return Error(ENOMEM);
- }
- }
- AllocChecker ac;
- auto *file = new (ac)
- LinuxFile(fd, buffer, File::DEFAULT_BUFFER_SIZE, _IOFBF, true, file_mode);
- if (!ac) {
- return Error(ENOMEM);
- }
+ ErrorOr<LinuxFile *> new_file = LinuxFile::create_buffered(fd, file_mode);
+ if (!new_file)
+ return Error(new_file.error());
+ LinuxFile *file = new_file.value();
File::add_file(file);
if (do_seek) {
result = file->seek(0, SEEK_END);
diff --git a/libc/src/__support/File/linux/file.h b/libc/src/__support/File/linux/file.h
index 539957b8b22c8c..d1ea9da9294f4c 100644
--- a/libc/src/__support/File/linux/file.h
+++ b/libc/src/__support/File/linux/file.h
@@ -1,14 +1,27 @@
-//===--- Linux specialization of the File data structure ------------------===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Linux specialization of the File data structure.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_FILE_LINUX_FILE_H
+#define LLVM_LIBC_SRC___SUPPORT_FILE_LINUX_FILE_H
+#include "hdr/stdio_macros.h"
#include "hdr/types/off_t.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/type_traits.h"
#include "src/__support/File/file.h"
#include "src/__support/File/file_mode.h"
+#include "src/__support/alloc-checker.h"
+#include "src/__support/error_or.h"
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
@@ -21,12 +34,49 @@ int linux_file_close(File *);
class LinuxFile : public File {
int fd;
+ friend int linux_file_close(File *);
+
+protected:
+ // For subclasses which need to do extra cleanup when the file is closed.
+ constexpr LinuxFile(int file_descriptor, uint8_t *buffer, size_t buffer_size,
+ int buffer_mode, bool owned, FileMode mode,
+ CloseFunc *close_func)
+ : File(&linux_file_write, &linux_file_read, &linux_file_seek, close_func,
+ buffer, buffer_size, buffer_mode, owned, mode),
+ fd(file_descriptor) {}
+
+ // Closes the underlying file descriptor. Returns 0 on success, or an error
+ // code. This does not free the file object.
+ int close_fd();
+
public:
constexpr LinuxFile(int file_descriptor, uint8_t *buffer, size_t buffer_size,
int buffer_mode, bool owned, FileMode mode)
- : File(&linux_file_write, &linux_file_read, &linux_file_seek,
- &linux_file_close, buffer, buffer_size, buffer_mode, owned, mode),
- fd(file_descriptor) {}
+ : LinuxFile(file_descriptor, buffer, buffer_size, buffer_mode, owned,
+ mode, &linux_file_close) {}
+
+ // Allocates a FileT owning a new default-size buffer. FileT must be
+ // LinuxFile or a subclass with LinuxFile's constructor signature.
+ template <typename FileT = LinuxFile>
+ LIBC_INLINE static ErrorOr<FileT *> create_buffered(int fd, FileMode mode) {
+ static_assert(cpp::is_base_of_v<LinuxFile, FileT>,
+ "FileT must be LinuxFile or derive from it");
+ uint8_t *buffer;
+ {
+ AllocChecker ac;
+ buffer = new (ac) uint8_t[DEFAULT_BUFFER_SIZE];
+ if (!ac)
+ return Error(ENOMEM);
+ }
+ AllocChecker ac;
+ auto *file = new (ac) FileT(fd, buffer, DEFAULT_BUFFER_SIZE, _IOFBF,
+ /*owned=*/true, mode);
+ if (!ac) {
+ delete buffer;
+ return Error(ENOMEM);
+ }
+ return file;
+ }
int get_fd() const { return fd; }
void set_fd(int new_fd) { fd = new_fd; }
@@ -38,3 +88,5 @@ class LinuxFile : public File {
ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode);
} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_FILE_LINUX_FILE_H
diff --git a/libc/src/__support/File/linux/popen_file.cpp b/libc/src/__support/File/linux/popen_file.cpp
new file mode 100644
index 00000000000000..18435e47bb2497
--- /dev/null
+++ b/libc/src/__support/File/linux/popen_file.cpp
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the Linux File specialization for popen streams.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/File/linux/popen_file.h"
+
+#include "src/__support/CPP/new.h"
+#include "src/__support/File/file.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/mutex.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+PopenFile *PopenFile::popen_list = nullptr;
+Mutex PopenFile::popen_list_lock(/*timed=*/false, /*recursive=*/false,
+ /*robust=*/false, /*pshared=*/false);
+
+void PopenFile::add_to_popen_list_unlocked(PopenFile *f) {
+ f->popen_prev = nullptr;
+ f->popen_next = popen_list;
+ if (popen_list != nullptr)
+ popen_list->popen_prev = f;
+ popen_list = f;
+}
+
+void PopenFile::close_popen_list_fds_unlocked() {
+ for (PopenFile *f = popen_list; f != nullptr; f = f->popen_next)
+ f->close_fd();
+}
+
+int PopenFile::popen_file_close(File *f) {
+ auto *pf = reinterpret_cast<PopenFile *>(f);
+
+ lock_popen_list();
+ if (pf->popen_prev != nullptr)
+ pf->popen_prev->popen_next = pf->popen_next;
+ if (pf->popen_next != nullptr)
+ pf->popen_next->popen_prev = pf->popen_prev;
+ if (popen_list == pf)
+ popen_list = pf->popen_next;
+ unlock_popen_list();
+
+ File::remove_file(f);
+ int retval = pf->close_fd();
+ delete pf;
+ return retval;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/File/linux/popen_file.h b/libc/src/__support/File/linux/popen_file.h
new file mode 100644
index 00000000000000..9c959b069c52dc
--- /dev/null
+++ b/libc/src/__support/File/linux/popen_file.h
@@ -0,0 +1,66 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Linux File specialization for streams created by popen.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_FILE_LINUX_POPEN_FILE_H
+#define LLVM_LIBC_SRC___SUPPORT_FILE_LINUX_POPEN_FILE_H
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/File/file.h"
+#include "src/__support/File/file_mode.h"
+#include "src/__support/File/linux/file.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/mutex.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// A LinuxFile connected to a pipe to a child process created by popen. Open
+// popen streams are kept on their own list, so that each new popen child can
+// close the streams of earlier popen calls.
+class PopenFile : public LinuxFile {
+ pid_t pid = -1;
+ PopenFile *popen_prev = nullptr;
+ PopenFile *popen_next = nullptr;
+
+ static PopenFile *popen_list;
+ static Mutex popen_list_lock;
+
+ // Removes |f| from the popen list, then closes and frees it like a
+ // LinuxFile. This does not wait for the child process.
+ static int popen_file_close(File *f);
+
+public:
+ PopenFile(int file_descriptor, uint8_t *buffer, size_t buffer_size,
+ int buffer_mode, bool owned, FileMode mode)
+ : LinuxFile(file_descriptor, buffer, buffer_size, buffer_mode, owned,
+ mode, &popen_file_close) {}
+
+ pid_t get_pid() const { return pid; }
+ void set_pid(pid_t new_pid) { pid = new_pid; }
+
+ // The popen list lock must be held while calling the *_unlocked functions
+ // below. popen holds it from before forking until the new stream is on the
+ // list, so that the child sees a consistent list.
+ static void lock_popen_list() { popen_list_lock.lock(); }
+ static void unlock_popen_list() { popen_list_lock.unlock(); }
+
+ // Adds |f| to the popen list.
+ static void add_to_popen_list_unlocked(PopenFile *f);
+
+ // Closes the file descriptors of every stream on the popen list without
+ // freeing anything. This is meant to be called in a newly forked child.
+ static void close_popen_list_fds_unlocked();
+};
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_FILE_LINUX_POPEN_FILE_H
diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index e01f079c6780e5..728166d37be6de 100644
--- a/libc/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt
@@ -66,6 +66,25 @@ add_header_library(
libc.src.__support.CPP.string_view
)
+add_header_library(
+ shell_fork
+ HDRS
+ shell_fork.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.signal_macros
+ libc.hdr.types.pid_t
+ libc.src.__support.common
+ libc.src.__support.CPP.functional
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.OSUtil.linux.syscall_wrappers.clone3
+ libc.src.__support.OSUtil.linux.syscall_wrappers.execle
+ libc.src.__support.OSUtil.linux.syscall_wrappers.wait4
+ libc.src.unistd.environ
+)
+
add_header_library(
sysinfo
HDRS
diff --git a/libc/src/__support/OSUtil/linux/shell_fork.h b/libc/src/__support/OSUtil/linux/shell_fork.h
new file mode 100644
index 00000000000000..3dd64fad34e80a
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/shell_fork.h
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Helpers for running a command with the shell in a child process, shared by
+/// functions like system and popen.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SHELL_FORK_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SHELL_FORK_H
+
+#include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
+#include "hdr/types/pid_t.h"
+#include "src/__support/CPP/functional.h"
+#include "src/__support/OSUtil/exit.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/clone3.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/execle.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/wait4.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include "src/unistd/environ.h"
+
+#include <linux/sched.h> // For struct clone_args
+
+namespace LIBC_NAMESPACE_DECL {
+
+LIBC_INLINE_VAR constexpr char SHELL_PATH[] = "/bin/sh";
+
+// Work to do in the child process after forking and before executing the
+// shell. Returns false if the setup failed and the shell should not be run.
+using ShellChildSetup = cpp::function<bool()>;
+
+// Forks a child process which runs |setup| and then executes
+// "sh -c |command|" with the current environment. If |setup| or the exec
+// fails, the child exits with status 127. Returns the child's pid in the
+// parent.
+LIBC_INLINE ErrorOr<pid_t> shell_fork(const char *command,
+ ShellChildSetup setup) {
+ struct clone_args cl_args{};
+ cl_args.exit_signal = SIGCHLD;
+ ErrorOr<pid_t> fork_res = linux_syscalls::clone3(&cl_args, sizeof(cl_args));
+ if (!fork_res.has_value())
+ return Error(fork_res.error());
+
+ if (fork_res.value() == 0) {
+ // Error checking isn't helpful since this is the forked process, so we
+ // can't set errno. All we can meaningfully do is exit with status 127.
+ if (setup())
+ linux_syscalls::execle(SHELL_PATH, "sh", "-c", command, 0, environ);
+ internal::exit(127);
+ }
+
+ return fork_res.value();
+}
+
+// Waits for the child process |pid| to terminate, retrying if interrupted by
+// a signal. Returns the child's wait status.
+LIBC_INLINE ErrorOr<int> shell_wait(pid_t pid) {
+ int status = 0;
+ while (true) {
+ ErrorOr<pid_t> wait_res = linux_syscalls::wait4(pid, &status, 0, nullptr);
+ if (wait_res.has_value())
+ return status;
+ if (wait_res.error() != EINTR)
+ return Error(wait_res.error());
+ }
+}
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SHELL_FORK_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 5247a6c71ffce9..85d0d76ba7f077 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -588,6 +588,19 @@ add_header_library(
libc.include.sys_syscall
)
+add_header_library(
+ pipe2
+ HDRS
+ pipe2.h
+ DEPENDS
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.src.__support.macros.sanitizer
+ libc.include.sys_syscall
+)
+
add_header_library(
posix_fadvise
HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/pipe2.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/pipe2.h
new file mode 100644
index 00000000000000..dec98c869fcb23
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/pipe2.h
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Syscall wrapper for pipe2.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_PIPE2_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_PIPE2_H
+
+#include "src/__support/OSUtil/linux/syscall.h" // syscall_checked
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/sanitizer.h" // LIBC_MSAN_UNPOISON
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<int> pipe2(int pipefd[2], int flags) {
+ ErrorOr<int> result = syscall_checked<int>(SYS_pipe2, pipefd, flags);
+ if (result.has_value())
+ LIBC_MSAN_UNPOISON(pipefd, sizeof(int) * 2);
+ return result;
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_PIPE2_H
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 5bcfb534737192..30cff885b526fe 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -320,6 +320,20 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.fdopen
)
+add_entrypoint_object(
+ popen
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.popen
+)
+
+add_entrypoint_object(
+ pclose
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.pclose
+)
+
add_entrypoint_object(
tmpfile
ALIAS
diff --git a/libc/src/stdio/linux/CMakeLists.txt b/libc/src/stdio/linux/CMakeLists.txt
index d782ee65866442..6fc5e7b7940a33 100644
--- a/libc/src/stdio/linux/CMakeLists.txt
+++ b/libc/src/stdio/linux/CMakeLists.txt
@@ -48,6 +48,49 @@ add_entrypoint_object(
libc.src.__support.File.platform_file
)
+add_entrypoint_object(
+ popen
+ SRCS
+ popen.cpp
+ HDRS
+ ../popen.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.fcntl_macros
+ libc.hdr.types.FILE
+ libc.hdr.types.pid_t
+ libc.hdr.unistd_macros
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.File.file
+ libc.src.__support.File.linux.popen_file
+ libc.src.__support.File.platform_file
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
+ libc.src.__support.OSUtil.linux.shell_fork
+ libc.src.__support.OSUtil.linux.syscall_wrappers.close
+ libc.src.__support.OSUtil.linux.syscall_wrappers.dup2
+ libc.src.__support.OSUtil.linux.syscall_wrappers.fcntl
+ libc.src.__support.OSUtil.linux.syscall_wrappers.pipe2
+)
+
+add_entrypoint_object(
+ pclose
+ SRCS
+ pclose.cpp
+ HDRS
+ ../pclose.h
+ DEPENDS
+ libc.hdr.types.FILE
+ libc.hdr.types.pid_t
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.File.linux.popen_file
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
+ libc.src.__support.OSUtil.linux.shell_fork
+)
+
add_entrypoint_object(
stdin
SRCS
diff --git a/libc/src/stdio/linux/pclose.cpp b/libc/src/stdio/linux/pclose.cpp
new file mode 100644
index 00000000000000..acb4a6a7e8763a
--- /dev/null
+++ b/libc/src/stdio/linux/pclose.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the POSIX pclose function for Linux.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/pclose.h"
+
+#include "hdr/types/FILE.h"
+#include "hdr/types/pid_t.h"
+#include "src/__support/File/linux/popen_file.h"
+#include "src/__support/OSUtil/linux/shell_fork.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, pclose, (::FILE * stream)) {
+ // Passing a stream that was not created by popen is undefined behavior.
+ auto *file = reinterpret_cast<PopenFile *>(stream);
+ const pid_t pid = file->get_pid();
+
+ // Closing the stream flushes it and closes the parent's end of the pipe, so
+ // a child reading from it sees EOF. It also frees the stream.
+ // TODO: File::close returns early without closing the fd if flushing fails,
+ // which could leave a child reading from the pipe waiting forever. Fix
+ // File::close to always release the fd in a followup.
+ int close_result = file->close();
+
+ // POSIX requires that pclose not return before the child has terminated, so
+ // wait even if closing failed.
+ ErrorOr<int> wait_result = shell_wait(pid);
+ if (!wait_result) {
+ libc_errno = wait_result.error();
+ return -1;
+ }
+ if (close_result != 0) {
+ libc_errno = close_result;
+ return -1;
+ }
+ return wait_result.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/linux/popen.cpp b/libc/src/stdio/linux/popen.cpp
new file mode 100644
index 00000000000000..ea5461c9870f23
--- /dev/null
+++ b/libc/src/stdio/linux/popen.cpp
@@ -0,0 +1,131 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the POSIX popen function for Linux.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/popen.h"
+
+#include "hdr/errno_macros.h"
+#include "hdr/fcntl_macros.h"
+#include "hdr/types/FILE.h"
+#include "hdr/types/pid_t.h"
+#include "hdr/unistd_macros.h"
+#include "src/__support/File/file.h"
+#include "src/__support/File/file_mode.h"
+#include "src/__support/File/linux/file.h"
+#include "src/__support/File/linux/popen_file.h"
+#include "src/__support/OSUtil/linux/shell_fork.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/dup2.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/fcntl.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/pipe2.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace {
+
+// A pipe only goes one way, so these are the only modes popen accepts.
+constexpr FileMode VALID_POPEN_MODES[] = {FileMode("r"), FileMode("w"),
+ FileMode("re"), FileMode("we")};
+
+// TODO: Simplify this with a cpp::find (or similar) helper in
+// src/__support/CPP/algorithm.h once one exists.
+bool is_valid_popen_mode(const FileMode &file_mode) {
+ for (const FileMode &valid_mode : VALID_POPEN_MODES)
+ if (file_mode == valid_mode)
+ return true;
+ return false;
+}
+
+// Clears FD_CLOEXEC on |fd|, leaving its other descriptor flags unchanged.
+ErrorOr<int> clear_close_on_exec(int fd) {
+ ErrorOr<int> flags = linux_syscalls::fcntl(fd, F_GETFD);
+ if (!flags)
+ return flags;
+ return linux_syscalls::fcntl(
+ fd, F_SETFD, reinterpret_cast<void *>(flags.value() & ~FD_CLOEXEC));
+}
+
+// Makes |fd| available as |target_fd| in a process that is about to exec.
+// Both ends of the pipe are created close-on-exec, so if |fd| is already
+// |target_fd| its FD_CLOEXEC flag has to be cleared instead.
+bool move_fd_for_exec(int fd, int target_fd) {
+ if (fd == target_fd)
+ return clear_close_on_exec(fd).has_value();
+ return linux_syscalls::dup2(fd, target_fd).has_value();
+}
+
+} // namespace
+
+LLVM_LIBC_FUNCTION(::FILE *, popen, (const char *command, const char *mode)) {
+ const FileMode file_mode(mode);
+ if (!is_valid_popen_mode(file_mode)) {
+ libc_errno = EINVAL;
+ return nullptr;
+ }
+ const bool read = file_mode.is_read();
+
+ // Both ends start out close-on-exec so that no other child process can
+ // inherit them. The child moves its end onto stdin or stdout before exec.
+ int pipe_fds[2];
+ if (ErrorOr<int> res = linux_syscalls::pipe2(pipe_fds, O_CLOEXEC); !res) {
+ libc_errno = res.error();
+ return nullptr;
+ }
+ const int parent_fd = read ? pipe_fds[0] : pipe_fds[1];
+ const int child_fd = read ? pipe_fds[1] : pipe_fds[0];
+ const int child_target_fd = read ? STDOUT_FILENO : STDIN_FILENO;
+
+ ErrorOr<PopenFile *> new_file =
+ LinuxFile::create_buffered<PopenFile>(parent_fd, file_mode);
+ if (!new_file) {
+ linux_syscalls::close(parent_fd);
+ linux_syscalls::close(child_fd);
+ libc_errno = new_file.error();
+ return nullptr;
+ }
+ PopenFile *file = new_file.value();
+
+ // Hold the popen list lock across the fork so that the child closes exactly
+ // the popen streams which are open in the parent.
+ PopenFile::lock_popen_list();
+ ErrorOr<pid_t> fork_result = shell_fork(command, [&] {
+ PopenFile::close_popen_list_fds_unlocked();
+ return move_fd_for_exec(child_fd, child_target_fd);
+ });
+ linux_syscalls::close(child_fd);
+
+ if (!fork_result) {
+ PopenFile::unlock_popen_list();
+ // The file is not on any list yet, so closing it just closes parent_fd
+ // and frees it.
+ file->close();
+ libc_errno = fork_result.error();
+ return nullptr;
+ }
+
+ file->set_pid(fork_result.value());
+ if (!file_mode.is_close_on_exec()) {
+ // This can only fail if parent_fd is invalid, which it is not.
+ clear_close_on_exec(parent_fd);
+ }
+ PopenFile::add_to_popen_list_unlocked(file);
+ PopenFile::unlock_popen_list();
+
+ File::add_file(file);
+ return reinterpret_cast<::FILE *>(file);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/pclose.h b/libc/src/stdio/pclose.h
new file mode 100644
index 00000000000000..36b220f2d20ca5
--- /dev/null
+++ b/libc/src/stdio/pclose.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of the POSIX pclose function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_PCLOSE_H
+#define LLVM_LIBC_SRC_STDIO_PCLOSE_H
+
+#include "hdr/types/FILE.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int pclose(::FILE *stream);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_PCLOSE_H
diff --git a/libc/src/stdio/popen.h b/libc/src/stdio/popen.h
new file mode 100644
index 00000000000000..52e829c8e97b92
--- /dev/null
+++ b/libc/src/stdio/popen.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of the POSIX popen function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_POPEN_H
+#define LLVM_LIBC_SRC_STDIO_POPEN_H
+
+#include "hdr/types/FILE.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+::FILE *popen(const char *command, const char *mode);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_POPEN_H
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index c40fc6a5b84ce7..a6e7b9920b2f99 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -114,21 +114,17 @@ add_entrypoint_object(
HDRS
../system.h
DEPENDS
- libc.hdr.errno_macros
libc.hdr.signal_macros
libc.hdr.types.pid_t
libc.hdr.types.sigset_t
libc.src.__support.common
+ libc.src.__support.error_or
libc.src.__support.libc_errno
libc.src.__support.macros.config
- libc.src.__support.OSUtil.osutil
- libc.src.__support.OSUtil.linux.syscall_wrappers.execle
- libc.src.__support.OSUtil.linux.syscall_wrappers.clone3
+ libc.src.__support.OSUtil.linux.shell_fork
libc.src.__support.OSUtil.linux.syscall_wrappers.rt_sigaction
libc.src.__support.OSUtil.linux.syscall_wrappers.rt_sigprocmask
- libc.src.__support.OSUtil.linux.syscall_wrappers.wait4
libc.src.signal.linux.signal_utils
- libc.src.unistd.environ
)
add_entrypoint_object(
diff --git a/libc/src/stdlib/linux/system.cpp b/libc/src/stdlib/linux/system.cpp
index f40e1f8756b155..d0383ab1d06909 100644
--- a/libc/src/stdlib/linux/system.cpp
+++ b/libc/src/stdlib/linux/system.cpp
@@ -12,28 +12,20 @@
//===----------------------------------------------------------------------===//
#include "src/stdlib/system.h"
-#include "hdr/errno_macros.h"
#include "hdr/signal_macros.h"
#include "hdr/types/pid_t.h"
#include "hdr/types/sigset_t.h"
-#include "src/__support/OSUtil/exit.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/clone3.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/execle.h"
+#include "src/__support/OSUtil/linux/shell_fork.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigaction.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigprocmask.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/wait4.h"
#include "src/__support/common.h"
+#include "src/__support/error_or.h"
#include "src/__support/libc_errno.h"
#include "src/__support/macros/config.h"
#include "src/signal/linux/signal_utils.h"
-#include "src/unistd/environ.h"
-
-#include <linux/sched.h> // For struct clone_args
namespace LIBC_NAMESPACE_DECL {
-const char *SHELL_PATH = "/bin/sh";
-
LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
if (command == nullptr)
return 1;
@@ -69,50 +61,32 @@ LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
return -1;
}
- struct clone_args cl_args{};
- cl_args.exit_signal = SIGCHLD;
- auto fork_res = linux_syscalls::clone3(&cl_args, sizeof(cl_args));
- if (!fork_res.has_value()) {
+ auto restore_signals = [&] {
linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
linux_syscalls::rt_sigprocmask(SIG_SETMASK, &orig_mask, nullptr);
+ };
+
+ // The child restores the original signal state before running the shell.
+ ErrorOr<pid_t> fork_res = shell_fork(command, [&] {
+ restore_signals();
+ return true;
+ });
+ if (!fork_res.has_value()) {
+ restore_signals();
libc_errno = fork_res.error();
return -1;
}
- pid_t pid = fork_res.value();
- if (pid == 0) {
- linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
- linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
- linux_syscalls::rt_sigprocmask(SIG_SETMASK, &orig_mask, nullptr);
-
- // Error checking isn't helpful since this is the forked process, so we
- // can't set errno. All we can meaningfully do is exit with status 127.
- linux_syscalls::execle(SHELL_PATH, "sh", "-c", command, 0, environ);
+ ErrorOr<int> wait_res = shell_wait(fork_res.value());
- internal::exit(127);
- }
+ restore_signals();
- int status = 0;
- int wait_ret = 0;
- while (true) {
- if (auto wait_res = linux_syscalls::wait4(pid, &status, 0, nullptr);
- !wait_res.has_value()) {
- if (wait_res.error() == EINTR)
- continue;
- wait_ret = -1;
- libc_errno = wait_res.error();
- break;
- }
- wait_ret = status;
- break;
+ if (!wait_res.has_value()) {
+ libc_errno = wait_res.error();
+ return -1;
}
-
- linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
- linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
- linux_syscalls::rt_sigprocmask(SIG_SETMASK, &orig_mask, nullptr);
-
- return wait_ret;
+ return wait_res.value();
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index fb77b8b738e614..f85acff74b6490 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -607,9 +607,10 @@ add_entrypoint_object(
HDRS
../pipe2.h
DEPENDS
- libc.include.unistd
- libc.include.sys_syscall
- libc.src.__support.OSUtil.osutil
+ libc.src.__support.OSUtil.linux.syscall_wrappers.pipe2
+ libc.src.__support.common
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
libc.src.errno.errno
)
diff --git a/libc/src/unistd/linux/pipe2.cpp b/libc/src/unistd/linux/pipe2.cpp
index baf78ff55be7b1..e12309449917fc 100644
--- a/libc/src/unistd/linux/pipe2.cpp
+++ b/libc/src/unistd/linux/pipe2.cpp
@@ -1,30 +1,32 @@
-//===-- Linux implementation of pipe --------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of pipe2 for Linux.
+///
+//===----------------------------------------------------------------------===//
#include "src/unistd/pipe2.h"
-#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
+#include "src/__support/OSUtil/linux/syscall_wrappers/pipe2.h"
#include "src/__support/common.h"
#include "src/__support/libc_errno.h"
#include "src/__support/macros/config.h"
-#include <sys/syscall.h> // For syscall numbers.
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, pipe2, (int pipefd[2], int flags)) {
- int ret = LIBC_NAMESPACE::syscall_impl<int>(
- SYS_pipe2, reinterpret_cast<long>(pipefd), flags);
- if (ret < 0) {
- libc_errno = -ret;
+ ErrorOr<int> result = linux_syscalls::pipe2(pipefd, flags);
+ if (!result.has_value()) {
+ libc_errno = result.error();
return -1;
}
- LIBC_MSAN_UNPOISON(pipefd, sizeof(int) * 2);
- return ret;
+ return result.value();
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 40d91e799b99ab..b5b16abf34ae3c 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -605,6 +605,29 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
libc.test.UnitTest.ErrnoCheckingTest
libc.test.UnitTest.ErrnoSetterMatcher
)
+
+ add_libc_test(
+ popen_pclose_test
+ SUITE
+ libc_stdio_unittests
+ SRCS
+ popen_pclose_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.fcntl_macros
+ libc.hdr.sys_wait_macros
+ libc.hdr.types.FILE
+ libc.src.fcntl.fcntl
+ libc.src.stdio.fgets
+ libc.src.stdio.fileno
+ libc.src.stdio.fputs
+ libc.src.stdio.pclose
+ libc.src.stdio.popen
+ libc.src.stdio.snprintf
+ libc.src.sys.wait.wait
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+ )
endif()
add_libc_test(
diff --git a/libc/test/src/stdio/popen_pclose_test.cpp b/libc/test/src/stdio/popen_pclose_test.cpp
new file mode 100644
index 00000000000000..f901e574fe30a4
--- /dev/null
+++ b/libc/test/src/stdio/popen_pclose_test.cpp
@@ -0,0 +1,158 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unit tests for popen and pclose.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/popen.h"
+
+#include "hdr/errno_macros.h"
+#include "hdr/fcntl_macros.h"
+#include "hdr/sys_wait_macros.h"
+#include "hdr/types/FILE.h"
+#include "src/fcntl/fcntl.h"
+#include "src/stdio/fgets.h"
+#include "src/stdio/fileno.h"
+#include "src/stdio/fputs.h"
+#include "src/stdio/pclose.h"
+#include "src/stdio/snprintf.h"
+#include "src/sys/wait/wait.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+using LlvmLibcPopenTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcPopenTest, ReadMode) {
+ ::FILE *stream = LIBC_NAMESPACE::popen("echo hello", "r");
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_NE(stream, nullptr);
+
+ char buf[16];
+ ASSERT_NE(LIBC_NAMESPACE::fgets(buf, sizeof(buf), stream), nullptr);
+ ASSERT_STREQ(buf, "hello\n");
+
+ int status = LIBC_NAMESPACE::pclose(stream);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST_F(LlvmLibcPopenTest, WriteMode) {
+ // The command exits with 0 only if it reads "hello" from its stdin.
+ ::FILE *stream = LIBC_NAMESPACE::popen("read x; test \"$x\" = hello", "w");
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_NE(stream, nullptr);
+
+ ASSERT_GE(LIBC_NAMESPACE::fputs("hello\n", stream), 0);
+
+ int status = LIBC_NAMESPACE::pclose(stream);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST_F(LlvmLibcPopenTest, ExitStatus) {
+ ::FILE *stream = LIBC_NAMESPACE::popen("exit 42", "r");
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_NE(stream, nullptr);
+
+ int status = LIBC_NAMESPACE::pclose(stream);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 42);
+}
+
+TEST_F(LlvmLibcPopenTest, InvalidMode) {
+ ::FILE *stream = LIBC_NAMESPACE::popen("true", "x");
+ ASSERT_ERRNO_EQ(EINVAL);
+ ASSERT_EQ(stream, nullptr);
+}
+
+TEST_F(LlvmLibcPopenTest, PreviousStreamsClosedInChild) {
+ // Keep a popen stream open while a second command is started.
+ ::FILE *first = LIBC_NAMESPACE::popen("cat >/dev/null", "w");
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_NE(first, nullptr);
+ int first_fd = LIBC_NAMESPACE::fileno(first);
+ ASSERT_GE(first_fd, 0);
+
+ // The second command reports whether the first stream's fd is open in its
+ // own process. Redirecting to a closed fd fails with EBADF.
+ char command[128];
+ ASSERT_GT(LIBC_NAMESPACE::snprintf(
+ command, sizeof(command),
+ "{ true >&%d; } 2>/dev/null && echo open || echo closed",
+ first_fd),
+ 0);
+ ::FILE *second = LIBC_NAMESPACE::popen(command, "r");
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_NE(second, nullptr);
+
+ char buf[16];
+ ASSERT_NE(LIBC_NAMESPACE::fgets(buf, sizeof(buf), second), nullptr);
+ ASSERT_STREQ(buf, "closed\n");
+
+ int status = LIBC_NAMESPACE::pclose(second);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+
+ status = LIBC_NAMESPACE::pclose(first);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST_F(LlvmLibcPopenTest, StatusUnavailable) {
+ ::FILE *stream = LIBC_NAMESPACE::popen("true", "r");
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_NE(stream, nullptr);
+
+ // Reap the child before pclose can, which makes its status unavailable.
+ int status = 0;
+ ASSERT_GT(LIBC_NAMESPACE::wait(&status), 0);
+ ASSERT_TRUE(WIFEXITED(status));
+
+ ASSERT_THAT(LIBC_NAMESPACE::pclose(stream), Fails(ECHILD));
+}
+
+TEST_F(LlvmLibcPopenTest, CloseOnExecMode) {
+ ::FILE *stream = LIBC_NAMESPACE::popen("true", "re");
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_NE(stream, nullptr);
+
+ int fd_flags = LIBC_NAMESPACE::fcntl(LIBC_NAMESPACE::fileno(stream), F_GETFD);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_GE(fd_flags, 0);
+ ASSERT_NE(fd_flags & FD_CLOEXEC, 0);
+
+ int status = LIBC_NAMESPACE::pclose(stream);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST_F(LlvmLibcPopenTest, NoCloseOnExecByDefault) {
+ ::FILE *stream = LIBC_NAMESPACE::popen("true", "r");
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_NE(stream, nullptr);
+
+ int fd_flags = LIBC_NAMESPACE::fcntl(LIBC_NAMESPACE::fileno(stream), F_GETFD);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_GE(fd_flags, 0);
+ ASSERT_EQ(fd_flags & FD_CLOEXEC, 0);
+
+ int status = LIBC_NAMESPACE::pclose(stream);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+}
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index 131288e8d43feb..6585af1a6c563b 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -2368,6 +2368,7 @@ libc_support_library(
":__support_alloc_checker",
":__support_common",
":__support_cpp_new",
+ ":__support_cpp_type_traits",
":__support_error_or",
":__support_file_file",
":__support_libc_errno",
More information about the libc-commits
mailing list