// -*- C++ -*- // Filesystem utils for the C++ library testsuite. // // Copyright (C) 2014-2024 Free Software Foundation, Inc. // // This file is part of the GNU ISO C++ Library. This library is free // software; you can redistribute it and/or modify it under the // terms of the GNU General Public License as published by the // Free Software Foundation; either version 3, or (at your option) // any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this library; see the file COPYING3. If not see // . // #ifndef _TESTSUITE_FS_H #define _TESTSUITE_FS_H 1 // Assume we want std::filesystem in C++17, unless USE_FILESYSTEM_TS defined: #if __cplusplus >= 201703L && ! defined USE_FILESYSTEM_TS #include namespace test_fs = std::filesystem; #else #include namespace test_fs = std::experimental::filesystem; #endif #include #include #include // std::random_device #include #include #include #include // unlink, close, getpid, geteuid #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L #include // mkstemp #endif #ifndef _GLIBCXX_HAVE_SYMLINK #define NO_SYMLINKS #endif #if !defined (_GLIBCXX_HAVE_SYS_STATVFS_H) \ && !defined (_GLIBCXX_FILESYSTEM_IS_WINDOWS) #define NO_SPACE #endif #if !(_GLIBCXX_HAVE_SYS_STAT_H \ && (_GLIBCXX_USE_UTIMENSAT || _GLIBCXX_USE_UTIME)) #define NO_LAST_WRITE_TIME 1 #endif namespace __gnu_test { #define PATH_CHK(p1, p2, fn) \ if ( p1.fn() != p2.fn() ) \ throw test_fs::filesystem_error("comparing '" #fn "' failed", p1, p2, \ std::make_error_code(std::errc::invalid_argument) ) void compare_paths(const test_fs::path& p1, const test_fs::path& p2) { PATH_CHK( p1, p2, native ); PATH_CHK( p1, p2, string ); PATH_CHK( p1, p2, empty ); PATH_CHK( p1, p2, has_root_path ); PATH_CHK( p1, p2, has_root_name ); PATH_CHK( p1, p2, has_root_directory ); PATH_CHK( p1, p2, has_relative_path ); PATH_CHK( p1, p2, has_parent_path ); PATH_CHK( p1, p2, has_filename ); PATH_CHK( p1, p2, has_stem ); PATH_CHK( p1, p2, has_extension ); PATH_CHK( p1, p2, is_absolute ); PATH_CHK( p1, p2, is_relative ); auto d1 = std::distance(p1.begin(), p1.end()); auto d2 = std::distance(p2.begin(), p2.end()); if (d1 != d2) throw test_fs::filesystem_error( "distance(begin1, end1) != distance(begin2, end2)", p1, p2, std::make_error_code(std::errc::invalid_argument) ); if (!std::equal(p1.begin(), p1.end(), p2.begin())) throw test_fs::filesystem_error( "!equal(begin1, end1, begin2)", p1, p2, std::make_error_code(std::errc::invalid_argument) ); } const std::string test_paths[] = { "", "/", "//", "/.", "/./", "/a", "/a/", "/a//", "/a/b/c/d", "/a//b", "a", "a/b", "a/b/", "a/b/c", "a/b/c.d", "a/b/..", "a/b/c.", "a/b/.c" }; test_fs::path root_path() { #if defined(__MINGW32__) || defined(__MINGW64__) return L"c:/"; #else return "/"; #endif } // This is NOT supposed to be a secure way to get a unique name! // We just need a path that doesn't exist for testing purposes. test_fs::path nonexistent_path(std::string file = __builtin_FILE()) { // Include the caller's filename to help identify tests that fail to // clean up the files they create. // Remove .cc extension: if (file.length() > 3 && file.compare(file.length() - 3, 3, ".cc") == 0) file.resize(file.length() - 3); // And directory: auto pos = file.find_last_of("/\\"); if (pos != file.npos) file.erase(0, pos+1); file.reserve(file.size() + 40); file.insert(0, "filesystem-test."); // A counter, starting from a random value, to be included as part // of the filename being returned, and incremented each time // this function is used. It allows us to ensure that two calls // to this function can never return the same filename, something // testcases do when they need multiple non-existent filenames // for their purposes. static unsigned counter = std::random_device{}(); file += '.'; file += std::to_string(counter++); file += '.'; test_fs::path p; #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L // Use mkstemp to determine the name of a file which does not exist yet. // // Note that we have seen on some systems (such as RTEMS, for instance) // that mkstemp behaves very predictably, causing it to always try // the same sequence of file names. In other words, if we call mkstemp // with a pattern, delete the file it created (which is what we do, here), // and call mkstemp with the same pattern again, it returns the same // filename once more. While most implementations introduce a degree // of randomness, it is not mandated by the standard, and this is why // we also include a counter in the template passed to mkstemp. file += "XXXXXX"; int fd = ::mkstemp(&file[0]); if (fd == -1) throw test_fs::filesystem_error("mkstemp failed", std::error_code(errno, std::generic_category())); ::unlink(file.c_str()); ::close(fd); p = std::move(file); #else if (file.length() > 64) file.resize(64); // The combination of random counter and PID should be unique for a given // run of the testsuite. N.B. getpid() returns a pointer type on vxworks // in kernel mode. file += std::to_string((unsigned long) ::getpid()); p = std::move(file); if (test_fs::exists(p)) throw test_fs::filesystem_error("Failed to generate unique pathname", p, std::make_error_code(std::errc::file_exists)); #endif return p; } // RAII helper to remove a file on scope exit. struct scoped_file { using path_type = test_fs::path; enum adopt_file_t { adopt_file }; explicit scoped_file(const path_type& p = nonexistent_path()) : path(p) { std::ofstream{p.c_str()}; } scoped_file(path_type p, adopt_file_t) : path(p) { } ~scoped_file() { if (!path.empty()) remove(path); } scoped_file(scoped_file&&) = default; scoped_file& operator=(scoped_file&&) = default; path_type path; }; inline bool permissions_are_testable(bool print_msg = true) { bool testable = false; #if !(defined __MINGW32__ || defined __MINGW64__) if (geteuid() != 0) testable = true; // XXX on Linux the CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH capabilities // can give normal users extra permissions for files and directories. // We ignore that possibility here. #endif if (print_msg && !testable) std::puts("Skipping tests that depend on filesystem permissions"); return testable; } } // namespace __gnu_test #endif