[libc] Add a simple implementation of the posix_spawn function.
authorSiva Chandra Reddy <sivachandra@google.com>
Tue, 11 Oct 2022 00:38:29 +0000 (00:38 +0000)
committerSiva Chandra Reddy <sivachandra@google.com>
Thu, 13 Oct 2022 18:47:47 +0000 (18:47 +0000)
The implementation currently ignores all spawn attributes. Support for
them will be added in future changes.

A simple allocator for integration tests has been added so that the
integration test for posix_spawn can use the
posix_spawn_file_actions_add* functions.

Reviewed By: michaelrj

Differential Revision: https://reviews.llvm.org/D135752

20 files changed:
libc/cmake/modules/LLVMLibCTestRules.cmake
libc/config/linux/api.td
libc/config/linux/x86_64/entrypoints.txt
libc/include/CMakeLists.txt
libc/include/llvm-libc-types/CMakeLists.txt
libc/include/llvm-libc-types/posix_spawnattr_t.h [new file with mode: 0644]
libc/spec/posix.td
libc/spec/spec.td
libc/src/spawn/CMakeLists.txt
libc/src/spawn/linux/CMakeLists.txt [new file with mode: 0644]
libc/src/spawn/linux/posix_spawn.cpp [new file with mode: 0644]
libc/src/spawn/posix_spawn.h [new file with mode: 0644]
libc/test/integration/src/CMakeLists.txt
libc/test/integration/src/spawn/CMakeLists.txt [new file with mode: 0644]
libc/test/integration/src/spawn/posix_spawn_test.cpp [new file with mode: 0644]
libc/test/integration/src/spawn/posix_spawn_test_binary.cpp [new file with mode: 0644]
libc/test/integration/src/spawn/test_binary_properties.h [new file with mode: 0644]
libc/test/integration/src/spawn/testdata/CMakeLists.txt [new file with mode: 0644]
libc/utils/IntegrationTest/CMakeLists.txt
libc/utils/IntegrationTest/test.cpp [new file with mode: 0644]

index f4f0a70..131bbad 100644 (file)
@@ -432,7 +432,8 @@ function(add_integration_test test_name)
       libc.src.__support.threads.thread
       libc.src.stdlib.atexit
       libc.src.stdlib.exit
-      libc.src.unistd.environ)
+      libc.src.unistd.environ
+      libc.utils.IntegrationTest.test)
   list(REMOVE_DUPLICATES fq_deps_list)
 
   # We don't want memory functions to be dependencies on integration tests.
index fff1ff0..1aa1b3f 100644 (file)
@@ -291,5 +291,5 @@ def SysUtsNameAPI : PublicAPI<"sys/utsname.h"> {
 }
 
 def SpawnAPI : PublicAPI<"spawn.h"> {
-  let Types = ["mode_t", "posix_spawn_file_actions_t"];
+  let Types = ["mode_t", "pid_t", "posix_spawnattr_t", "posix_spawn_file_actions_t"];
 }
index 2855096..3a7b142 100644 (file)
@@ -391,6 +391,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.signal.signal
 
     # spawn.h entrypoints
+    libc.src.spawn.posix_spawn
     libc.src.spawn.posix_spawn_file_actions_addclose
     libc.src.spawn.posix_spawn_file_actions_adddup2
     libc.src.spawn.posix_spawn_file_actions_addopen
index 35e84b7..1e401a0 100644 (file)
@@ -220,6 +220,8 @@ add_gen_header(
   DEPENDS
     .llvm_libc_common_h
     .llvm-libc-types.mode_t
+    .llvm-libc-types.pid_t
+    .llvm-libc-types.posix_spawnattr_t
     .llvm-libc-types.posix_spawn_file_actions_t
 )
 
index 46b7393..225f2fd 100644 (file)
@@ -40,6 +40,7 @@ add_header(off_t HDR off_t.h)
 add_header(once_flag HDR once_flag.h DEPENDS .__futex_word)
 add_header(pid_t HDR pid_t.h)
 add_header(posix_spawn_file_actions_t HDR posix_spawn_file_actions_t.h)
+add_header(posix_spawnattr_t HDR posix_spawnattr_t.h)
 add_header(pthread_attr_t HDR pthread_attr_t.h DEPENDS .size_t)
 add_header(pthread_key_t HDR pthread_key_t.h)
 add_header(pthread_mutex_t HDR pthread_mutex_t.h DEPENDS .__futex_word .__mutex_type)
