1 // -*- C++ -*-
2 // Filesystem utils for the C++ library testsuite.
3 //
4 // Copyright (C) 2014-2021 Free Software Foundation, Inc.
5 //
6 // This file is part of the GNU ISO C++ Library.  This library is free
7 // software; you can redistribute it and/or modify it under the
8 // terms of the GNU General Public License as published by the
9 // Free Software Foundation; either version 3, or (at your option)
10 // any later version.
11 //
12 // This library is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License along
18 // with this library; see the file COPYING3.  If not see
19 // <http://www.gnu.org/licenses/>.
20 //
21 
22 #ifndef _TESTSUITE_FS_H
23 #define _TESTSUITE_FS_H 1
24 
25 // Assume we want std::filesystem in C++17, unless USE_FILESYSTEM_TS defined:
26 #if __cplusplus >= 201703L && ! defined USE_FILESYSTEM_TS
27 #include <filesystem>
28 namespace test_fs = std::filesystem;
29 #else
30 #include <experimental/filesystem>
31 namespace test_fs = std::experimental::filesystem;
32 #endif
33 #include <algorithm>
34 #include <fstream>
35 #include <string>
36 #include <cstdio>
37 #include <unistd.h> // unlink, close, getpid
38 
39 #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L
40 #include <stdlib.h> // mkstemp
41 #else
42 #include <random>   // std::random_device
43 #endif
44 
45 namespace __gnu_test
46 {
47 #define PATH_CHK(p1, p2, fn) \
48     if ( p1.fn() != p2.fn() ) \
49       throw test_fs::filesystem_error("comparing '" #fn "' failed", p1, p2, \
50 	  std::make_error_code(std::errc::invalid_argument) )
51 
52   void
compare_paths(const test_fs::path & p1,const test_fs::path & p2)53   compare_paths(const test_fs::path& p1,
54 		const test_fs::path& p2)
55   {
56     PATH_CHK( p1, p2, native );
57     PATH_CHK( p1, p2, string );
58     PATH_CHK( p1, p2, empty );
59     PATH_CHK( p1, p2, has_root_path );
60     PATH_CHK( p1, p2, has_root_name );
61     PATH_CHK( p1, p2, has_root_directory );
62     PATH_CHK( p1, p2, has_relative_path );
63     PATH_CHK( p1, p2, has_parent_path );
64     PATH_CHK( p1, p2, has_filename );
65     PATH_CHK( p1, p2, has_stem );
66     PATH_CHK( p1, p2, has_extension );
67     PATH_CHK( p1, p2, is_absolute );
68     PATH_CHK( p1, p2, is_relative );
69     auto d1 = std::distance(p1.begin(), p1.end());
70     auto d2 = std::distance(p2.begin(), p2.end());
71     if (d1 != d2)
72       throw test_fs::filesystem_error(
73 	  "distance(begin1, end1) != distance(begin2, end2)", p1, p2,
74 	  std::make_error_code(std::errc::invalid_argument) );
75     if (!std::equal(p1.begin(), p1.end(), p2.begin()))
76       throw test_fs::filesystem_error(
77 	  "!equal(begin1, end1, begin2)", p1, p2,
78 	  std::make_error_code(std::errc::invalid_argument) );
79 
80   }
81 
82   const std::string test_paths[] = {
83     "", "/", "//", "/.", "/./", "/a", "/a/", "/a//", "/a/b/c/d", "/a//b",
84     "a", "a/b", "a/b/", "a/b/c", "a/b/c.d", "a/b/..", "a/b/c.", "a/b/.c"
85   };
86 
87   test_fs::path
root_path()88   root_path()
89   {
90 #if defined(__MINGW32__) || defined(__MINGW64__)
91     return L"c:/";
92 #else
93     return "/";
94 #endif
95   }
96 
97   // This is NOT supposed to be a secure way to get a unique name!
98   // We just need a path that doesn't exist for testing purposes.
99   test_fs::path
100   nonexistent_path(std::string file = __builtin_FILE())
101   {
102     // Include the caller's filename to help identify tests that fail to
103     // clean up the files they create.
104     // Remove .cc extension:
105     if (file.length() > 3 && file.compare(file.length() - 3, 3, ".cc") == 0)
106       file.resize(file.length() - 3);
107     // And directory:
108     auto pos = file.find_last_of("/\\");
109     if (pos != file.npos)
110       file.erase(0, pos+1);
111 
112     test_fs::path p;
113 #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L
114     char tmp[] = "filesystem-test.XXXXXX";
115     int fd = ::mkstemp(tmp);
116     if (fd == -1)
117       throw test_fs::filesystem_error("mkstemp failed",
118 	  std::error_code(errno, std::generic_category()));
119     ::unlink(tmp);
120     ::close(fd);
121     if (!file.empty())
122       file.insert(0, 1, '-');
123     file.insert(0, tmp);
124     p = file;
125 #else
126     if (file.length() > 64)
127       file.resize(64);
128     char buf[128];
129     static unsigned counter = std::random_device{}();
130 #if _GLIBCXX_USE_C99_STDIO
131     std::snprintf(buf, 128,
132 #else
133     std::sprintf(buf,
134 #endif
135       "filesystem-test.%u.%lu-%s", counter++, (unsigned long) ::getpid(),
136       file.c_str());
137     p = buf;
138 #endif
139     return p;
140   }
141 
142   // RAII helper to remove a file on scope exit.
143   struct scoped_file
144   {
145     using path_type = test_fs::path;
146 
147     enum adopt_file_t { adopt_file };
148 
149     explicit
pathscoped_file150     scoped_file(const path_type& p = nonexistent_path()) : path(p)
151     { std::ofstream{p.c_str()}; }
152 
scoped_filescoped_file153     scoped_file(path_type p, adopt_file_t) : path(p) { }
154 
~scoped_filescoped_file155     ~scoped_file() { if (!path.empty()) remove(path); }
156 
157     scoped_file(scoped_file&&) = default;
158     scoped_file& operator=(scoped_file&&) = default;
159 
160     path_type path;
161   };
162 
163 } // namespace __gnu_test
164 #endif
165