diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td index a00e0f61b90df..5e5835a88f646 100644 --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -19,6 +19,9 @@ def FCntlAPI : PublicAPI<"fcntl.h"> { ]; } +def FTWAPI : PublicAPI<"ftw.h"> { +} + def IntTypesAPI : PublicAPI<"inttypes.h"> { let Types = ["imaxdiv_t"]; } diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index d0651c06b930a..79fcddf88b33e 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -32,6 +32,9 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.fcntl.open libc.src.fcntl.openat + # ftw.h entrypoints + libc.src.ftw.ftw + # sched.h entrypoints libc.src.sched.sched_get_priority_max libc.src.sched.sched_get_priority_min @@ -429,7 +432,7 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.fabs libc.src.math.fabsf libc.src.math.fabsl - libc.src.math.fadd + libc.src.math.fadd libc.src.math.faddl libc.src.math.fadd libc.src.math.fdim diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt index 899a93ad72d4c..413744ccedef1 100644 --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -76,6 +76,15 @@ add_header_macro( .llvm_libc_common_h ) +add_header_macro( + ftw + ../libc/newhdrgen/yaml/ftw.yaml + ftw.h.def + ftw.h + DEPENDS + .llvm_libc_common_h +) + add_header_macro( dlfcn ../libc/newhdrgen/yaml/dlfcn.yaml diff --git a/libc/include/ftw.h.def b/libc/include/ftw.h.def new file mode 100644 index 0000000000000..146de47de9191 --- /dev/null +++ b/libc/include/ftw.h.def @@ -0,0 +1,43 @@ +//===-- C standard library header ftw.h -----------------------------------===// +// +// 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_FTW_H +#define LLVM_LIBC_FTW_H + +#include "__llvm-libc-common.h" + +/* macros needed */ + +enum { + FTW_D, + FTW_DNR, + FTW_F, + FTW_DP, + FTW_SL, + FTW_NS, + FTW_SLN +}; // typeflag + +enum { + FTW_ACTIONRETVAL, + FTW_CHDIR, + FTW_DEPTH, + FTW_MOUNT, + FTW_PHYS +}; // flags + +enum { + FTW_CONTINUE, + FTW_SKIP_SIBLINGS, + FTW_SKIP_SUBTREE, + FTW_STOP +}; /* fn return */ + +%%public_api() + +#endif // LLVM_LIBC_FTW_H diff --git a/libc/newhdrgen/yaml/ftw.h b/libc/newhdrgen/yaml/ftw.h new file mode 100644 index 0000000000000..37b44834d56c3 --- /dev/null +++ b/libc/newhdrgen/yaml/ftw.h @@ -0,0 +1,15 @@ +header: fcntl.h +macros: [] +types: [] +enums: [] +objects: [] +functions: + - name: ftw + standards: + - POSIX + return_type: int + arguments: + - type: const char * + - type: int + - type: int + diff --git a/libc/newhdrgen/yaml/ftw.yaml b/libc/newhdrgen/yaml/ftw.yaml new file mode 100644 index 0000000000000..37b44834d56c3 --- /dev/null +++ b/libc/newhdrgen/yaml/ftw.yaml @@ -0,0 +1,15 @@ +header: fcntl.h +macros: [] +types: [] +enums: [] +objects: [] +functions: + - name: ftw + standards: + - POSIX + return_type: int + arguments: + - type: const char * + - type: int + - type: int + diff --git a/libc/spec/posix.td b/libc/spec/posix.td index e354deef340f1..5869f9d3498cd 100644 --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -303,6 +303,37 @@ def POSIX : StandardSpec<"POSIX"> { ] >; + HeaderSpec FTW = HeaderSpec< + "ftw.h", + [], // Macros + [], // Types + [ + EnumeratedNameValue<"FTW_CHDIR">, + EnumeratedNameValue<"FTW_DEPTH">, + EnumeratedNameValue<"FTW_MOUNT">, + EnumeratedNameValue<"FTW_PHYS">, + EnumeratedNameValue<"FTW_F">, + EnumeratedNameValue<"FTW_D">, + EnumeratedNameValue<"FTW_DP">, + EnumeratedNameValue<"FTW_SL">, + EnumeratedNameValue<"FTW_SLN">, + EnumeratedNameValue<"FTW_DNR">, + EnumeratedNameValue<"FTW_SLN"> + ], // Enumerations + [ + FunctionSpec< + "ftw", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "nftw", + RetValSpec, + [ArgSpec] + > + ] + >; + HeaderSpec SysMMan = HeaderSpec< "sys/mman.h", [ @@ -1842,6 +1873,7 @@ def POSIX : StandardSpec<"POSIX"> { DlFcn, Errno, FCntl, + FTW, PThread, Sched, Signal, diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt index 02c193e635362..d8b24f4b74424 100644 --- a/libc/src/CMakeLists.txt +++ b/libc/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(ctype) add_subdirectory(dlfcn) add_subdirectory(errno) add_subdirectory(fenv) +add_subdirectory(ftw) add_subdirectory(inttypes) add_subdirectory(math) add_subdirectory(stdbit) diff --git a/libc/src/ftw/CMakeLists.txt b/libc/src/ftw/CMakeLists.txt new file mode 100644 index 0000000000000..e0a45d6ee0fb9 --- /dev/null +++ b/libc/src/ftw/CMakeLists.txt @@ -0,0 +1,14 @@ +add_entrypoint_object( + ftw + SRCS + ftw.cpp + HDRS + ftw.h + DEPENDS + libc.include.ftw + libc.src.__support.FPUtil.fenv_impl + libc.src.errno.errno + libc.src.fcntl.open + libc.src.unistd.close + +) diff --git a/libc/src/ftw/ftw.cpp b/libc/src/ftw/ftw.cpp new file mode 100644 index 0000000000000..dabd9b2c86445 --- /dev/null +++ b/libc/src/ftw/ftw.cpp @@ -0,0 +1,178 @@ +//===-- Implementation of ftw function ------------------------------------===// +// +// 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/ftw/ftw.h" + +#include "src/__support/common.h" +#include "src/__support/CPP/string.h" +#include "src/errno/libc_errno.h" +#include "src/fcntl/open.h" +#include "src/unistd/close.h" + +#include +#include +#include + + +namespace LIBC_NAMESPACE_DECL { +class Func { + public: + virtual int call(const char*, const struct stat*, int, struct FTW*) = 0; + virtual ~Func() = 0; +}; + +using nftwFn = int (*)(const char* filePath, const struct stat* statBuf, + int tFlag, struct FTW* ftwbuf); + +using ftwFn = int (*)(const char* filePath, const struct stat* statBuf, + int tFlag); + +class NftwFunc : public Func { + public: + NftwFunc(nftwFn fn) : fn(fn) {} + virtual int call(const char* dirPath, const struct stat* statBuf, int tFlag, + struct FTW* ftwBuf) override { + return fn(dirPath, statBuf, tFlag, ftwBuf); + } + virtual ~NftwFunc() {} + + private: + const nftwFn fn; +}; + +class FtwFunc : public Func { + public: + FtwFunc(ftwFn fn) : fn(fn) {} + virtual int call(const char* dirPath, const struct stat* statBuf, int tFlag, + struct FTW*) override { + return fn(dirPath, statBuf, tFlag); + } + virtual ~FtwFunc() {} + + private: + const ftwFn fn; +}; + +int doMergedFtw(const cpp::string& dirPath, Func& fn, int fdLimit, int flags, + int level) { + // fdLimit specifies the maximum number of directories that ftw() + // will hold open simultaneously. When a directory is opened, fdLimit is + // decreased and if it becomes 0 or less, we won't open any more directories. + if (fdLimit <= 0) { + return 0; + } + + // Determine the type of path that is passed. + int typeFlag; + struct stat statBuf; + if (flags & FTW_PHYS) { + if (lstat(dirPath.c_str(), &statBuf) < 0) return -1; + } else { + if (stat(dirPath.c_str(), &statBuf) < 0) { + if (!lstat(dirPath.c_str(), &statBuf)) { + typeFlag = FTW_SLN; /* Symbolic link pointing to a nonexistent file. */ + } else if (libc_errno != EACCES) { + /* stat failed with an errror that is not Permission denied */ + return -1; + } else { + /* The probable cause for the failure is that the caller had read + * permission on the parent directory, so that the filename fpath could + * be seen, but did not have execute permission on the directory. + */ + typeFlag = FTW_NS; + } + } + } + + if (S_ISDIR(statBuf.st_mode)) { + if (flags & FTW_DEPTH) { + typeFlag = FTW_DP; /* Directory, all subdirs have been visited. */ + } else { + typeFlag = FTW_D; /* Directory. */ + } + } else if (S_ISLNK(statBuf.st_mode)) { + if (flags & FTW_PHYS) { + typeFlag = FTW_SL; /* Symbolic link. */ + } else { + typeFlag = FTW_SLN; /* Symbolic link pointing to a nonexistent file. */ + } + } else { + typeFlag = FTW_F; /* Regular file. */ + } + + struct FTW ftwBuf; + // Find the base by finding the last slash. + size_t slash_found = dirPath.rfind("/"); + if (slash_found != cpp::string::npos) { + ftwBuf.base = slash_found + 1; + } + + ftwBuf.level = level; + + // If the dirPath is a file, call the function on it and return. + if ((typeFlag == FTW_SL) || (typeFlag == FTW_F)) { + int returnValue = fn.call(dirPath.c_str(), &statBuf, typeFlag, &ftwBuf); + if (returnValue) { + return returnValue; + } + return 0; + } + + // If FTW_DEPTH is not set, nftw() shall report any directory before reporting + // the files in that directory. + if (!(flags & FTW_DEPTH)) { + // Call the function on the directory. + int directory_fd = open(dirPath.c_str(), O_RDONLY); + if (directory_fd < 0 && libc_errno == EACCES) { + typeFlag = FTW_DNR; /* Directory can't be read. */ + } + close(directory_fd); + + int returnValue = fn.call(dirPath.c_str(), &statBuf, typeFlag, &ftwBuf); + if (returnValue) { + return returnValue; + } + } + + for (std::error_code ec; auto const& dir_entry : + std::filesystem::directory_iterator(dirPath, ec)) { + if (ec) continue; + int returnValue = + doMergedFtw(dir_entry.path(), fn, fdLimit - 1, flags, ftwBuf.level + 1); + if (returnValue) { + return returnValue; + } + } + + // If FTW_DEPTH is set, nftw() shall report all files in a directory before + // reporting the directory itself. + if (flags & FTW_DEPTH) { + // Call the function on the directory. + return fn.call(dirPath.c_str(), &statBuf, typeFlag, &ftwBuf); + } + return 0; +} + +LLVM_LIBC_FUNCTION(int, nftw, (const char *dirPath, + int (*fn)(const char *filePath, const struct stat *statBuf, + int tFlag, struct FTW *ftwbuf), + int fdLimit, int flags)) { + NftwFunc wrappedFn{fn}; + return doMergedFtw(dirPath, wrappedFn, fdLimit, flags, 0); +} + +LLVM_LIBC_FUNCTION(int, ftw, (const char *dirPath, + int (*fn)(const char *filePath, const struct stat *statBuf, + int tFlag), + int fdLimit)) { + FtwFunc wrappedFn{fn}; + return doMergedFtw(dirPath, wrappedFn, fdLimit, FTW_PHYS, 0); +} + +} // namespace LIBC_NAMESPACE_DECL + diff --git a/libc/src/ftw/ftw.h b/libc/src/ftw/ftw.h new file mode 100644 index 0000000000000..107bd97fc1a2e --- /dev/null +++ b/libc/src/ftw/ftw.h @@ -0,0 +1,20 @@ +//===-- Implementation header of ftw ----------------------------*- 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_FTW_FTW_H +#define LLVM_LIBC_SRC_FTW_FTW_H + +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +int ftw(const char* dirpath, int noopenfd, int flags); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_FTW_FTW_H diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt index e121555bd60a9..66c11420a0aef 100644 --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -51,6 +51,7 @@ add_subdirectory(complex) add_subdirectory(ctype) add_subdirectory(errno) add_subdirectory(fenv) +add_subdirectory(ftw) add_subdirectory(math) add_subdirectory(search) add_subdirectory(stdbit) diff --git a/libc/test/src/ftw/CMakeLists.txt b/libc/test/src/ftw/CMakeLists.txt new file mode 100644 index 0000000000000..a2b655f6ff272 --- /dev/null +++ b/libc/test/src/ftw/CMakeLists.txt @@ -0,0 +1,11 @@ +add_custom_target(libc_ftw_unittests) + +add_libc_unittest( + ftw_test + SUITE + libc_ftw_unittests + SRCS + ftw_test.cpp + DEPENDS + libc.src.errno.errno +) diff --git a/libc/test/src/ftw/ftw_test.cpp b/libc/test/src/ftw/ftw_test.cpp new file mode 100644 index 0000000000000..9b93e49cb36bc --- /dev/null +++ b/libc/test/src/ftw/ftw_test.cpp @@ -0,0 +1,236 @@ +// Test code starts here + +//nftw test code: + +namespace fs = std::filesystem; + +class TemporaryDirectory { + public: + TemporaryDirectory() { + fs::path temp_path = fs::temp_directory_path(); + std::string temp_path_prefix = temp_path.string() + "/tmpdir.XXXXXX"; + // Use data() to get a writable string. mkdtemp doesn't write beyond the + // allocated data. + char* dir_name = mkdtemp(temp_path_prefix.data()); + _path = dir_name; + fs::current_path(_path); + } + + ~TemporaryDirectory() { + fs::current_path(_path); + fs::remove_all(_path); + } + const std::string GetDirectoryPath() { return _path.c_str(); } + + private: + fs::path _path; +}; + +static void setupTestData() { + fs::create_directories("sandbox"); + fs::create_directory("sandbox/owner_all_group_read_others_read_dir"); + fs::permissions("sandbox/owner_all_group_read_others_read_dir", + fs::perms::owner_all | fs::perms::group_read | + fs::perms::group_exec | fs::perms::others_read | + fs::perms::others_exec, + fs::perm_options::add); + fs::create_directory( + "sandbox/owner_all_group_read_others_read_dir/" + "owner_read_group_read_others_read_dir"); + fs::permissions( + "sandbox/owner_all_group_read_others_read_dir/" + "owner_read_group_read_others_read_dir", + fs::perms::owner_read | fs::perms::owner_exec | fs::perms::group_read | + fs::perms::group_exec | fs::perms::others_read | + fs::perms::others_exec, + fs::perm_options::add); + fs::create_directory("sandbox/no_perm_dir"); + fs::permissions("sandbox/no_perm_dir", fs::perms::none, + fs::perm_options::add); + + fs::create_symlink("invalid_target", "sandbox/sym1"); + fs::create_directory_symlink("owner_all_group_read_others_read_dir", + "sandbox/sym2"); + + std::ofstream ofs("sandbox/file"); // create regular file +} + +static bool isReadable(const fs::path& p) { + std::error_code ec; // For noexcept overload usage. + auto perms = fs::status(p, ec).permissions(); + if ((perms & fs::perms::owner_read) != fs::perms::none && + (perms & fs::perms::group_read) != fs::perms::none && + (perms & fs::perms::others_read) != fs::perms::none) { + return true; + } + return false; +} + +static int checkNftw(const char* arg_fpath, const struct stat* statBuf, + int typeFlag, struct FTW* ftwbuf) { + displayInfo(arg_fpath, statBuf, typeFlag, ftwbuf); + if (arg_fpath == NULL) { + std::cout << " fpath is null\n"; + return -1; + } + std::string fpath = arg_fpath; + if (statBuf == NULL) { + std::cout << " stat is null " << fpath << "\n"; + return -1; + } + + const fs::path path = fpath; + + // status says we don't know the status of this file. + if (typeFlag == FTW_NS || typeFlag == FTW_SLN) { + struct stat sb; + // Verify we can't stat the path. + if (-1 != stat(arg_fpath, &sb)) { + std::cout << "status doesn't match for " << arg_fpath << "\n"; + return -1; + } + return 0; + } + + // If it is directory + if (S_ISDIR(statBuf->st_mode)) { + if (!fs::is_directory(fs::status(path))) { + std::cout << "Is not directory> " << fpath << "\n"; + return -1; + } + if (isReadable(fpath)) { + // It is readable, verify typeFlag is correct. + if (typeFlag != FTW_D && typeFlag != FTW_DP) { + std::cout << "typeFlag != FTW_D && typeFlag != FTW_DP " << fpath + << "\n"; + return -1; + } + return 0; + } + // It is not readable, and verify typeFlag is correct. + if (typeFlag != FTW_DNR && typeFlag != FTW_D) { + std::cout << "typeFlag != FTW_DNR && typeFlag != FTW_D " << fpath << "\n"; + return -1; + } + return 0; + } + + // If it symlink, verify the filestatus and verify typeFlag is correct. + if (S_ISLNK(statBuf->st_mode)) { + if (!fs::is_symlink(fs::status(path))) { + std::cout << "Is not symlink" << fpath << "\n"; + return -1; + } + if (FTW_SL != typeFlag) { + std::cout << " FTW_SL != typeFlag " << fpath << "\n"; + return -1; + } + return 0; + } + + if (!fs::is_regular_file(fs::status(path))) { + std::cout << " is not a regular file " << fpath << "\n"; + return -1; + } + if (FTW_F != typeFlag) { + std::cout << " FTW_SL != typeFlag " << fpath << "\n"; + return -1; + } + return 0; /* To tell llvm_libc_nftw() to continue */ +} + +static void testNftw() { + std::cout << std::endl << "Calling testNftw: " << std::endl; + TemporaryDirectory tmpDir = TemporaryDirectory(); + setupTestData(); + int flags = 0; + llvm_libc_nftw(tmpDir.GetDirectoryPath(), checkNftw, 128, flags); + std::cout << "All testNftw tests have passed: " << std::endl; +} + +int main(int argc, char* argv[]) { + std::cout << "ftw called with args: " << argv[1] << std::endl; + + int flags = 0; + + if (argc > 2) { + if (strchr(argv[2], 'p') != NULL) flags |= FTW_PHYS; + if (strchr(argv[2], 'd') != NULL) flags |= FTW_DEPTH; + } else { + flags |= FTW_DEPTH; + } + + std::cout << "Calling nftw: " << std::endl; + if (nftw((argc < 2) ? "." : argv[1], displayInfo, 20, flags) == -1) { + perror("nftw"); + exit(EXIT_FAILURE); + } + + std::cout << "Calling llvm_libc_nftw: " << std::endl; + std::string_view dirPath(argv[1]); + if (llvm_libc_nftw((argc < 2) ? "." : argv[1], displayInfo, 20, flags) == + -1) { + perror("llvm_libc_nftw"); + exit(EXIT_FAILURE); + } + + // Unit tests for FTW. + testNftw(); + + exit(EXIT_SUCCESS); +} + +// ftw test code: + + +static int display_info(const char *filePath, const struct stat *sb, int tflag, + struct FTW *ftwbuf) { + printf("%-3s %2d ", + (tflag == FTW_D) ? "d" + : (tflag == FTW_DNR) ? "dnr" + : (tflag == FTW_DP) ? "dp" + : (tflag == FTW_F) ? "f" + : (tflag == FTW_NS) ? "ns" + : (tflag == FTW_SL) ? "sl" + : (tflag == FTW_SLN) ? "sln" + : "???", + ftwbuf->level); + + if (tflag == FTW_NS) + printf("-------"); + else + printf("%7jd", (intmax_t)sb->st_size); + + printf(" %-40s %d %s\n", filePath, ftwbuf->base, filePath + ftwbuf->base); + + return 0; /* To tell llvm_libc_nftw() to continue */ +} + +int main(int argc, char *argv[]) { + std::cout << "ftw called with args: " << argv[1] << std::endl; + + int flags = 0; + + if (argc > 2) { + if (strchr(argv[2], 'p') != NULL) flags |= FTW_PHYS; + if (strchr(argv[2], 'd') != NULL) flags |= FTW_DEPTH; + } else { + flags |= FTW_DEPTH; + } + + std::cout << "Calling nftw: " << std::endl; + if (ftw((argc < 2) ? "." : argv[1], display_info, 20) == -1) { + perror("nftw"); + exit(EXIT_FAILURE); + } + + std::cout << "Calling llvm_libc_nftw: " << std::endl; + std::string_view dirPath(argv[1]); + if (llvm_libc_ftw((argc < 2) ? "." : argv[1], display_info, 20) == -1) { + perror("llvm_libc_nftw"); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} +