diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake --- a/libc/cmake/modules/LLVMLibCTestRules.cmake +++ b/libc/cmake/modules/LLVMLibCTestRules.cmake @@ -432,7 +432,8 @@ 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. diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -291,5 +291,5 @@ } 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"]; } diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -391,6 +391,7 @@ 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 diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -220,6 +220,8 @@ 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 ) diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt --- a/libc/include/llvm-libc-types/CMakeLists.txt +++ b/libc/include/llvm-libc-types/CMakeLists.txt @@ -40,6 +40,7 @@ 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 --- /dev/null +++ b/libc/include/llvm-libc-types/posix_spawnattr_t.h @@ -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 diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -52,8 +52,12 @@ def PosixSpawnFileActionsT : NamedType<"posix_spawn_file_actions_t">; def PosixSpawnFileActionsTPtr : PtrType; +def ConstPosixSpawnFileActionsTPtr : ConstType; def PosixSpawnFileActionsTRestrictedPtr : RestrictedPtrType; +def PosixSpawnAttrT : NamedType<"posix_spawnattr_t">; +def RestrictedPosixSpawnAttrTPtrType : RestrictedPtrType; + def POSIX : StandardSpec<"POSIX"> { PtrType CharPtr = PtrType; RestrictedPtrType RestrictedCharPtr = RestrictedPtrType; @@ -1052,7 +1056,7 @@ HeaderSpec Spawn = HeaderSpec< "spawn.h", [], // Macros - [PosixSpawnFileActionsT, ModeTType], + [ModeTType, PosixSpawnAttrT, PidT, PosixSpawnFileActionsT], [], // Enumerations [ FunctionSpec< @@ -1081,6 +1085,13 @@ RetValSpec, [ArgSpec] >, + FunctionSpec< + "posix_spawn", + RetValSpec, + [ArgSpec, ArgSpec, + ArgSpec, ArgSpec, + ArgSpec, ArgSpec] + >, ] >; diff --git a/libc/spec/spec.td b/libc/spec/spec.td --- a/libc/spec/spec.td +++ b/libc/spec/spec.td @@ -80,6 +80,7 @@ def CharRestrictedPtr : RestrictedPtrType; def CharRestrictedPtrPtr : RestrictedPtrType; def ConstCharRestrictedPtr : ConstType; +def ConstCharRestrictedPtrPtr : PtrType; def OnceFlagType : NamedType<"once_flag">; def OnceFlagTypePtr : PtrType; @@ -115,6 +116,8 @@ def PThreadTType : NamedType<"pthread_t">; def PidT : NamedType<"pid_t">; +def RestrictedPidTPtr : RestrictedPtrType; + def StructRUsage : NamedType<"struct rusage">; def StructRUsagePtr : PtrType; diff --git a/libc/src/spawn/CMakeLists.txt b/libc/src/spawn/CMakeLists.txt --- a/libc/src/spawn/CMakeLists.txt +++ b/libc/src/spawn/CMakeLists.txt @@ -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 @@ 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 --- /dev/null +++ b/libc/src/spawn/linux/CMakeLists.txt @@ -0,0 +1,13 @@ +add_entrypoint_object( + posix_spawn + SRCS + posix_spawn.cpp + HDRS + ../posix_spawn.h + DEPENDS + 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 --- /dev/null +++ b/libc/src/spawn/linux/posix_spawn.cpp @@ -0,0 +1,138 @@ +//===-- 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 +#include // 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 + // 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 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(file_actions->__front); + while (act != nullptr) { + switch (act->type) { + case BaseSpawnFileAction::OPEN: { + auto *open_act = reinterpret_cast(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(act); + close(close_act->fd); + break; + } + case BaseSpawnFileAction::DUP2: { + auto *dup2_act = reinterpret_cast(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 --- /dev/null +++ b/libc/src/spawn/posix_spawn.h @@ -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 + +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 diff --git a/libc/test/integration/src/CMakeLists.txt b/libc/test/integration/src/CMakeLists.txt --- a/libc/test/integration/src/CMakeLists.txt +++ b/libc/test/integration/src/CMakeLists.txt @@ -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 --- /dev/null +++ b/libc/test/integration/src/spawn/CMakeLists.txt @@ -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 --- /dev/null +++ b/libc/test/integration/src/spawn/posix_spawn_test.cpp @@ -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 +#include +#include +#include +#include + +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 --- /dev/null +++ b/libc/test/integration/src/spawn/posix_spawn_test_binary.cpp @@ -0,0 +1,22 @@ +#include "test_binary_properties.h" +#include +#include +#include + +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 --- /dev/null +++ b/libc/test/integration/src/spawn/test_binary_properties.h @@ -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 --- /dev/null +++ b/libc/test/integration/src/spawn/testdata/CMakeLists.txt @@ -0,0 +1 @@ +file(GENERATE OUTPUT posix_spawn.test CONTENT "Hello, posix_spawn") diff --git a/libc/utils/IntegrationTest/CMakeLists.txt b/libc/utils/IntegrationTest/CMakeLists.txt --- a/libc/utils/IntegrationTest/CMakeLists.txt +++ b/libc/utils/IntegrationTest/CMakeLists.txt @@ -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 --- /dev/null +++ b/libc/utils/IntegrationTest/test.cpp @@ -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 +#include + +// 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"