diff --git a/libc/include/llvm-libc-types/posix_spawnattr_t.h b/libc/include/llvm-libc-types/posix_spawnattr_t.h
new file mode 100644 (file)
index 0000000..f1bcb3e
--- /dev/null
@@ -0,0 +1,16 @@
+//===-- Definition of type posix_spawn_file_actions_t ---------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef __LLVM_LIBC_TYPES_POSIX_SPAWNATTR_T_H
+#define __LLVM_LIBC_TYPES_POSIX_SPAWNATTR_T_H
+
+typedef struct {
+  // This data structure will be populated as required.
+} posix_spawnattr_t;
+
+#endif // __LLVM_LIBC_TYPES_POSIX_SPAWNATTR_T_H
index 431a571..53906d0 100644 (file)
@@ -52,8 +52,12 @@ def AtForkCallbackT : NamedType<"__atfork_callback_t">;
 
 def PosixSpawnFileActionsT : NamedType<"posix_spawn_file_actions_t">;
 def PosixSpawnFileActionsTPtr : PtrType<PosixSpawnFileActionsT>;
+def ConstPosixSpawnFileActionsTPtr : ConstType<PosixSpawnFileActionsTPtr>;
 def PosixSpawnFileActionsTRestrictedPtr : RestrictedPtrType<PosixSpawnFileActionsT>;
 
