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 @@ -152,6 +152,7 @@ libc.src.unistd.fchdir libc.src.unistd.fsync libc.src.unistd.ftruncate + libc.src.unistd.getcwd libc.src.unistd.geteuid libc.src.unistd.getpid libc.src.unistd.getppid diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -359,6 +359,11 @@ RetValSpec, [ArgSpec] >, + FunctionSpec< + "getcwd", + RetValSpec, + [ArgSpec, ArgSpec] + >, FunctionSpec< "close", RetValSpec, diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt --- a/libc/src/string/CMakeLists.txt +++ b/libc/src/string/CMakeLists.txt @@ -5,6 +5,7 @@ HDRS string_utils.h DEPENDS + libc.include.stdlib libc.src.__support.CPP.bitset .memory_utils.memcpy_implementation .memory_utils.bzero_implementation diff --git a/libc/src/string/strdup.cpp b/libc/src/string/strdup.cpp --- a/libc/src/string/strdup.cpp +++ b/libc/src/string/strdup.cpp @@ -17,16 +17,7 @@ namespace __llvm_libc { LLVM_LIBC_FUNCTION(char *, strdup, (const char *src)) { - if (src == nullptr) { - return nullptr; - } - size_t len = internal::string_length(src) + 1; - char *dest = reinterpret_cast(::malloc(len)); - if (dest == nullptr) { - return nullptr; - } - inline_memcpy(dest, src, len); - return dest; + return internal::strdup(src); } } // namespace __llvm_libc diff --git a/libc/src/string/string_utils.h b/libc/src/string/string_utils.h --- a/libc/src/string/string_utils.h +++ b/libc/src/string/string_utils.h @@ -11,9 +11,10 @@ #include "src/__support/CPP/bitset.h" #include "src/__support/common.h" -#include "src/string/memory_utils/memcpy_implementations.h" #include "src/string/memory_utils/bzero_implementations.h" -#include // size_t +#include "src/string/memory_utils/memcpy_implementations.h" +#include // For size_t +#include // For malloc and free namespace __llvm_libc { namespace internal { @@ -98,6 +99,17 @@ return len; } +inline char *strdup(const char *src) { + if (src == nullptr) + return nullptr; + size_t len = string_length(src) + 1; + char *newstr = reinterpret_cast(::malloc(len)); + if (newstr == nullptr) + return nullptr; + inline_memcpy(newstr, src, len); + return newstr; +} + } // namespace internal } // namespace __llvm_libc diff --git a/libc/src/unistd/CMakeLists.txt b/libc/src/unistd/CMakeLists.txt --- a/libc/src/unistd/CMakeLists.txt +++ b/libc/src/unistd/CMakeLists.txt @@ -86,6 +86,13 @@ .${LIBC_TARGET_OS}.ftruncate ) +add_entrypoint_object( + getcwd + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.getcwd +) + add_entrypoint_object( getpid ALIAS diff --git a/libc/src/unistd/getcwd.h b/libc/src/unistd/getcwd.h new file mode 100644 --- /dev/null +++ b/libc/src/unistd/getcwd.h @@ -0,0 +1,20 @@ +//===-- Implementation header for getcwd ------------------------*- 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_UNISTD_GETCWD_H +#define LLVM_LIBC_SRC_UNISTD_GETCWD_H + +#include + +namespace __llvm_libc { + +char *getcwd(char *buf, size_t size); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_GETCWD_H diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt --- a/libc/src/unistd/linux/CMakeLists.txt +++ b/libc/src/unistd/linux/CMakeLists.txt @@ -159,6 +159,21 @@ libc.src.errno.errno ) +add_entrypoint_object( + getcwd + SRCS + getcwd.cpp + HDRS + ../getcwd.h + DEPENDS + libc.include.errno + libc.include.stdlib + libc.include.unistd + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + add_entrypoint_object( geteuid SRCS diff --git a/libc/src/unistd/linux/getcwd.cpp b/libc/src/unistd/linux/getcwd.cpp new file mode 100644 --- /dev/null +++ b/libc/src/unistd/linux/getcwd.cpp @@ -0,0 +1,66 @@ +//===-- Linux implementation of getcwd ------------------------------------===// +// +// 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/unistd/getcwd.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" +#include "src/string/string_utils.h" // For strdup. + +#include +#include // This is safe to include without any name pollution. +#include +#include // For syscall numbers. + +namespace __llvm_libc { + +namespace { + +bool getcwd_syscall(char *buf, size_t size) { + int ret = __llvm_libc::syscall_impl(SYS_getcwd, buf, size); + if (ret < 0) { + errno = -ret; + return false; + } else if (ret == 0 || buf[0] != '/') { + errno = ENOENT; + return false; + } + return true; +} + +} // anonymous namespace + +LLVM_LIBC_FUNCTION(char *, getcwd, (char *buf, size_t size)) { + if (buf == nullptr) { + // We match glibc's behavior here and return the cwd in a malloc-ed buffer. + // We will allocate a static buffer of size PATH_MAX first and fetch the cwd + // into it. This way, if the syscall fails, we avoid unnecessary malloc + // and free. + char pathbuf[PATH_MAX]; + if (!getcwd_syscall(pathbuf, PATH_MAX)) + return nullptr; + char *cwd = internal::strdup(pathbuf); + if (cwd == nullptr) { + errno = ENOMEM; + return nullptr; + } + return cwd; + } else if (size == 0) { + errno = EINVAL; + return nullptr; + } + + // TODO: When buf is not sufficient, evaluate the full cwd path using + // alternate approaches. + + if (!getcwd_syscall(buf, size)) + return nullptr; + return buf; +} + +} // namespace __llvm_libc diff --git a/libc/test/integration/src/unistd/CMakeLists.txt b/libc/test/integration/src/unistd/CMakeLists.txt --- a/libc/test/integration/src/unistd/CMakeLists.txt +++ b/libc/test/integration/src/unistd/CMakeLists.txt @@ -1,6 +1,21 @@ add_custom_target(unistd-integration-tests) add_dependencies(libc-integration-tests unistd-integration-tests) +add_integration_test( + getcwd_test + SUITE + unistd-integration-tests + SRCS + getcwd_test.cpp + LOADER + libc.loader.linux.crt1 + DEPENDS + libc.include.errno + libc.src.__support.CPP.string_view + libc.src.stdlib.getenv + libc.src.unistd.getcwd +) + add_integration_test( fork_test SUITE diff --git a/libc/test/integration/src/unistd/getcwd_test.cpp b/libc/test/integration/src/unistd/getcwd_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/integration/src/unistd/getcwd_test.cpp @@ -0,0 +1,42 @@ +//===-- Unittests for getcwd ----------------------------------------------===// +// +// 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/__support/CPP/string_view.h" +#include "src/stdlib/getenv.h" +#include "src/unistd/getcwd.h" + +#include "utils/IntegrationTest/test.h" + +#include + +using __llvm_libc::cpp::string_view; + +TEST_MAIN(int argc, char **argv, char **envp) { + char buffer[1024]; + ASSERT_TRUE(string_view(__llvm_libc::getenv("PWD")) == + __llvm_libc::getcwd(buffer, 1024)); + + // nullptr buffer + char *cwd = __llvm_libc::getcwd(nullptr, 0); + ASSERT_TRUE(string_view(__llvm_libc::getenv("PWD")) == cwd); + free(cwd); + + // Bad size + cwd = __llvm_libc::getcwd(buffer, 0); + ASSERT_TRUE(cwd == nullptr); + ASSERT_EQ(errno, EINVAL); + errno = 0; + + // Insufficient size + cwd = __llvm_libc::getcwd(buffer, 2); + ASSERT_TRUE(cwd == nullptr); + int err = errno; + ASSERT_EQ(err, ERANGE); + + return 0; +}