1cc89063bSNico Weber #ifndef FILESYSTEM_TEST_HELPER_H 2cc89063bSNico Weber #define FILESYSTEM_TEST_HELPER_H 3cc89063bSNico Weber 4ac8c9f1eSLouis Dionne #include <filesystem> 5cc89063bSNico Weber 6e0d01294SLouis Dionne #include <sys/stat.h> // for stat, mkdir, mkfifo 75c39eebcSMartin Storsjö #ifndef _WIN32 8b62ce9e0SSergej Jaskiewicz #include <unistd.h> // for ftruncate, link, symlink, getcwd, chdir 978ba1e93SMartin Storsjö #include <sys/statvfs.h> 105c39eebcSMartin Storsjö #else 115c39eebcSMartin Storsjö #include <io.h> 125c39eebcSMartin Storsjö #include <direct.h> 135c39eebcSMartin Storsjö #include <windows.h> // for CreateSymbolicLink, CreateHardLink 145c39eebcSMartin Storsjö #endif 15cc89063bSNico Weber 16cc89063bSNico Weber #include <cassert> 17eb65912eSNikolas Klauser #include <cerrno> 181d50cf98SNikolas Klauser #include <chrono> 196c44ccabSRyan Prichard #include <cstdint> 20cc89063bSNico Weber #include <cstdio> // for printf 217918e624SLouis Dionne #include <cstring> 22cc89063bSNico Weber #include <string> 231d50cf98SNikolas Klauser #include <system_error> 240a4aa8a1SNikolas Klauser #include <type_traits> 25cc89063bSNico Weber #include <vector> 26cc89063bSNico Weber 272f2ed477SMark de Wever #include "assert_macros.h" 28e275e629SMark de Wever #include "make_string.h" 29cc89063bSNico Weber #include "test_macros.h" 30cc89063bSNico Weber #include "format_string.h" 31cc89063bSNico Weber 32d5fa8b11SLouis Dionne // For creating socket files 33fa88f61eSMartin Storsjö #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(_WIN32) 34d5fa8b11SLouis Dionne # include <sys/socket.h> 35d5fa8b11SLouis Dionne # include <sys/un.h> 36d5fa8b11SLouis Dionne #endif 37ac8c9f1eSLouis Dionne namespace fs = std::filesystem; 38d5fa8b11SLouis Dionne 39e0d01294SLouis Dionne namespace utils { 405c39eebcSMartin Storsjö #ifdef _WIN32 415c39eebcSMartin Storsjö inline int mkdir(const char* path, int mode) { (void)mode; return ::_mkdir(path); } 425c39eebcSMartin Storsjö inline int symlink(const char* oldname, const char* newname, bool is_dir) { 435c39eebcSMartin Storsjö DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; 445c39eebcSMartin Storsjö if (CreateSymbolicLinkA(newname, oldname, 455c39eebcSMartin Storsjö flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) 465c39eebcSMartin Storsjö return 0; 475c39eebcSMartin Storsjö if (GetLastError() != ERROR_INVALID_PARAMETER) 485c39eebcSMartin Storsjö return 1; 495c39eebcSMartin Storsjö return !CreateSymbolicLinkA(newname, oldname, flags); 505c39eebcSMartin Storsjö } 515c39eebcSMartin Storsjö inline int link(const char *oldname, const char* newname) { 525c39eebcSMartin Storsjö return !CreateHardLinkA(newname, oldname, NULL); 535c39eebcSMartin Storsjö } 5493671fffSMartin Storsjö inline int setenv(const char *var, const char *val, int overwrite) { 5593671fffSMartin Storsjö (void)overwrite; 5693671fffSMartin Storsjö return ::_putenv((std::string(var) + "=" + std::string(val)).c_str()); 5793671fffSMartin Storsjö } 5893671fffSMartin Storsjö inline int unsetenv(const char *var) { 5993671fffSMartin Storsjö return ::_putenv((std::string(var) + "=").c_str()); 6093671fffSMartin Storsjö } 6178ba1e93SMartin Storsjö inline bool space(std::string path, std::uintmax_t &capacity, 6278ba1e93SMartin Storsjö std::uintmax_t &free, std::uintmax_t &avail) { 6378ba1e93SMartin Storsjö ULARGE_INTEGER FreeBytesAvailableToCaller, TotalNumberOfBytes, 6478ba1e93SMartin Storsjö TotalNumberOfFreeBytes; 6578ba1e93SMartin Storsjö if (!GetDiskFreeSpaceExA(path.c_str(), &FreeBytesAvailableToCaller, 6678ba1e93SMartin Storsjö &TotalNumberOfBytes, &TotalNumberOfFreeBytes)) 6778ba1e93SMartin Storsjö return false; 6878ba1e93SMartin Storsjö capacity = TotalNumberOfBytes.QuadPart; 6978ba1e93SMartin Storsjö free = TotalNumberOfFreeBytes.QuadPart; 7078ba1e93SMartin Storsjö avail = FreeBytesAvailableToCaller.QuadPart; 7178ba1e93SMartin Storsjö assert(capacity > 0); 7278ba1e93SMartin Storsjö assert(free > 0); 7378ba1e93SMartin Storsjö assert(avail > 0); 7478ba1e93SMartin Storsjö return true; 7578ba1e93SMartin Storsjö } 765c39eebcSMartin Storsjö #else 775c39eebcSMartin Storsjö using ::mkdir; 785c39eebcSMartin Storsjö inline int symlink(const char* oldname, const char* newname, bool is_dir) { (void)is_dir; return ::symlink(oldname, newname); } 795c39eebcSMartin Storsjö using ::link; 80539ce1d2SMartin Storsjö using ::setenv; 81539ce1d2SMartin Storsjö using ::unsetenv; 8278ba1e93SMartin Storsjö inline bool space(std::string path, std::uintmax_t &capacity, 8378ba1e93SMartin Storsjö std::uintmax_t &free, std::uintmax_t &avail) { 8478ba1e93SMartin Storsjö struct statvfs expect; 8578ba1e93SMartin Storsjö if (::statvfs(path.c_str(), &expect) == -1) 8678ba1e93SMartin Storsjö return false; 8778ba1e93SMartin Storsjö assert(expect.f_bavail > 0); 8878ba1e93SMartin Storsjö assert(expect.f_bfree > 0); 8978ba1e93SMartin Storsjö assert(expect.f_bsize > 0); 9078ba1e93SMartin Storsjö assert(expect.f_blocks > 0); 9178ba1e93SMartin Storsjö assert(expect.f_frsize > 0); 9278ba1e93SMartin Storsjö auto do_mult = [&](std::uintmax_t val) { 9378ba1e93SMartin Storsjö std::uintmax_t fsize = expect.f_frsize; 9478ba1e93SMartin Storsjö std::uintmax_t new_val = val * fsize; 9578ba1e93SMartin Storsjö assert(new_val / fsize == val); // Test for overflow 9678ba1e93SMartin Storsjö return new_val; 9778ba1e93SMartin Storsjö }; 9878ba1e93SMartin Storsjö capacity = do_mult(expect.f_blocks); 9978ba1e93SMartin Storsjö free = do_mult(expect.f_bfree); 10078ba1e93SMartin Storsjö avail = do_mult(expect.f_bavail); 10178ba1e93SMartin Storsjö return true; 10278ba1e93SMartin Storsjö } 1035c39eebcSMartin Storsjö #endif 1045c39eebcSMartin Storsjö 1056c44ccabSRyan Prichard // N.B. libc might define some of the foo[64] identifiers using macros from 1066c44ccabSRyan Prichard // foo64 -> foo or vice versa. 1076c44ccabSRyan Prichard #if defined(_WIN32) 108bd5d0feeSMark de Wever using off64_t = std::int64_t; 1096c44ccabSRyan Prichard #elif defined(__MVS__) || defined(__LP64__) 1106c44ccabSRyan Prichard using off64_t = ::off_t; 1116c44ccabSRyan Prichard #else 1126c44ccabSRyan Prichard using ::off64_t; 1136c44ccabSRyan Prichard #endif 1146c44ccabSRyan Prichard 1156c44ccabSRyan Prichard inline FILE* fopen64(const char* pathname, const char* mode) { 1166c44ccabSRyan Prichard // Bionic does not distinguish between fopen and fopen64, but fopen64 1176c44ccabSRyan Prichard // wasn't added until API 24. 1186c44ccabSRyan Prichard #if defined(_WIN32) || defined(__MVS__) || defined(__LP64__) || defined(__BIONIC__) 1196c44ccabSRyan Prichard return ::fopen(pathname, mode); 1206c44ccabSRyan Prichard #else 1216c44ccabSRyan Prichard return ::fopen64(pathname, mode); 1226c44ccabSRyan Prichard #endif 1236c44ccabSRyan Prichard } 1246c44ccabSRyan Prichard 1256c44ccabSRyan Prichard inline int ftruncate64(int fd, off64_t length) { 1266c44ccabSRyan Prichard #if defined(_WIN32) 1276c44ccabSRyan Prichard // _chsize_s sets errno on failure and also returns the error number. 1286c44ccabSRyan Prichard return ::_chsize_s(fd, length) ? -1 : 0; 1296c44ccabSRyan Prichard #elif defined(__MVS__) || defined(__LP64__) 1306c44ccabSRyan Prichard return ::ftruncate(fd, length); 1316c44ccabSRyan Prichard #else 1326c44ccabSRyan Prichard return ::ftruncate64(fd, length); 1336c44ccabSRyan Prichard #endif 1346c44ccabSRyan Prichard } 1356c44ccabSRyan Prichard 136e0d01294SLouis Dionne inline std::string getcwd() { 137e0d01294SLouis Dionne // Assume that path lengths are not greater than this. 138e0d01294SLouis Dionne // This should be fine for testing purposes. 139e0d01294SLouis Dionne char buf[4096]; 140e0d01294SLouis Dionne char* ret = ::getcwd(buf, sizeof(buf)); 141e0d01294SLouis Dionne assert(ret && "getcwd failed"); 142e0d01294SLouis Dionne return std::string(ret); 143cc89063bSNico Weber } 144cc89063bSNico Weber 145e0d01294SLouis Dionne inline bool exists(std::string const& path) { 146e0d01294SLouis Dionne struct ::stat tmp; 147e0d01294SLouis Dionne return ::stat(path.c_str(), &tmp) == 0; 148cc89063bSNico Weber } 149953af0e7SLouis Dionne } // namespace utils 150cc89063bSNico Weber 151cc89063bSNico Weber struct scoped_test_env 152cc89063bSNico Weber { 153e0d01294SLouis Dionne scoped_test_env() : test_root(available_cwd_path()) { 1545c39eebcSMartin Storsjö #ifdef _WIN32 1555c39eebcSMartin Storsjö // Windows mkdir can create multiple recursive directories 1565c39eebcSMartin Storsjö // if needed. 1575c39eebcSMartin Storsjö std::string cmd = "mkdir " + test_root.string(); 1585c39eebcSMartin Storsjö #else 1595c39eebcSMartin Storsjö std::string cmd = "mkdir -p " + test_root.string(); 1605c39eebcSMartin Storsjö #endif 161d5fa8b11SLouis Dionne int ret = std::system(cmd.c_str()); 162d5fa8b11SLouis Dionne assert(ret == 0); 16305bc588aSLouis Dionne 16405bc588aSLouis Dionne // Ensure that the root_path is fully resolved, i.e. it contains no 16505bc588aSLouis Dionne // symlinks. The filesystem tests depend on that. We do this after 16605bc588aSLouis Dionne // creating the root_path, because `fs::canonical` requires the 16705bc588aSLouis Dionne // path to exist. 16805bc588aSLouis Dionne test_root = fs::canonical(test_root); 16905bc588aSLouis Dionne } 170cc89063bSNico Weber 171d5fa8b11SLouis Dionne ~scoped_test_env() { 1725c39eebcSMartin Storsjö #ifdef _WIN32 1735c39eebcSMartin Storsjö std::string cmd = "rmdir /s /q " + test_root.string(); 174b03ea054SLouis Dionne int ret = std::system(cmd.c_str()); 175b03ea054SLouis Dionne assert(ret == 0); 1765c39eebcSMartin Storsjö #else 177b8c12af9SMuiez Ahmed #if defined(__MVS__) 178b8c12af9SMuiez Ahmed // The behaviour of chmod -R on z/OS prevents recursive 179b8c12af9SMuiez Ahmed // permission change for directories that do not have read permission. 180b8c12af9SMuiez Ahmed std::string cmd = "find " + test_root.string() + " -exec chmod 777 {} \\;"; 181b8c12af9SMuiez Ahmed #else 1825c39eebcSMartin Storsjö std::string cmd = "chmod -R 777 " + test_root.string(); 183b8c12af9SMuiez Ahmed #endif // defined(__MVS__) 184d5fa8b11SLouis Dionne int ret = std::system(cmd.c_str()); 185bce3b505SRyan Prichard # if !defined(_AIX) && !defined(__ANDROID__) 186d193f7beSSean Fertile // On AIX the chmod command will return non-zero when trying to set 187d193f7beSSean Fertile // the permissions on a directory that contains a bad symlink. This triggers 188d193f7beSSean Fertile // the assert, despite being able to delete everything with the following 189d193f7beSSean Fertile // `rm -r` command. 190bce3b505SRyan Prichard // 191bce3b505SRyan Prichard // Android's chmod was buggy in old OSs, but skipping this assert is 192bce3b505SRyan Prichard // sufficient to ensure that the `rm -rf` succeeds for almost all tests: 193bce3b505SRyan Prichard // - Android L: chmod aborts after one error 194bce3b505SRyan Prichard // - Android L and M: chmod -R tries to set permissions of a symlink 195bce3b505SRyan Prichard // target. 196bce3b505SRyan Prichard // LIBCXX-ANDROID-FIXME: Other fixes to consider: place a toybox chmod 197bce3b505SRyan Prichard // onto old devices, re-enable this assert for devices running Android N 198bce3b505SRyan Prichard // and up, rewrite this chmod+rm in C or C++. 199d5fa8b11SLouis Dionne assert(ret == 0); 200d193f7beSSean Fertile # endif 201d5fa8b11SLouis Dionne 202b8c12af9SMuiez Ahmed cmd = "rm -rf " + test_root.string(); 203d5fa8b11SLouis Dionne ret = std::system(cmd.c_str()); 204d5fa8b11SLouis Dionne assert(ret == 0); 205b03ea054SLouis Dionne #endif 206d5fa8b11SLouis Dionne } 207cc89063bSNico Weber 208cc89063bSNico Weber scoped_test_env(scoped_test_env const &) = delete; 209cc89063bSNico Weber scoped_test_env & operator=(scoped_test_env const &) = delete; 210cc89063bSNico Weber 211cc89063bSNico Weber fs::path make_env_path(std::string p) { return sanitize_path(p); } 212cc89063bSNico Weber 213cc89063bSNico Weber std::string sanitize_path(std::string raw) { 214cc89063bSNico Weber assert(raw.find("..") == std::string::npos); 2155c39eebcSMartin Storsjö std::string root = test_root.string(); 216cc89063bSNico Weber if (root.compare(0, root.size(), raw, 0, root.size()) != 0) { 217cc89063bSNico Weber assert(raw.front() != '\\'); 218cc89063bSNico Weber fs::path tmp(test_root); 219cc89063bSNico Weber tmp /= raw; 2205c39eebcSMartin Storsjö return tmp.string(); 221cc89063bSNico Weber } 222cc89063bSNico Weber return raw; 223cc89063bSNico Weber } 224cc89063bSNico Weber 225cc89063bSNico Weber // Purposefully using a size potentially larger than off_t here so we can 226cc89063bSNico Weber // test the behavior of libc++fs when it is built with _FILE_OFFSET_BITS=64 227cc89063bSNico Weber // but the caller is not (std::filesystem also uses uintmax_t rather than 228cc89063bSNico Weber // off_t). On a 32-bit system this allows us to create a file larger than 229cc89063bSNico Weber // 2GB. 23092fc93e1SMark de Wever std::string create_file(fs::path filename_path, std::uintmax_t size = 0) { 2316c44ccabSRyan Prichard std::string filename = sanitize_path(filename_path.string()); 232cc89063bSNico Weber 233dde0fcd7SMarek Kurdej if (size > 2346c44ccabSRyan Prichard static_cast<typename std::make_unsigned<utils::off64_t>::type>( 2356c44ccabSRyan Prichard std::numeric_limits<utils::off64_t>::max())) { 2367918e624SLouis Dionne std::fprintf(stderr, "create_file(%s, %ju) too large\n", 237cc89063bSNico Weber filename.c_str(), size); 2387918e624SLouis Dionne std::abort(); 239cc89063bSNico Weber } 240cc89063bSNico Weber 24127ea6713SMuiez Ahmed #if defined(_WIN32) || defined(__MVS__) 2425c39eebcSMartin Storsjö # define FOPEN_CLOEXEC_FLAG "" 24327ea6713SMuiez Ahmed #else 24427ea6713SMuiez Ahmed # define FOPEN_CLOEXEC_FLAG "e" 2455c39eebcSMartin Storsjö #endif 2466c44ccabSRyan Prichard FILE* file = utils::fopen64(filename.c_str(), "w" FOPEN_CLOEXEC_FLAG); 247cc89063bSNico Weber if (file == nullptr) { 2487918e624SLouis Dionne std::fprintf(stderr, "fopen %s failed: %s\n", filename.c_str(), 2497918e624SLouis Dionne std::strerror(errno)); 2507918e624SLouis Dionne std::abort(); 251cc89063bSNico Weber } 252cc89063bSNico Weber 2536c44ccabSRyan Prichard if (utils::ftruncate64( 2546c44ccabSRyan Prichard fileno(file), static_cast<utils::off64_t>(size)) == -1) { 2557918e624SLouis Dionne std::fprintf(stderr, "ftruncate %s %ju failed: %s\n", filename.c_str(), 2567918e624SLouis Dionne size, std::strerror(errno)); 2577918e624SLouis Dionne std::fclose(file); 2587918e624SLouis Dionne std::abort(); 259cc89063bSNico Weber } 260cc89063bSNico Weber 2617918e624SLouis Dionne std::fclose(file); 262cc89063bSNico Weber return filename; 263cc89063bSNico Weber } 264cc89063bSNico Weber 2655c39eebcSMartin Storsjö std::string create_dir(fs::path filename_path) { 2665c39eebcSMartin Storsjö std::string filename = filename_path.string(); 267cc89063bSNico Weber filename = sanitize_path(std::move(filename)); 2685c39eebcSMartin Storsjö int ret = utils::mkdir(filename.c_str(), 0777); // rwxrwxrwx mode 269d5fa8b11SLouis Dionne assert(ret == 0); 270cc89063bSNico Weber return filename; 271cc89063bSNico Weber } 272cc89063bSNico Weber 2738f5f6ee2SMartin Storsjö std::string create_file_dir_symlink(fs::path source_path, 2745c39eebcSMartin Storsjö fs::path to_path, 2755c39eebcSMartin Storsjö bool sanitize_source = true, 2765c39eebcSMartin Storsjö bool is_dir = false) { 2775c39eebcSMartin Storsjö std::string source = source_path.string(); 2785c39eebcSMartin Storsjö std::string to = to_path.string(); 279b62ce9e0SSergej Jaskiewicz if (sanitize_source) 280cc89063bSNico Weber source = sanitize_path(std::move(source)); 281cc89063bSNico Weber to = sanitize_path(std::move(to)); 2825c39eebcSMartin Storsjö int ret = utils::symlink(source.c_str(), to.c_str(), is_dir); 283d5fa8b11SLouis Dionne assert(ret == 0); 284cc89063bSNico Weber return to; 285cc89063bSNico Weber } 286cc89063bSNico Weber 2878f5f6ee2SMartin Storsjö std::string create_symlink(fs::path source_path, 2888f5f6ee2SMartin Storsjö fs::path to_path, 2898f5f6ee2SMartin Storsjö bool sanitize_source = true) { 2908f5f6ee2SMartin Storsjö return create_file_dir_symlink(source_path, to_path, sanitize_source, 2918f5f6ee2SMartin Storsjö false); 2928f5f6ee2SMartin Storsjö } 2938f5f6ee2SMartin Storsjö 2948f5f6ee2SMartin Storsjö std::string create_directory_symlink(fs::path source_path, 2958f5f6ee2SMartin Storsjö fs::path to_path, 2968f5f6ee2SMartin Storsjö bool sanitize_source = true) { 2978f5f6ee2SMartin Storsjö return create_file_dir_symlink(source_path, to_path, sanitize_source, 2988f5f6ee2SMartin Storsjö true); 2998f5f6ee2SMartin Storsjö } 3008f5f6ee2SMartin Storsjö 3015c39eebcSMartin Storsjö std::string create_hardlink(fs::path source_path, fs::path to_path) { 3025c39eebcSMartin Storsjö std::string source = source_path.string(); 3035c39eebcSMartin Storsjö std::string to = to_path.string(); 304cc89063bSNico Weber source = sanitize_path(std::move(source)); 305cc89063bSNico Weber to = sanitize_path(std::move(to)); 3065c39eebcSMartin Storsjö int ret = utils::link(source.c_str(), to.c_str()); 307d5fa8b11SLouis Dionne assert(ret == 0); 308cc89063bSNico Weber return to; 309cc89063bSNico Weber } 310cc89063bSNico Weber 3115c39eebcSMartin Storsjö #ifndef _WIN32 312cc89063bSNico Weber std::string create_fifo(std::string file) { 313cc89063bSNico Weber file = sanitize_path(std::move(file)); 314b62ce9e0SSergej Jaskiewicz int ret = ::mkfifo(file.c_str(), 0666); // rw-rw-rw- mode 315d5fa8b11SLouis Dionne assert(ret == 0); 316cc89063bSNico Weber return file; 317cc89063bSNico Weber } 3185c39eebcSMartin Storsjö #endif 319cc89063bSNico Weber 320fa88f61eSMartin Storsjö // Some platforms doesn't support socket files so we shouldn't even 321cc89063bSNico Weber // allow tests to call this unguarded. 322fa88f61eSMartin Storsjö #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(_WIN32) 323cc89063bSNico Weber std::string create_socket(std::string file) { 324cc89063bSNico Weber file = sanitize_path(std::move(file)); 325d5fa8b11SLouis Dionne 326d5fa8b11SLouis Dionne ::sockaddr_un address; 327d5fa8b11SLouis Dionne address.sun_family = AF_UNIX; 328a43e0f90SWu Yingcong 329a43e0f90SWu Yingcong // If file.size() is too big, try to create a file directly inside 330a43e0f90SWu Yingcong // /tmp to make sure file path is short enough. 331a43e0f90SWu Yingcong // Android platform warns about tmpnam, since the problem does not appear 332a43e0f90SWu Yingcong // on Android, let's not apply it for Android. 333a43e0f90SWu Yingcong # if !defined(__ANDROID__) 334dc1ae837SWu Yingcong if (file.size() > sizeof(address.sun_path)) { 335a43e0f90SWu Yingcong file = std::tmpnam(nullptr); 336a43e0f90SWu Yingcong } 337a43e0f90SWu Yingcong # endif 338d5fa8b11SLouis Dionne assert(file.size() <= sizeof(address.sun_path)); 339d5fa8b11SLouis Dionne ::strncpy(address.sun_path, file.c_str(), sizeof(address.sun_path)); 340d5fa8b11SLouis Dionne int fd = ::socket(AF_UNIX, SOCK_STREAM, 0); 341a43e0f90SWu Yingcong assert(::bind(fd, reinterpret_cast<::sockaddr*>(&address), sizeof(address)) == 0); 342cc89063bSNico Weber return file; 343cc89063bSNico Weber } 344cc89063bSNico Weber #endif 345cc89063bSNico Weber 34605bc588aSLouis Dionne fs::path test_root; 347cc89063bSNico Weber 348cc89063bSNico Weber private: 349e0d01294SLouis Dionne // This could potentially introduce a filesystem race if multiple 350e0d01294SLouis Dionne // scoped_test_envs were created concurrently in the same test (hence 351e0d01294SLouis Dionne // sharing the same cwd). However, it is fairly unlikely to happen as 352e0d01294SLouis Dionne // we generally don't use scoped_test_env from multiple threads, so 353e0d01294SLouis Dionne // this is deemed acceptable. 354b982c6f5SMartin Storsjö // The cwd.filename() itself isn't unique across all tests in the suite, 355b982c6f5SMartin Storsjö // so start the numbering from a hash of the full cwd, to avoid 356b982c6f5SMartin Storsjö // different tests interfering with each other. 357e0d01294SLouis Dionne static inline fs::path available_cwd_path() { 358e0d01294SLouis Dionne fs::path const cwd = utils::getcwd(); 359e0d01294SLouis Dionne fs::path const tmp = fs::temp_directory_path(); 360b982c6f5SMartin Storsjö std::string base = cwd.filename().string(); 361fb855eb9SMark de Wever std::size_t i = std::hash<std::string>()(cwd.string()); 362b982c6f5SMartin Storsjö fs::path p = tmp / (base + "-static_env." + std::to_string(i)); 3635c39eebcSMartin Storsjö while (utils::exists(p.string())) { 364b982c6f5SMartin Storsjö p = tmp / (base + "-static_env." + std::to_string(++i)); 365cc89063bSNico Weber } 366cc89063bSNico Weber return p; 367cc89063bSNico Weber } 368cc89063bSNico Weber }; 369cc89063bSNico Weber 370b62ce9e0SSergej Jaskiewicz /// This class generates the following tree: 371b62ce9e0SSergej Jaskiewicz /// 372b62ce9e0SSergej Jaskiewicz /// static_test_env 373355e0ce3SLouis Dionne /// |-- bad_symlink -> dne 374355e0ce3SLouis Dionne /// |-- dir1 375355e0ce3SLouis Dionne /// | |-- dir2 376355e0ce3SLouis Dionne /// | | |-- afile3 377355e0ce3SLouis Dionne /// | | |-- dir3 378355e0ce3SLouis Dionne /// | | | `-- file5 379355e0ce3SLouis Dionne /// | | |-- file4 380355e0ce3SLouis Dionne /// | | `-- symlink_to_dir3 -> dir3 381355e0ce3SLouis Dionne /// | `-- file1 382355e0ce3SLouis Dionne /// | `-- file2 383355e0ce3SLouis Dionne /// |-- empty_file 384355e0ce3SLouis Dionne /// |-- non_empty_file 385355e0ce3SLouis Dionne /// |-- symlink_to_dir -> dir1 386355e0ce3SLouis Dionne /// `-- symlink_to_empty_file -> empty_file 387b62ce9e0SSergej Jaskiewicz /// 388b62ce9e0SSergej Jaskiewicz class static_test_env { 389b62ce9e0SSergej Jaskiewicz scoped_test_env env_; 390b62ce9e0SSergej Jaskiewicz public: 391b62ce9e0SSergej Jaskiewicz static_test_env() { 392b62ce9e0SSergej Jaskiewicz env_.create_symlink("dne", "bad_symlink", false); 393b62ce9e0SSergej Jaskiewicz env_.create_dir("dir1"); 394b62ce9e0SSergej Jaskiewicz env_.create_dir("dir1/dir2"); 395b62ce9e0SSergej Jaskiewicz env_.create_file("dir1/dir2/afile3"); 396b62ce9e0SSergej Jaskiewicz env_.create_dir("dir1/dir2/dir3"); 397b62ce9e0SSergej Jaskiewicz env_.create_file("dir1/dir2/dir3/file5"); 398b62ce9e0SSergej Jaskiewicz env_.create_file("dir1/dir2/file4"); 3998f5f6ee2SMartin Storsjö env_.create_directory_symlink("dir3", "dir1/dir2/symlink_to_dir3", false); 400b62ce9e0SSergej Jaskiewicz env_.create_file("dir1/file1"); 401b62ce9e0SSergej Jaskiewicz env_.create_file("dir1/file2", 42); 402b62ce9e0SSergej Jaskiewicz env_.create_file("empty_file"); 403b62ce9e0SSergej Jaskiewicz env_.create_file("non_empty_file", 42); 4048f5f6ee2SMartin Storsjö env_.create_directory_symlink("dir1", "symlink_to_dir", false); 405b62ce9e0SSergej Jaskiewicz env_.create_symlink("empty_file", "symlink_to_empty_file", false); 406b62ce9e0SSergej Jaskiewicz } 407b62ce9e0SSergej Jaskiewicz 408b62ce9e0SSergej Jaskiewicz const fs::path Root = env_.test_root; 409b62ce9e0SSergej Jaskiewicz 410b62ce9e0SSergej Jaskiewicz fs::path makePath(fs::path const& p) const { 411b62ce9e0SSergej Jaskiewicz // env_path is expected not to contain symlinks. 412b62ce9e0SSergej Jaskiewicz fs::path const& env_path = Root; 413b62ce9e0SSergej Jaskiewicz return env_path / p; 414b62ce9e0SSergej Jaskiewicz } 415b62ce9e0SSergej Jaskiewicz 416b62ce9e0SSergej Jaskiewicz const std::vector<fs::path> TestFileList = { 417b62ce9e0SSergej Jaskiewicz makePath("empty_file"), 418b62ce9e0SSergej Jaskiewicz makePath("non_empty_file"), 419b62ce9e0SSergej Jaskiewicz makePath("dir1/file1"), 420b62ce9e0SSergej Jaskiewicz makePath("dir1/file2") 421b62ce9e0SSergej Jaskiewicz }; 422b62ce9e0SSergej Jaskiewicz 423b62ce9e0SSergej Jaskiewicz const std::vector<fs::path> TestDirList = { 424b62ce9e0SSergej Jaskiewicz makePath("dir1"), 425b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2"), 426b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/dir3") 427b62ce9e0SSergej Jaskiewicz }; 428b62ce9e0SSergej Jaskiewicz 429b62ce9e0SSergej Jaskiewicz const fs::path File = TestFileList[0]; 430b62ce9e0SSergej Jaskiewicz const fs::path Dir = TestDirList[0]; 431b62ce9e0SSergej Jaskiewicz const fs::path Dir2 = TestDirList[1]; 432b62ce9e0SSergej Jaskiewicz const fs::path Dir3 = TestDirList[2]; 433b62ce9e0SSergej Jaskiewicz const fs::path SymlinkToFile = makePath("symlink_to_empty_file"); 434b62ce9e0SSergej Jaskiewicz const fs::path SymlinkToDir = makePath("symlink_to_dir"); 435b62ce9e0SSergej Jaskiewicz const fs::path BadSymlink = makePath("bad_symlink"); 436b62ce9e0SSergej Jaskiewicz const fs::path DNE = makePath("DNE"); 437b62ce9e0SSergej Jaskiewicz const fs::path EmptyFile = TestFileList[0]; 438b62ce9e0SSergej Jaskiewicz const fs::path NonEmptyFile = TestFileList[1]; 439b62ce9e0SSergej Jaskiewicz const fs::path CharFile = "/dev/null"; // Hopefully this exists 440b62ce9e0SSergej Jaskiewicz 441b62ce9e0SSergej Jaskiewicz const std::vector<fs::path> DirIterationList = { 442b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2"), 443b62ce9e0SSergej Jaskiewicz makePath("dir1/file1"), 444b62ce9e0SSergej Jaskiewicz makePath("dir1/file2") 445b62ce9e0SSergej Jaskiewicz }; 446b62ce9e0SSergej Jaskiewicz 447b62ce9e0SSergej Jaskiewicz const std::vector<fs::path> DirIterationListDepth1 = { 448b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/afile3"), 449b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/dir3"), 450b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/symlink_to_dir3"), 451b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/file4"), 452b62ce9e0SSergej Jaskiewicz }; 453b62ce9e0SSergej Jaskiewicz 454b62ce9e0SSergej Jaskiewicz const std::vector<fs::path> RecDirIterationList = { 455b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2"), 456b62ce9e0SSergej Jaskiewicz makePath("dir1/file1"), 457b62ce9e0SSergej Jaskiewicz makePath("dir1/file2"), 458b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/afile3"), 459b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/dir3"), 460b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/symlink_to_dir3"), 461b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/file4"), 462b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/dir3/file5") 463b62ce9e0SSergej Jaskiewicz }; 464b62ce9e0SSergej Jaskiewicz 465b62ce9e0SSergej Jaskiewicz const std::vector<fs::path> RecDirFollowSymlinksIterationList = { 466b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2"), 467b62ce9e0SSergej Jaskiewicz makePath("dir1/file1"), 468b62ce9e0SSergej Jaskiewicz makePath("dir1/file2"), 469b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/afile3"), 470b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/dir3"), 471b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/file4"), 472b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/dir3/file5"), 473b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/symlink_to_dir3"), 474b62ce9e0SSergej Jaskiewicz makePath("dir1/dir2/symlink_to_dir3/file5"), 475b62ce9e0SSergej Jaskiewicz }; 476b62ce9e0SSergej Jaskiewicz }; 477b62ce9e0SSergej Jaskiewicz 478b62ce9e0SSergej Jaskiewicz struct CWDGuard { 479e0d01294SLouis Dionne std::string oldCwd_; 480e0d01294SLouis Dionne CWDGuard() : oldCwd_(utils::getcwd()) { } 481b62ce9e0SSergej Jaskiewicz ~CWDGuard() { 482e0d01294SLouis Dionne int ret = ::chdir(oldCwd_.c_str()); 483b62ce9e0SSergej Jaskiewicz assert(ret == 0 && "chdir failed"); 484b62ce9e0SSergej Jaskiewicz } 485b62ce9e0SSergej Jaskiewicz 486b62ce9e0SSergej Jaskiewicz CWDGuard(CWDGuard const&) = delete; 487b62ce9e0SSergej Jaskiewicz CWDGuard& operator=(CWDGuard const&) = delete; 488b62ce9e0SSergej Jaskiewicz }; 489b62ce9e0SSergej Jaskiewicz 490cc89063bSNico Weber // We often need to test that the error_code was cleared if no error occurs 491cc89063bSNico Weber // this function returns an error_code which is set to an error that will 492cc89063bSNico Weber // never be returned by the filesystem functions. 493cc89063bSNico Weber inline std::error_code GetTestEC(unsigned Idx = 0) { 494cc89063bSNico Weber using std::errc; 495cc89063bSNico Weber auto GetErrc = [&]() { 496cc89063bSNico Weber switch (Idx) { 497cc89063bSNico Weber case 0: 498cc89063bSNico Weber return errc::address_family_not_supported; 499cc89063bSNico Weber case 1: 500cc89063bSNico Weber return errc::address_not_available; 501cc89063bSNico Weber case 2: 502cc89063bSNico Weber return errc::address_in_use; 503cc89063bSNico Weber case 3: 504cc89063bSNico Weber return errc::argument_list_too_long; 505cc89063bSNico Weber default: 506cc89063bSNico Weber assert(false && "Idx out of range"); 507cc89063bSNico Weber std::abort(); 508cc89063bSNico Weber } 509cc89063bSNico Weber }; 510cc89063bSNico Weber return std::make_error_code(GetErrc()); 511cc89063bSNico Weber } 512cc89063bSNico Weber 513cc89063bSNico Weber inline bool ErrorIsImp(const std::error_code& ec, 514cc89063bSNico Weber std::vector<std::errc> const& errors) { 515529ba612SMartin Storsjö std::error_condition cond = ec.default_error_condition(); 516cc89063bSNico Weber for (auto errc : errors) { 517529ba612SMartin Storsjö if (cond.value() == static_cast<int>(errc)) 518cc89063bSNico Weber return true; 519cc89063bSNico Weber } 520cc89063bSNico Weber return false; 521cc89063bSNico Weber } 522cc89063bSNico Weber 523cc89063bSNico Weber template <class... ErrcT> 524cc89063bSNico Weber inline bool ErrorIs(const std::error_code& ec, std::errc First, ErrcT... Rest) { 525cc89063bSNico Weber std::vector<std::errc> errors = {First, Rest...}; 526cc89063bSNico Weber return ErrorIsImp(ec, errors); 527cc89063bSNico Weber } 528cc89063bSNico Weber 529cc89063bSNico Weber // Provide our own Sleep routine since std::this_thread::sleep_for is not 530cc89063bSNico Weber // available in single-threaded mode. 5316bd3d8a1SMartin Storsjö template <class Dur> void SleepFor(Dur dur) { 532cc89063bSNico Weber using namespace std::chrono; 533c6f3b7bcSNikolas Klauser #if !_LIBCPP_HAS_MONOTONIC_CLOCK 534cc89063bSNico Weber using Clock = system_clock; 535cc89063bSNico Weber #else 536cc89063bSNico Weber using Clock = steady_clock; 537cc89063bSNico Weber #endif 538cc89063bSNico Weber const auto wake_time = Clock::now() + dur; 539cc89063bSNico Weber while (Clock::now() < wake_time) 540cc89063bSNico Weber ; 541cc89063bSNico Weber } 542cc89063bSNico Weber 54368de58cdSMartin Storsjö inline fs::perms NormalizeExpectedPerms(fs::perms P) { 54468de58cdSMartin Storsjö #ifdef _WIN32 54568de58cdSMartin Storsjö // On Windows, fs::perms only maps down to one bit stored in the filesystem, 54668de58cdSMartin Storsjö // a boolean readonly flag. 54768de58cdSMartin Storsjö // Normalize permissions to the format it gets returned; all fs entries are 54868de58cdSMartin Storsjö // read+exec for all users; writable ones also have the write bit set for 54968de58cdSMartin Storsjö // all users. 55068de58cdSMartin Storsjö P |= fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read; 55168de58cdSMartin Storsjö P |= fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec; 55268de58cdSMartin Storsjö fs::perms Write = 55368de58cdSMartin Storsjö fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write; 55468de58cdSMartin Storsjö if ((P & Write) != fs::perms::none) 55568de58cdSMartin Storsjö P |= Write; 55668de58cdSMartin Storsjö #endif 55768de58cdSMartin Storsjö return P; 55868de58cdSMartin Storsjö } 55968de58cdSMartin Storsjö 560cc89063bSNico Weber struct ExceptionChecker { 561cc89063bSNico Weber std::errc expected_err; 562cc89063bSNico Weber fs::path expected_path1; 563cc89063bSNico Weber fs::path expected_path2; 564cc89063bSNico Weber unsigned num_paths; 565cc89063bSNico Weber const char* func_name; 566cc89063bSNico Weber std::string opt_message; 567cc89063bSNico Weber 568c479e0c9SLouis Dionne explicit ExceptionChecker(std::errc first_err, const char* fun_name, 569cc89063bSNico Weber std::string opt_msg = {}) 570c479e0c9SLouis Dionne : expected_err{first_err}, num_paths(0), func_name(fun_name), 571cc89063bSNico Weber opt_message(opt_msg) {} 572cc89063bSNico Weber explicit ExceptionChecker(fs::path p, std::errc first_err, 573c479e0c9SLouis Dionne const char* fun_name, std::string opt_msg = {}) 574cc89063bSNico Weber : expected_err(first_err), expected_path1(p), num_paths(1), 575c479e0c9SLouis Dionne func_name(fun_name), opt_message(opt_msg) {} 576cc89063bSNico Weber 577cc89063bSNico Weber explicit ExceptionChecker(fs::path p1, fs::path p2, std::errc first_err, 578c479e0c9SLouis Dionne const char* fun_name, std::string opt_msg = {}) 579cc89063bSNico Weber : expected_err(first_err), expected_path1(p1), expected_path2(p2), 580c479e0c9SLouis Dionne num_paths(2), func_name(fun_name), opt_message(opt_msg) {} 581cc89063bSNico Weber 582cc89063bSNico Weber void operator()(fs::filesystem_error const& Err) { 5832f2ed477SMark de Wever assert(ErrorIsImp(Err.code(), {expected_err})); 5842f2ed477SMark de Wever assert(Err.path1() == expected_path1); 5852f2ed477SMark de Wever assert(Err.path2() == expected_path2); 586*2b26ee6eSJames Y Knight #ifndef _WIN32 587*2b26ee6eSJames Y Knight // On Windows, the error strings are windows error code strings, and don't 588*2b26ee6eSJames Y Knight // match textually with the strings generated for generic std::errc::*. 589cc89063bSNico Weber LIBCPP_ONLY(check_libcxx_string(Err)); 590*2b26ee6eSJames Y Knight #endif 591cc89063bSNico Weber } 592cc89063bSNico Weber 593cc89063bSNico Weber void check_libcxx_string(fs::filesystem_error const& Err) { 594cc89063bSNico Weber std::string message = std::make_error_code(expected_err).message(); 595cc89063bSNico Weber 596cc89063bSNico Weber std::string additional_msg = ""; 597cc89063bSNico Weber if (!opt_message.empty()) { 598cc89063bSNico Weber additional_msg = opt_message + ": "; 599cc89063bSNico Weber } 600cc89063bSNico Weber auto transform_path = [](const fs::path& p) { 6010aa637b2SArthur O'Dwyer return "\"" + p.string() + "\""; 602cc89063bSNico Weber }; 603cc89063bSNico Weber std::string format = [&]() -> std::string { 604cc89063bSNico Weber switch (num_paths) { 605cc89063bSNico Weber case 0: 606cc89063bSNico Weber return format_string("filesystem error: in %s: %s%s", func_name, 607cc89063bSNico Weber additional_msg, message); 608cc89063bSNico Weber case 1: 609cc89063bSNico Weber return format_string("filesystem error: in %s: %s%s [%s]", func_name, 610cc89063bSNico Weber additional_msg, message, 6115c39eebcSMartin Storsjö transform_path(expected_path1).c_str()); 612cc89063bSNico Weber case 2: 613cc89063bSNico Weber return format_string("filesystem error: in %s: %s%s [%s] [%s]", 614cc89063bSNico Weber func_name, additional_msg, message, 6155c39eebcSMartin Storsjö transform_path(expected_path1).c_str(), 6165c39eebcSMartin Storsjö transform_path(expected_path2).c_str()); 617cc89063bSNico Weber default: 6182f2ed477SMark de Wever TEST_FAIL("unexpected case"); 619cc89063bSNico Weber return ""; 620cc89063bSNico Weber } 621cc89063bSNico Weber }(); 6222f2ed477SMark de Wever assert(format == Err.what()); 623cc89063bSNico Weber if (format != Err.what()) { 6247918e624SLouis Dionne std::fprintf(stderr, "filesystem_error::what() does not match expected output:\n"); 6257918e624SLouis Dionne std::fprintf(stderr, " expected: \"%s\"\n", format.c_str()); 6267918e624SLouis Dionne std::fprintf(stderr, " actual: \"%s\"\n\n", Err.what()); 627cc89063bSNico Weber } 628cc89063bSNico Weber } 629cc89063bSNico Weber 630cc89063bSNico Weber ExceptionChecker(ExceptionChecker const&) = delete; 631cc89063bSNico Weber ExceptionChecker& operator=(ExceptionChecker const&) = delete; 632cc89063bSNico Weber 633cc89063bSNico Weber }; 634cc89063bSNico Weber 635e69c65d5SMartin Storsjö inline fs::path GetWindowsInaccessibleDir() { 636e69c65d5SMartin Storsjö // Only makes sense on windows, but the code can be compiled for 637e69c65d5SMartin Storsjö // any platform. 638e69c65d5SMartin Storsjö const fs::path dir("C:\\System Volume Information"); 639e69c65d5SMartin Storsjö std::error_code ec; 640e69c65d5SMartin Storsjö const fs::path root("C:\\"); 641a9a7498bSMartin Storsjö for (const auto &ent : fs::directory_iterator(root, ec)) { 642a9a7498bSMartin Storsjö if (ent != dir) 643a9a7498bSMartin Storsjö continue; 644e69c65d5SMartin Storsjö // Basic sanity checks on the directory_entry 645a9a7498bSMartin Storsjö if (!ent.exists() || !ent.is_directory()) { 6467918e624SLouis Dionne std::fprintf(stderr, "The expected inaccessible directory \"%s\" was found " 647a9a7498bSMartin Storsjö "but doesn't behave as expected, skipping tests " 648a9a7498bSMartin Storsjö "regarding it\n", dir.string().c_str()); 649e69c65d5SMartin Storsjö return fs::path(); 650a9a7498bSMartin Storsjö } 651a9a7498bSMartin Storsjö // Check that it indeed is inaccessible as expected 652a9a7498bSMartin Storsjö (void)fs::exists(ent, ec); 653a9a7498bSMartin Storsjö if (!ec) { 6547918e624SLouis Dionne std::fprintf(stderr, "The expected inaccessible directory \"%s\" was found " 655a9a7498bSMartin Storsjö "but seems to be accessible, skipping tests " 656a9a7498bSMartin Storsjö "regarding it\n", dir.string().c_str()); 657e69c65d5SMartin Storsjö return fs::path(); 658a9a7498bSMartin Storsjö } 659e69c65d5SMartin Storsjö return ent; 660e69c65d5SMartin Storsjö } 6617918e624SLouis Dionne std::fprintf(stderr, "No inaccessible directory \"%s\" found, skipping tests " 662a9a7498bSMartin Storsjö "regarding it\n", dir.string().c_str()); 663e69c65d5SMartin Storsjö return fs::path(); 664e69c65d5SMartin Storsjö } 665e69c65d5SMartin Storsjö 666506cf6dcSLouis Dionne #endif /* FILESYSTEM_TEST_HELPER_H */ 667