diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -93,6 +93,7 @@ libc.src.stdlib.free # stdio.h entrypoints + libc.src.stdio.remove libc.src.stdio.sprintf libc.src.stdio.snprintf 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 @@ -93,6 +93,7 @@ libc.src.stdlib.free # stdio.h entrypoints + libc.src.stdio.remove libc.src.stdio.sprintf libc.src.stdio.snprintf diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -557,6 +557,11 @@ ArgSpec, ArgSpec] >, + FunctionSpec< + "remove", + RetValSpec, + [ArgSpec] + >, FunctionSpec< "sprintf", RetValSpec, diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt --- a/libc/src/stdio/CMakeLists.txt +++ b/libc/src/stdio/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_subdirectory(printf_core) add_entrypoint_object( @@ -275,3 +279,10 @@ libc.src.__support.arg_list libc.src.stdio.printf_core.vfprintf_internal ) + +add_entrypoint_object( + remove + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.remove +) diff --git a/libc/src/stdio/linux/CMakeLists.txt b/libc/src/stdio/linux/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/stdio/linux/CMakeLists.txt @@ -0,0 +1,13 @@ +add_entrypoint_object( + remove + SRCS + remove.cpp + HDRS + ../remove.h + DEPENDS + libc.include.fcntl + libc.include.unistd + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) diff --git a/libc/src/stdio/linux/remove.cpp b/libc/src/stdio/linux/remove.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/linux/remove.cpp @@ -0,0 +1,32 @@ +//===-- Linux implementation of remove ------------------------------------===// +// +// 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/stdio/remove.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" + +#include +#include // For AT_* macros. +#include // For syscall numbers. + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, remove, (const char *path)) { + // We first try unlinking it as a file. If it is ia file, it will succeed. If + // it fails with EISDIR, we will try unlinking it as a directory. + int ret = __llvm_libc::syscall(SYS_unlinkat, AT_FDCWD, path, 0); + if (ret == -EISDIR) + ret = __llvm_libc::syscall(SYS_unlinkat, AT_FDCWD, path, AT_REMOVEDIR); + if (ret >= 0) + return 0; + errno = -ret; + return -1; +} + +} // namespace __llvm_libc diff --git a/libc/src/stdio/remove.h b/libc/src/stdio/remove.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/remove.h @@ -0,0 +1,20 @@ +//===-- Implementation header of remove -------------------------*- 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_STDIO_REMOVE_H +#define LLVM_LIBC_SRC_STDIO_REMOVE_H + +#include + +namespace __llvm_libc { + +int remove(const char *path); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_REMOVE_H diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -111,6 +111,21 @@ libc.src.fenv.fesetround ) -add_subdirectory(printf_core) +add_libc_unittest( + remove_test + SUITE + libc_stdio_unittests + SRCS + remove_test.cpp + DEPENDS + libc.include.errno + libc.include.unistd + libc.src.fcntl.open + libc.src.stdio.remove + libc.src.sys.stat.mkdirat + libc.src.unistd.access + libc.src.unistd.close +) +add_subdirectory(printf_core) add_subdirectory(testdata) diff --git a/libc/test/src/stdio/remove_test.cpp b/libc/test/src/stdio/remove_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/remove_test.cpp @@ -0,0 +1,55 @@ +//===-- Unittests for remove ----------------------------------------------===// +// +// 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/fcntl/open.h" +#include "src/stdio/remove.h" +#include "src/sys/stat/mkdirat.h" +#include "src/unistd/access.h" +#include "src/unistd/close.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" +#include "utils/testutils/FDReader.h" + +#include +#include + +TEST(LlvmLibcRemoveTest, CreateAndRemoveFile) { + // The test strategy is to create a file and remove it, and also verify that + // it was removed. + errno = 0; + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + constexpr const char *TEST_FILE = "testdata/remove.test.file"; + int fd = __llvm_libc::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU); + ASSERT_EQ(errno, 0); + ASSERT_GT(fd, 0); + ASSERT_THAT(__llvm_libc::close(fd), Succeeds(0)); + + ASSERT_THAT(__llvm_libc::access(TEST_FILE, F_OK), Succeeds(0)); + ASSERT_THAT(__llvm_libc::remove(TEST_FILE), Succeeds(0)); + ASSERT_THAT(__llvm_libc::access(TEST_FILE, F_OK), Fails(ENOENT)); +} + +TEST(LlvmLibcRemoveTest, CreateAndRemoveDir) { + // The test strategy is to create a dir and remove it, and also verify that + // it was removed. + errno = 0; + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + constexpr const char *TEST_DIR = "testdata/remove.test.dir"; + ASSERT_THAT(__llvm_libc::mkdirat(AT_FDCWD, TEST_DIR, S_IRWXU), Succeeds(0)); + + ASSERT_THAT(__llvm_libc::access(TEST_DIR, F_OK), Succeeds(0)); + ASSERT_THAT(__llvm_libc::remove(TEST_DIR), Succeeds(0)); + ASSERT_THAT(__llvm_libc::access(TEST_DIR, F_OK), Fails(ENOENT)); +} + +TEST(LlvmLibcRemoveTest, RemoveNonExistent) { + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + ASSERT_THAT(__llvm_libc::remove("testdata/non-existent"), Fails(ENOENT)); +}