| // -*- C++ -*- |
| // Filesystem utils for the C++ library testsuite. |
| // |
| // Copyright (C) 2014-2022 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 |
| // <http://www.gnu.org/licenses/>. |
| // |
| |
| #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 <filesystem> |
| namespace test_fs = std::filesystem; |
| #else |
| #include <experimental/filesystem> |
| namespace test_fs = std::experimental::filesystem; |
| #endif |
| #include <algorithm> |
| #include <fstream> |
| #include <random> // std::random_device |
| #include <string> |
| #include <system_error> |
| #include <cstdio> |
| #include <unistd.h> // unlink, close, getpid, geteuid |
| |
| #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L |
| #include <stdlib.h> // 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 |