| // -*- C++ -*- | 
 | // Filesystem utils for the C++ library testsuite. | 
 | // | 
 | // Copyright (C) 2014-2025 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 |