Branch data Line data Source code
1 : : #ifdef RUN_TESTS
2 : : # include <gtest/gtest.h>
3 : : #endif
4 : :
5 : : /** @file file/share.cc
6 : : * @brief Facilities for finding share-files (files supplied with program)
7 : : * and cache-files (files generated by program).
8 : : */
9 : :
10 : : #include <filesystem>
11 : : #include <optional>
12 : : #include <fstream>
13 : : #include <string_view>
14 : : #include <string>
15 : : #include <vector>
16 : :
17 : : #include <unistd.h> // For getuid()
18 : :
19 : : #include "share.hh"
20 : :
21 : : using std::filesystem::path;
22 : :
23 : : static const char* arg0 = nullptr;
24 : : static path arg0_path;
25 : :
26 : 3 : void SaveArg0(const char* a)
27 : : {
28 : 3 : arg0 = a;
29 : :
30 : 3 : path p = arg0;
31 [ + - + - ]: 9 : arg0_path = std::filesystem::canonical(p).parent_path();
32 : 3 : }
33 : :
34 : : /*
35 : : std::pair<path, std::filesystem::file_status>
36 : : FindShareFile(std::string_view file_to_find, std::initializer_list<std::string_view> extra_paths)
37 : : {
38 : : return FindShareFile( std::filesystem::path(file_to_find), std::move(extra_paths));
39 : : }
40 : : */
41 : :
42 : : template<typename T, typename V>
43 : 93 : static std::pair<path, std::filesystem::file_status> FindFileCommon(
44 : : const std::filesystem::path& file_to_find,
45 : : V&& components,
46 : : T&& try_path_fun)
47 : : {
48 : 93 : path first;
49 : 93 : std::vector<path> specifics;
50 : 93 : bool has_first = false;
51 [ + + ]: 606 : for(auto s: components)
52 : : {
53 [ + + ]: 601 : if(!has_first)
54 : : {
55 [ + - ]: 137 : first = s;
56 : : has_first = true;
57 : : }
58 [ + + ]: 464 : else if(!s.empty())
59 [ + - ]: 327 : specifics.emplace_back(s);
60 : : else
61 : : {
62 [ + + ]: 137 : if(!first.empty())
63 : : {
64 : : try {
65 [ + - + - : 246 : auto temp = try_path_fun(first, specifics);
+ + ]
66 [ + + ]: 123 : if(temp.has_value())
67 : : {
68 [ + - ]: 88 : return std::move(temp).value();
69 : : }
70 [ - - ]: 123 : } catch(...)
71 : : {
72 : : }
73 : : }
74 : 49 : has_first = false;
75 : 49 : specifics.clear();
76 : : }
77 : : }
78 [ + - ]: 10 : return { file_to_find, std::filesystem::status(file_to_find) };
79 : 186 : }
80 : :
81 : : std::pair<path, std::filesystem::file_status>
82 : 73 : FindShareFile(const std::filesystem::path& file_to_find,
83 : : std::initializer_list<std::string_view> extra_paths)
84 : : {
85 : 73 : std::vector<std::pair<path, std::vector<path>>> tries;
86 : :
87 : 73 : const char* file = file_to_find.c_str();
88 [ + + ]: 73 : const char* homedir = std::getenv("HOME"); if(!homedir) homedir="";
89 [ + + ]: 73 : const char* user = std::getenv("USER"); if(!user) user="";
90 : 73 : const char* name = "that_terminal";
91 [ + - ]: 73 : std::vector<std::string_view> components
92 : : {
93 : : arg0_path.c_str(), "share", file, "",
94 : : homedir, ".local/share", name, file, "",
95 : : "/home", user, ".local/share", name, file, "",
96 : : "/usr/local/share", name, file, "",
97 : : "/usr/share", name, file, "",
98 [ + - ]: 73 : };
99 [ + + ]: 86 : for(auto s: extra_paths)
100 [ + - ]: 13 : components.insert(components.end(), std::initializer_list<std::string_view>{s, file, ""});
101 : :
102 : 73 : using o = std::optional<std::pair<path, std::filesystem::file_status>>;
103 : 73 : return FindFileCommon(file_to_find, std::move(components),
104 : 95 : [](path test, const std::vector<path>& com) -> o
105 : : {
106 : : /* Append all directory components */
107 [ + + ]: 292 : for(auto i = com.begin(); i != com.end(); ++i)
108 : 197 : test /= *i;
109 : :
110 : 95 : auto status = std::filesystem::status(test);
111 [ - + ]: 122 : if(std::filesystem::exists(status))
112 : 68 : return o{ std::in_place_t{}, std::move(test), std::move(status) };
113 : 27 : return {};
114 [ + - ]: 146 : });
115 : 73 : }
116 : :
117 : : std::pair<path, std::filesystem::file_status>
118 : 20 : FindCacheFile(const std::filesystem::path& file_to_find, bool is_file)
119 : : {
120 : 20 : const char* file = file_to_find.c_str();
121 [ + + ]: 20 : const char* homedir = std::getenv("HOME"); if(!homedir) homedir="";
122 [ + + ]: 20 : const char* user = std::getenv("USER"); if(!user) user=".";
123 : 20 : const char* name = "that_terminal";
124 : 20 : std::string uid = std::to_string(getuid());
125 [ + - + - : 40 : std::string pu = name + std::string("-") + uid;
+ - ]
126 [ + - ]: 20 : auto temp = std::filesystem::temp_directory_path();
127 : :
128 [ + - ]: 20 : std::array<std::string_view,23> components
129 : : {
130 : : homedir, ".cache", name, file, "",
131 : : "/home", user, ".cache", name, file, "",
132 : : "/run/user", uid.c_str(), file, "",
133 : : "/run", uid.c_str(), file, "",
134 : : temp.c_str(), pu.c_str(), file, ""
135 [ + - ]: 20 : };
136 : :
137 : 20 : using o = std::optional<std::pair<path, std::filesystem::file_status>>;
138 : 20 : return FindFileCommon(file_to_find, std::move(components),
139 : 136 : [is_file](path test, const std::vector<path>& com) -> o
140 : : {
141 : 28 : std::error_code err;
142 : : /* Append all directory components */
143 [ + + ]: 116 : for(auto i = com.begin(); i != com.end(); ++i)
144 [ + + + + ]: 88 : if(!is_file || std::next(i) != com.end())
145 : 71 : test /= *i;
146 : :
147 : 28 : std::filesystem::create_directories(test, err);
148 : 28 : auto status = std::filesystem::status(test);
149 [ + + ]: 28 : if(!err && std::filesystem::exists(status))
150 : : {
151 [ + + - + ]: 20 : if(!is_file && std::filesystem::is_directory(status))
152 : 6 : return o{ std::in_place_t{}, std::move(test), std::move(status) };
153 [ + - ]: 14 : if(is_file)
154 : : {
155 : : /* Test if the file exists in this directory */
156 : 14 : auto test2 = test / *std::next(com.begin(), com.size()-1);
157 [ + - ]: 14 : status = std::filesystem::status(test2);
158 [ + - + + ]: 28 : if(std::filesystem::exists(status) && !std::filesystem::is_directory(status))
159 : 6 : return o{ std::in_place_t{}, std::move(test2), std::move(status) };
160 : :
161 : : /* Test if the file could be created in this directory */
162 [ + - ]: 8 : std::ofstream testfile(test2);
163 [ + - ]: 8 : auto status2 = std::filesystem::status(test2);
164 : : /* Return the old status, before it existed */
165 [ + - ]: 8 : if(std::filesystem::exists(status2))
166 : 8 : return o{ std::in_place_t{}, std::move(test2), std::move(status) };
167 : 22 : }
168 : : }
169 : 28 : return {};
170 [ + - ]: 20 : });
171 : 20 : }
172 : :
173 : : #undef try_path
174 : :
175 : : #ifdef RUN_TESTS
176 : 3 : static void RunTests()
177 : : {
178 : : // Reconstruct a plausible argv[0].
179 : 3 : char Buf[4096]{};
180 : 3 : getcwd(Buf, sizeof(Buf)-1);
181 : 3 : strcat(Buf, "/test");
182 : 3 : SaveArg0(Buf);
183 : :
184 : : // These tests do not actually test anything,
185 : : // except that the program does not crash.
186 : : // They exist for coverage purposes.
187 : :
188 [ + - ]: 6 : FindShareFile("unicode/UnicodeData.txt", {"/usr/local/share", "/usr/share"});
189 [ + - ]: 6 : FindShareFile("notfound.txt", {"/abc"});
190 [ + - ]: 6 : FindShareFile("notfound.txt", {});
191 [ + - ]: 6 : FindCacheFile("deleteme.dat", false);
192 [ + - ]: 6 : FindCacheFile("similarities.dat", true);
193 [ + - ]: 6 : FindCacheFile("similarities.dat", false);
194 : :
195 [ + - - + ]: 6 : {auto [path, status] = FindCacheFile("deleteme.dat", true);
196 : 3 : if(std::filesystem::exists(status)) //LCOV_EXCL_BR_LINE
197 : : {
198 [ + - ]: 3 : std::filesystem::remove(path);
199 : 0 : }}
200 [ + - + - ]: 6 : {auto [path, status] = FindCacheFile("similarities.dat", true);
201 : 3 : if(std::filesystem::exists(status)) //LCOV_EXCL_BR_LINE
202 : : {
203 [ + - ]: 3 : std::filesystem::remove(path);
204 : 3 : }}
205 : 3 : }
206 : 3 : TEST(fileshare, sharetest)
207 : : {
208 : 1 : RunTests();
209 [ + - + - : 1 : static std::string home = std::string("HOME=") + getenv("HOME");
+ - + - ]
210 [ + - + - : 1 : static std::string user = std::string("USER=") + getenv("USER");
+ - + - ]
211 : 1 : putenv(const_cast<char*>("HOME")); RunTests();
212 : 1 : putenv(const_cast<char*>("USER")); RunTests();
213 : 1 : putenv(const_cast<char*>(home.c_str()));
214 : 1 : putenv(const_cast<char*>(user.c_str()));
215 : 1 : }
216 : : #endif
|