[libc-commits] [libc] [llvm] [libc] implement popen/pclose (PR #232846)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Tue Oct 20 02:51:14 PDT 2026


================
@@ -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;
----------------
labath wrote:

Sounds good. FWIW, I think this looks much better than having it all caps. I'd change the rules to be:
- constexpr variables MUST be all caps (this should be easy to enforce)
- variables that CAN be constexpr, MUST be constexpr (I don't know if clang-tidy can do that)

https://github.com/llvm/llvm-project/pull/232846


More information about the libc-commits mailing list