+def PosixSpawnAttrT : NamedType<"posix_spawnattr_t">;
+def RestrictedPosixSpawnAttrTPtrType : RestrictedPtrType<PosixSpawnAttrT>;
+
 def POSIX : StandardSpec<"POSIX"> {
   PtrType CharPtr = PtrType<CharType>;
   RestrictedPtrType RestrictedCharPtr = RestrictedPtrType<CharType>;
@@ -1052,7 +1056,7 @@ def POSIX : StandardSpec<"POSIX"> {
   HeaderSpec Spawn = HeaderSpec<
     "spawn.h",
     [], // Macros
-    [PosixSpawnFileActionsT, ModeTType],
+    [ModeTType, PosixSpawnAttrT, PidT, PosixSpawnFileActionsT],
     [], // Enumerations
     [
       FunctionSpec<
@@ -1081,6 +1085,13 @@ def POSIX : StandardSpec<"POSIX"> {
         RetValSpec<IntType>,
         [ArgSpec<PosixSpawnFileActionsTPtr>]
       >,
+      FunctionSpec<
+        "posix_spawn",
+        RetValSpec<IntType>,
+        [ArgSpec<RestrictedPidTPtr>, ArgSpec<ConstCharRestrictedPtr>,
+         ArgSpec<PosixSpawnFileActionsTPtr>, ArgSpec<RestrictedPosixSpawnAttrTPtrType>,
+         ArgSpec<ConstCharRestrictedPtrPtr>, ArgSpec<ConstCharRestrictedPtrPtr>]
+      >,
     ]
   >;
 
index 85a8f46..1c39bba 100644 (file)
@@ -80,6 +80,7 @@ def ConstCharPtr : ConstType<CharPtr>;
 def CharRestrictedPtr : RestrictedPtrType<CharType>;
 def CharRestrictedPtrPtr : RestrictedPtrType<CharPtr>;
 def ConstCharRestrictedPtr : ConstType<CharRestrictedPtr>;
+def ConstCharRestrictedPtrPtr : PtrType<ConstCharRestrictedPtr>;
 
 def OnceFlagType : NamedType<"once_flag">;
 def OnceFlagTypePtr : PtrType<OnceFlagType>;
@@ -115,6 +116,8 @@ def FILERestrictedPtr : RestrictedPtrType<FILE>;
 def PThreadTType : NamedType<"pthread_t">;
 
 def PidT : NamedType<"pid_t">;
+def RestrictedPidTPtr : RestrictedPtrType<PidT>;
+
 def StructRUsage : NamedType<"struct rusage">;
 def StructRUsagePtr : PtrType<StructRUsage>;
 
index 95a0c74..fd929fa 100644 (file)
@@ -1,3 +1,7 @@
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+endif()
+
 add_header_library(
   file_actions
   HDRS
@@ -63,3 +67,10 @@ add_entrypoint_object(
     libc.include.errno
     libc.include.spawn
 )
+
+add_entrypoint_object(
+  posix_spawn
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.posix_spawn
+)
diff --git a/libc/src/spawn/linux/CMakeLists.txt b/libc/src/spawn/linux/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9bd3ac5
--- /dev/null
@@ -0,0 +1,14 @@
+add_entrypoint_object(
+  posix_spawn
+  SRCS
+    posix_spawn.cpp
+  HDRS
+    ../posix_spawn.h
+  DEPENDS
+    libc.include.fcntl
+    libc.include.spawn
+    libc.include.sys_syscall
+    libc.src.__support.CPP.optional
+    libc.src.__support.OSUtil.osutil
+    libc.src.spawn.file_actions
+)
diff --git a/libc/src/spawn/linux/posix_spawn.cpp b/libc/src/spawn/linux/posix_spawn.cpp
new file mode 100644 (file)
index 0000000..2f82872
--- /dev/null
@@ -0,0 +1,139 @@
+//===-- Linux implementation of posix_spawn -------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/spawn/posix_spawn.h"
+
+#include "src/__support/CPP/optional.h"
+#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
+#include "src/__support/common.h"
+#include "src/spawn/file_actions.h"
+
+#include <fcntl.h>
+#include <spawn.h>
+#include <sys/syscall.h> // For syscall numbers.
+
+namespace __llvm_libc {
+
+namespace {
+
+pid_t fork() {
+  // TODO: Use only the clone syscall and use a sperate small stack in the child
+  // to avoid duplicating the complete stack from the parent. A new stack will
+  // be created on exec anyway so duplicating the full stack is unnecessary.
+#ifdef SYS_fork
+  return __llvm_libc::syscall_impl(SYS_fork);
+#elif defined(SYS_clone)
+  return __llvm_libc::syscall_impl(SYS_clone, SIGCHLD, 0);
+#else
+#error "SYS_fork or SYS_clone not available."
+#endif
+}
+
+cpp::optional<int> open(const char *path, int oflags, mode_t mode) {
+#ifdef SYS_open
+  int fd = __llvm_libc::syscall_impl(SYS_open, path, oflags, mode);
+#else
+  int fd = __llvm_libc::syscall_impl(SYS_openat, AT_FDCWD, path, oflags, mode);
+#endif
+  if (fd > 0)
+    return fd;
+  // The open function is called as part of the child process' preparatory
+  // steps. If an open fails, the child process just exits. So, unlike
+  // the public open function, we do not need to set errno here.
+  return cpp::nullopt;
+}
+
+void close(int fd) { __llvm_libc::syscall_impl(SYS_close, fd); }
+
+bool dup2(int fd, int newfd) {
+  long ret = __llvm_libc::syscall_impl(SYS_dup2, fd, newfd);
+  return ret < 0 ? false : true;
+}
+
+// All exits from child_process are error exits. So, we use a simple
+// exit implementation which exits with code 127.
+void exit() {
+  for (;;) {
+    __llvm_libc::syscall_impl(SYS_exit_group, 127);
+    __llvm_libc::syscall_impl(SYS_exit, 127);
+  }
+}
+
+void child_process(const char *__restrict path,
+                   const posix_spawn_file_actions_t *file_actions,
+                   const posix_spawnattr_t *__restrict, // For now unused
+                   char *const *__restrict argv, char *const *__restrict envp) {
+  // TODO: In the code below, the child_process just exits on error during
+  // processing |file_actions| and |attr|. The correct way would be to exit
+  // after conveying the information about the failure to the parent process
+  // (via a pipe for example).
+  // TODO: Handle |attr|.
+
+  if (file_actions != nullptr) {
+    auto *act = reinterpret_cast<BaseSpawnFileAction *>(file_actions->__front);
+    while (act != nullptr) {
+      switch (act->type) {
+      case BaseSpawnFileAction::OPEN: {
+        auto *open_act = reinterpret_cast<SpawnFileOpenAction *>(act);
+        auto fd = open(open_act->path, open_act->oflag, open_act->mode);
+        if (!fd)
+          exit();
+        int actual_fd = *fd;
+        if (actual_fd != open_act->fd) {
+          bool dup2_result = dup2(actual_fd, open_act->fd);
+          close(actual_fd); // The old fd is not needed anymore.
+          if (!dup2_result)
+            exit();
+        }
+        break;
+      }
+      case BaseSpawnFileAction::CLOSE: {
+        auto *close_act = reinterpret_cast<SpawnFileCloseAction *>(act);
+        close(close_act->fd);
+        break;
+      }
+      case BaseSpawnFileAction::DUP2: {
+        auto *dup2_act = reinterpret_cast<SpawnFileDup2Action *>(act);
+        if (!dup2(dup2_act->fd, dup2_act->newfd))
+          exit();
+        break;
+      }
+      }
+      act = act->next;
+    }
+  }
+
+  if (__llvm_libc::syscall_impl(SYS_execve, path, argv, envp) < 0)
+    exit();
+}
+
+} // anonymous namespace
+
+LLVM_LIBC_FUNCTION(int, posix_spawn,
+                   (pid_t *__restrict pid, const char *__restrict path,
+                    const posix_spawn_file_actions_t *file_actions,
+                    const posix_spawnattr_t *__restrict attr,
+                    char *const *__restrict argv,
+                    char *const *__restrict envp)) {
+  pid_t cpid = fork();
+  if (cpid == 0)
+    child_process(path, file_actions, attr, argv, envp);
+  else if (cpid < 0)
+    return -cpid;
+
+  if (pid != nullptr)
+    *pid = cpid;
+
+  // TODO: Before returning, one should wait for the child_process to startup
+  // successfully. For now, we will just return. Future changes will add proper
+  // wait (using pipes for example).
+
+  return 0;
+}
+
+} // namespace __llvm_libc
diff --git a/libc/src/spawn/posix_spawn.h b/libc/src/spawn/posix_spawn.h
new file mode 100644 (file)
index 0000000..7214627
--- /dev/null
@@ -0,0 +1,23 @@
+//===-- Implementation header for posix_spawn -------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SPAWN_POSIX_SPAWN_H
+#define LLVM_LIBC_SRC_SPAWN_POSIX_SPAWN_H
+
+#include <spawn.h>
+
+namespace __llvm_libc {
+
+int posix_spawn(pid_t *__restrict pid, const char *__restrict path,
+                const posix_spawn_file_actions_t *file_actions,
+                const posix_spawnattr_t *__restrict attr,
+                char *const *__restrict argv, char *const *__restrict envp);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_SPAWN_POSIX_SPAWN_H
index c86cd17..1104b3d 100644 (file)
@@ -1,5 +1,6 @@
 add_subdirectory(__support)
 add_subdirectory(pthread)
+add_subdirectory(spawn)
 add_subdirectory(stdio)
 add_subdirectory(stdlib)
 add_subdirectory(threads)
diff --git a/libc/test/integration/src/spawn/CMakeLists.txt b/libc/test/integration/src/spawn/CMakeLists.txt
new file mode 100644 (file)
index 0000000..dd0e8a3
--- /dev/null
@@ -0,0 +1,46 @@
+add_custom_target(spawn-integration-tests)
+add_dependencies(libc-integration-tests spawn-integration-tests)
+
+add_executable(
+  libc_posix_spawn_test_binary
+  EXCLUDE_FROM_ALL
+  posix_spawn_test_binary.cpp
+  test_binary_properties.h
+)
+set_target_properties(
+  libc_posix_spawn_test_binary
+  PROPERTIES
+    OUTPUT_NAME libc_posix_spawn_test_binary
+    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+add_header_library(
+  test_binary_properties
+  HDRS
+    test_binary_properties.h
+)
+
+add_integration_test(
+  posix_spawn_test
+  SUITE
+    spawn-integration-tests
+  SRCS
+    posix_spawn_test.cpp
+  LOADER
+    libc.loader.linux.crt1
+  DEPENDS
+    libc_posix_spawn_test_binary
+    libc.test.integration.src.spawn.test_binary_properties
+    libc.include.fcntl
+    libc.include.signal
+    libc.include.spawn
+    libc.include.sys_wait
+    libc.src.signal.raise
+    libc.src.spawn.posix_spawn
+    libc.src.spawn.posix_spawn_file_actions_addopen
+    libc.src.spawn.posix_spawn_file_actions_destroy
+    libc.src.spawn.posix_spawn_file_actions_init
+    libc.src.sys.wait.waitpid
+)
+
+add_subdirectory(testdata)
diff --git a/libc/test/integration/src/spawn/posix_spawn_test.cpp b/libc/test/integration/src/spawn/posix_spawn_test.cpp
new file mode 100644 (file)
index 0000000..74fb7d3
--- /dev/null
@@ -0,0 +1,51 @@
+//===-- Unittests for posix_spawn -----------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "test_binary_properties.h"
+
+#include "src/spawn/posix_spawn.h"
+#include "src/spawn/posix_spawn_file_actions_addopen.h"
+#include "src/spawn/posix_spawn_file_actions_destroy.h"
+#include "src/spawn/posix_spawn_file_actions_init.h"
+#include "src/sys/wait/waitpid.h"
+#include "utils/IntegrationTest/test.h"
+
+#include <fcntl.h>
+#include <spawn.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/wait.h>
+
+char arg0[] = "libc_posix_spawn_test_binary";
+char *argv[] = {
+    arg0,
+    nullptr,
+};
+
+void spawn_and_wait_for_normal_exit(char **envp) {
+  pid_t cpid;
+  posix_spawn_file_actions_t file_actions;
+  ASSERT_EQ(__llvm_libc::posix_spawn_file_actions_init(&file_actions), 0);
+  __llvm_libc::posix_spawn_file_actions_addopen(
+      &file_actions, CHILD_FD, "testdata/posix_spawn.test", O_RDONLY, 0);
+  ASSERT_EQ(
+      __llvm_libc::posix_spawn(&cpid, arg0, &file_actions, nullptr, argv, envp),
+      0);
+  ASSERT_TRUE(cpid > 0);
+  int status;
+  ASSERT_EQ(__llvm_libc::waitpid(cpid, &status, 0), cpid);
+  ASSERT_EQ(__llvm_libc::posix_spawn_file_actions_destroy(&file_actions), 0);
+  ASSERT_TRUE(WIFEXITED(status));
+  int exit_status = WEXITSTATUS(status);
+  ASSERT_EQ(exit_status, 0);
+}
+
+TEST_MAIN(int argc, char **argv, char **envp) {
+  spawn_and_wait_for_normal_exit(envp);
+  return 0;
+}
diff --git a/libc/test/integration/src/spawn/posix_spawn_test_binary.cpp b/libc/test/integration/src/spawn/posix_spawn_test_binary.cpp
new file mode 100644 (file)
index 0000000..7aec39c
--- /dev/null
@@ -0,0 +1,22 @@
+#include "test_binary_properties.h"
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char **argv) {
+  if (argc != 1)
+    return 5;
+  constexpr size_t bufsize = sizeof(TEXT);
+  char buf[bufsize];
+  ssize_t readsize = bufsize - 1;
+  ssize_t len = read(CHILD_FD, buf, readsize);
+  if (len != readsize) {
+    return 1;
+  }
+  buf[readsize] = '\0'; // Null terminator
+  if (close(CHILD_FD) != 0)
+    return 2;
+  if (strcmp(buf, TEXT) != 0)
+    return 3;
+  return 0;
+}
diff --git a/libc/test/integration/src/spawn/test_binary_properties.h b/libc/test/integration/src/spawn/test_binary_properties.h
new file mode 100644 (file)
index 0000000..f1521c2
--- /dev/null
@@ -0,0 +1,15 @@
+//===-- Common definitions shared between test binary and test --*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LIBC_TEST_INTEGRATION_SRC_SPAWN_TEST_BINARY_PROPERTIES_H
+#define LIBC_TEST_INTEGRATION_SRC_SPAWN_TEST_BINARY_PROPERTIES_H
+
+constexpr int CHILD_FD = 10;
+constexpr char TEXT[] = "Hello, posix_spawn";
+
+#endif // LIBC_TEST_INTEGRATION_SRC_SPAWN_TEST_BINARY_PROPERTIES_H
diff --git a/libc/test/integration/src/spawn/testdata/CMakeLists.txt b/libc/test/integration/src/spawn/testdata/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f6da5dc
--- /dev/null
@@ -0,0 +1 @@
+file(GENERATE OUTPUT posix_spawn.test CONTENT "Hello, posix_spawn")
index 3866bbe..3d8a349 100644 (file)
@@ -1,5 +1,7 @@
-add_header_library(
+add_object_library(
   test
+  SRCS
+    test.cpp
   HDRS
     test.h
   DEPENDS
diff --git a/libc/utils/IntegrationTest/test.cpp b/libc/utils/IntegrationTest/test.cpp
new file mode 100644 (file)
index 0000000..3de4fa7
--- /dev/null
@@ -0,0 +1,32 @@
+//===-- Simple malloc and free for use with integration tests -------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include <stddef.h>
+#include <stdint.h>
+
+// Integration tests cannot use the SCUDO standalone allocator as SCUDO pulls
+// various other parts of the libc. Since SCUDO development does not use
+// LLVM libc build rules, it is very hard to keep track or pull all that SCUDO
+// requires. Hence, as a work around for this problem, we use a simple allocator
+// which just hands out continuous blocks from a statically allocated chunk of
+// memory.
+
+static uint8_t memory[16384];
+static uint8_t *ptr = memory;
+
+extern "C" {
+
+void *malloc(size_t s) {
+  void *mem = ptr;
+  ptr += s;
+  return mem;
+}
+
+void free(void *) {}
+
+} // extern "C"