/src/CMake/Source/cmPathResolver.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | #include "cmPathResolver.h" |
4 | | |
5 | | #include <algorithm> |
6 | | #include <cerrno> |
7 | | #include <cstddef> |
8 | | #include <string> |
9 | | #include <utility> |
10 | | |
11 | | #include <cm/optional> |
12 | | #include <cm/string_view> |
13 | | #include <cmext/string_view> |
14 | | |
15 | | #ifdef _WIN32 |
16 | | # include <cctype> |
17 | | |
18 | | # include <windows.h> |
19 | | #endif |
20 | | |
21 | 0 | #define MAX_SYMBOLIC_LINKS 32 |
22 | | |
23 | | namespace cm { |
24 | | namespace PathResolver { |
25 | | |
26 | | namespace { |
27 | | |
28 | | namespace Options { |
29 | | |
30 | | enum class ActualCase |
31 | | { |
32 | | No, |
33 | | Yes, |
34 | | }; |
35 | | |
36 | | enum class Symlinks |
37 | | { |
38 | | None, |
39 | | Lazy, |
40 | | Eager, |
41 | | }; |
42 | | |
43 | | enum class Existence |
44 | | { |
45 | | Agnostic, |
46 | | Required, |
47 | | }; |
48 | | } |
49 | | |
50 | | enum class Root |
51 | | { |
52 | | None, |
53 | | POSIX, |
54 | | #ifdef _WIN32 |
55 | | Drive, |
56 | | Network, |
57 | | #endif |
58 | | }; |
59 | | |
60 | | struct Control |
61 | | { |
62 | | enum class Tag |
63 | | { |
64 | | Continue, |
65 | | Restart, |
66 | | Error, |
67 | | }; |
68 | | Tag tag; |
69 | | union |
70 | | { |
71 | | std::string::size_type slash; // data for Continue |
72 | | cmsys::Status error; // data for Error |
73 | | }; |
74 | | static Control Continue(std::string::size_type s) |
75 | 56 | { |
76 | 56 | Control c{ Tag::Continue }; |
77 | 56 | c.slash = s; |
78 | 56 | return c; |
79 | 56 | } |
80 | 0 | static Control Restart() { return Control{ Tag::Restart }; } |
81 | | static Control Error(cmsys::Status e) |
82 | 0 | { |
83 | 0 | Control c{ Tag::Error }; |
84 | 0 | c.error = e; |
85 | 0 | return c; |
86 | 0 | } |
87 | | |
88 | | private: |
89 | | Control(Tag t) |
90 | 56 | : tag(t) |
91 | 56 | { |
92 | 56 | } |
93 | | }; |
94 | | |
95 | | Root ClassifyRoot(cm::string_view p) |
96 | 11 | { |
97 | | #ifdef _WIN32 |
98 | | if (p.size() >= 2 && std::isalpha(p[0]) && p[1] == ':') { |
99 | | return Root::Drive; |
100 | | } |
101 | | if (p.size() >= 3 && p[0] == '/' && p[1] == '/' && p[2] != '/') { |
102 | | return Root::Network; |
103 | | } |
104 | | #endif |
105 | 11 | if (!p.empty() && p[0] == '/') { |
106 | 11 | return Root::POSIX; |
107 | 11 | } |
108 | 0 | return Root::None; |
109 | 11 | } |
110 | | |
111 | | class ImplBase |
112 | | { |
113 | | protected: |
114 | | ImplBase(System& os) |
115 | 11 | : OS(os) |
116 | 11 | { |
117 | 11 | } |
118 | | |
119 | | System& OS; |
120 | | std::string P; |
121 | | std::size_t SymlinkDepth = 0; |
122 | | |
123 | | #ifdef _WIN32 |
124 | | std::string GetWorkingDirectoryOnDrive(char letter); |
125 | | Control ResolveRootRelative(); |
126 | | #endif |
127 | | cm::optional<std::string> ReadSymlink(std::string const& path, |
128 | | cmsys::Status& status); |
129 | | Control ResolveSymlink(Root root, std::string::size_type slash, |
130 | | std::string::size_type next_slash, |
131 | | std::string symlink_target); |
132 | | }; |
133 | | |
134 | | template <class Policy> |
135 | | class Impl : public ImplBase |
136 | | { |
137 | | Control ResolveRelativePath(); |
138 | | Control ResolveRoot(Root root); |
139 | | Control ResolveComponent(Root root, std::string::size_type root_slash, |
140 | | std::string::size_type slash); |
141 | | Control ResolvePath(); |
142 | | |
143 | | public: |
144 | | Impl(System& os) |
145 | 11 | : ImplBase(os) |
146 | 11 | { |
147 | 11 | } cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::LogicalPath>::Impl(cm::PathResolver::System&) Line | Count | Source | 145 | 11 | : ImplBase(os) | 146 | 11 | { | 147 | 11 | } |
Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::RealPath>::Impl(cm::PathResolver::System&) Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::CasePath>::Impl(cm::PathResolver::System&) Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::NaivePath>::Impl(cm::PathResolver::System&) |
148 | | cmsys::Status Resolve(std::string in, std::string& out); |
149 | | }; |
150 | | |
151 | | template <class Policy> |
152 | | Control Impl<Policy>::ResolveRelativePath() |
153 | 0 | { |
154 | | // This is a relative path. Convert it to absolute and restart. |
155 | 0 | std::string p = this->OS.GetWorkingDirectory(); |
156 | 0 | std::replace(p.begin(), p.end(), '\\', '/'); |
157 | 0 | if (ClassifyRoot(p) == Root::None) { |
158 | 0 | p.insert(0, 1, '/'); |
159 | 0 | } |
160 | 0 | if (p.back() != '/') { |
161 | 0 | p.push_back('/'); |
162 | 0 | } |
163 | 0 | P.insert(0, p); |
164 | 0 | return Control::Restart(); |
165 | 0 | } Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::LogicalPath>::ResolveRelativePath() Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::RealPath>::ResolveRelativePath() Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::CasePath>::ResolveRelativePath() Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::NaivePath>::ResolveRelativePath() |
166 | | |
167 | | #ifdef _WIN32 |
168 | | std::string ImplBase::GetWorkingDirectoryOnDrive(char letter) |
169 | | { |
170 | | // Use the drive's working directory, if any. |
171 | | std::string d = this->OS.GetWorkingDirectoryOnDrive(letter); |
172 | | std::replace(d.begin(), d.end(), '\\', '/'); |
173 | | if (d.size() >= 3 && |
174 | | std::toupper(static_cast<unsigned char>(d[0])) == |
175 | | std::toupper(static_cast<unsigned char>(letter)) && |
176 | | d[1] == ':' && d[2] == '/') { |
177 | | d[0] = letter; |
178 | | d.push_back('/'); |
179 | | return d; |
180 | | } |
181 | | |
182 | | // Use the current working directory if the drive matches. |
183 | | d = this->OS.GetWorkingDirectory(); |
184 | | if (d.size() >= 3 && |
185 | | std::toupper(static_cast<unsigned char>(d[0])) == |
186 | | std::toupper(static_cast<unsigned char>(letter)) && |
187 | | d[1] == ':' && d[2] == '/') { |
188 | | d[0] = letter; |
189 | | d.push_back('/'); |
190 | | return d; |
191 | | } |
192 | | |
193 | | // Fall back to the root directory on the drive. |
194 | | d = "_:/"; |
195 | | d[0] = letter; |
196 | | return d; |
197 | | } |
198 | | |
199 | | Control ImplBase::ResolveRootRelative() |
200 | | { |
201 | | // This is a root-relative path. Resolve the root drive and restart. |
202 | | P.replace(0, 2, this->GetWorkingDirectoryOnDrive(P[0])); |
203 | | return Control::Restart(); |
204 | | } |
205 | | #endif |
206 | | |
207 | | cm::optional<std::string> ImplBase::ReadSymlink(std::string const& path, |
208 | | cmsys::Status& status) |
209 | 0 | { |
210 | 0 | cm::optional<std::string> result; |
211 | 0 | std::string target; |
212 | 0 | status = this->OS.ReadSymlink(path, target); |
213 | 0 | if (status && ++this->SymlinkDepth >= MAX_SYMBOLIC_LINKS) { |
214 | 0 | status = cmsys::Status::POSIX(ELOOP); |
215 | 0 | } |
216 | 0 | if (status) { |
217 | 0 | if (!target.empty()) { |
218 | 0 | result = std::move(target); |
219 | 0 | } |
220 | 0 | } else if (status.GetPOSIX() == EINVAL |
221 | | #ifdef _WIN32 |
222 | | || status.GetWindows() == ERROR_NOT_A_REPARSE_POINT |
223 | | #endif |
224 | 0 | ) { |
225 | | // The path was not a symlink. |
226 | 0 | status = cmsys::Status::Success(); |
227 | 0 | } |
228 | 0 | return result; |
229 | 0 | } |
230 | | |
231 | | Control ImplBase::ResolveSymlink(Root root, std::string::size_type slash, |
232 | | std::string::size_type next_slash, |
233 | | std::string symlink_target) |
234 | 0 | { |
235 | 0 | std::replace(symlink_target.begin(), symlink_target.end(), '\\', '/'); |
236 | 0 | Root const symlink_target_root = ClassifyRoot(symlink_target); |
237 | 0 | if (symlink_target_root == Root::None) { |
238 | | // This is a symlink to a relative path. |
239 | | // Resolve the symlink, while preserving the leading and |
240 | | // trailing (if any) slash: |
241 | | // "*/link/" => "*/dest/" |
242 | | // ^slash ^slash |
243 | 0 | P.replace(slash + 1, next_slash - slash - 1, symlink_target); |
244 | 0 | return Control::Continue(slash); |
245 | 0 | } |
246 | | |
247 | | #ifdef _WIN32 |
248 | | if (root == Root::Drive && symlink_target_root == Root::POSIX) { |
249 | | // This is a symlink to a POSIX absolute path, |
250 | | // but the current path is on a drive letter. Resolve the |
251 | | // symlink while preserving the drive letter, and start over: |
252 | | // "C:/*/link/" => "C:/dest/" |
253 | | // ^slash (restart) |
254 | | P.replace(2, next_slash - 2, symlink_target); |
255 | | return Control::Restart(); |
256 | | } |
257 | | #else |
258 | 0 | static_cast<void>(root); |
259 | 0 | #endif |
260 | | |
261 | | // This is a symlink to an absolute path. |
262 | | // Resolve it and start over: |
263 | | // "*/link/" => "/dest/" |
264 | | // ^slash (restart) |
265 | 0 | P.replace(0, next_slash, symlink_target); |
266 | 0 | return Control::Restart(); |
267 | 0 | } |
268 | | |
269 | | template <class Policy> |
270 | | Control Impl<Policy>::ResolveRoot(Root root) |
271 | 11 | { |
272 | 11 | if (root == Root::None) { |
273 | 0 | return this->ResolveRelativePath(); |
274 | 0 | } |
275 | | |
276 | | // POSIX absolute paths always start with a '/'. |
277 | 11 | std::string::size_type root_slash = 0; |
278 | | |
279 | | #ifdef _WIN32 |
280 | | if (root == Root::Drive) { |
281 | | if (P.size() == 2 || P[2] != '/') { |
282 | | return this->ResolveRootRelative(); |
283 | | } |
284 | | |
285 | | if (Policy::ActualCase == Options::ActualCase::Yes) { |
286 | | // Normalize the drive letter to upper-case. |
287 | | P[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(P[0]))); |
288 | | } |
289 | | |
290 | | // The root is a drive letter. The root '/' immediately follows. |
291 | | root_slash = 2; |
292 | | } else if (root == Root::Network) { |
293 | | // The root is a network name. Find the root '/' after it. |
294 | | root_slash = P.find('/', 2); |
295 | | if (root_slash == std::string::npos) { |
296 | | root_slash = P.size(); |
297 | | P.push_back('/'); |
298 | | } |
299 | | } |
300 | | #endif |
301 | | |
302 | 11 | if (Policy::Existence == Options::Existence::Required |
303 | | #ifdef _WIN32 |
304 | | && root != Root::Network |
305 | | #endif |
306 | 11 | ) { |
307 | 0 | std::string path = P.substr(0, root_slash + 1); |
308 | 0 | if (!this->OS.PathExists(path)) { |
309 | 0 | P = std::move(path); |
310 | 0 | return Control::Error(cmsys::Status::POSIX(ENOENT)); |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | 11 | return Control::Continue(root_slash); |
315 | 11 | } cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::LogicalPath>::ResolveRoot(cm::PathResolver::(anonymous namespace)::Root) Line | Count | Source | 271 | 11 | { | 272 | 11 | if (root == Root::None) { | 273 | 0 | return this->ResolveRelativePath(); | 274 | 0 | } | 275 | | | 276 | | // POSIX absolute paths always start with a '/'. | 277 | 11 | std::string::size_type root_slash = 0; | 278 | | | 279 | | #ifdef _WIN32 | 280 | | if (root == Root::Drive) { | 281 | | if (P.size() == 2 || P[2] != '/') { | 282 | | return this->ResolveRootRelative(); | 283 | | } | 284 | | | 285 | | if (Policy::ActualCase == Options::ActualCase::Yes) { | 286 | | // Normalize the drive letter to upper-case. | 287 | | P[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(P[0]))); | 288 | | } | 289 | | | 290 | | // The root is a drive letter. The root '/' immediately follows. | 291 | | root_slash = 2; | 292 | | } else if (root == Root::Network) { | 293 | | // The root is a network name. Find the root '/' after it. | 294 | | root_slash = P.find('/', 2); | 295 | | if (root_slash == std::string::npos) { | 296 | | root_slash = P.size(); | 297 | | P.push_back('/'); | 298 | | } | 299 | | } | 300 | | #endif | 301 | | | 302 | 11 | if (Policy::Existence == Options::Existence::Required | 303 | | #ifdef _WIN32 | 304 | | && root != Root::Network | 305 | | #endif | 306 | 11 | ) { | 307 | 0 | std::string path = P.substr(0, root_slash + 1); | 308 | 0 | if (!this->OS.PathExists(path)) { | 309 | 0 | P = std::move(path); | 310 | 0 | return Control::Error(cmsys::Status::POSIX(ENOENT)); | 311 | 0 | } | 312 | 0 | } | 313 | | | 314 | 11 | return Control::Continue(root_slash); | 315 | 11 | } |
Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::RealPath>::ResolveRoot(cm::PathResolver::(anonymous namespace)::Root) Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::CasePath>::ResolveRoot(cm::PathResolver::(anonymous namespace)::Root) Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::NaivePath>::ResolveRoot(cm::PathResolver::(anonymous namespace)::Root) |
316 | | |
317 | | template <class Policy> |
318 | | Control Impl<Policy>::ResolveComponent(Root root, |
319 | | std::string::size_type root_slash, |
320 | | std::string::size_type slash) |
321 | 34 | { |
322 | | // Look for the '/' or end-of-input that ends this component. |
323 | | // The sample paths in comments below show the trailing slash |
324 | | // even if it is actually beyond the end of the path. |
325 | 34 | std::string::size_type next_slash = P.find('/', slash + 1); |
326 | 34 | if (next_slash == std::string::npos) { |
327 | 11 | next_slash = P.size(); |
328 | 11 | } |
329 | 34 | cm::string_view c = |
330 | 34 | cm::string_view(P).substr(slash + 1, next_slash - (slash + 1)); |
331 | | |
332 | 34 | if (slash == root_slash) { |
333 | 11 | if (c.empty() || c == "."_s || c == ".."_s) { |
334 | | // This is an empty, '.', or '..' component at the root. |
335 | | // Drop the component and its trailing slash, if any, |
336 | | // while preserving the root slash: |
337 | | // "//" => "/" |
338 | | // "/./" => "/" |
339 | | // "/../" => "/" |
340 | | // ^slash ^slash |
341 | 0 | P.erase(slash + 1, next_slash - slash); |
342 | 0 | return Control::Continue(slash); |
343 | 0 | } |
344 | 23 | } else { |
345 | 23 | if (c.empty() || c == "."_s) { |
346 | | // This is an empty or '.' component not at the root. |
347 | | // Drop the component and its leading slash: |
348 | | // "*//" => "*/" |
349 | | // "*/./" => "*/" |
350 | | // ^slash ^slash |
351 | 0 | P.erase(slash, next_slash - slash); |
352 | 0 | return Control::Continue(slash); |
353 | 0 | } |
354 | | |
355 | 23 | if (c == ".."_s) { |
356 | | // This is a '..' component not at the root. |
357 | | // Rewind to the previous component: |
358 | | // "*/prev/../" => "*/prev/../" |
359 | | // ^slash ^slash |
360 | 0 | next_slash = slash; |
361 | 0 | slash = P.rfind('/', slash - 1); |
362 | |
|
363 | 0 | if (Policy::Symlinks == Options::Symlinks::Lazy) { |
364 | 0 | cmsys::Status status; |
365 | 0 | std::string path = P.substr(0, next_slash); |
366 | 0 | if (cm::optional<std::string> maybe_symlink_target = |
367 | 0 | this->ReadSymlink(path, status)) { |
368 | 0 | return this->ResolveSymlink(root, slash, next_slash, |
369 | 0 | std::move(*maybe_symlink_target)); |
370 | 0 | } |
371 | 0 | if (!status && Policy::Existence == Options::Existence::Required) { |
372 | 0 | P = std::move(path); |
373 | 0 | return Control::Error(status); |
374 | 0 | } |
375 | 0 | } |
376 | | |
377 | | // This is not a symlink. |
378 | | // Drop the component, the following '..', and its trailing slash, |
379 | | // if any, while preserving the (possibly root) leading slash: |
380 | | // "*/dir/../" => "*/" |
381 | | // ^slash ^slash |
382 | 0 | P.erase(slash + 1, next_slash + 3 - slash); |
383 | 0 | return Control::Continue(slash); |
384 | 0 | } |
385 | 23 | } |
386 | | |
387 | | // This is a named component. |
388 | | |
389 | 34 | if (Policy::Symlinks == Options::Symlinks::Eager) { |
390 | 0 | cmsys::Status status; |
391 | 0 | std::string path = P.substr(0, next_slash); |
392 | 0 | if (cm::optional<std::string> maybe_symlink_target = |
393 | 0 | this->ReadSymlink(path, status)) { |
394 | 0 | return this->ResolveSymlink(root, slash, next_slash, |
395 | 0 | std::move(*maybe_symlink_target)); |
396 | 0 | } |
397 | 0 | if (!status && Policy::Existence == Options::Existence::Required) { |
398 | 0 | P = std::move(path); |
399 | 0 | return Control::Error(status); |
400 | 0 | } |
401 | 0 | } |
402 | | |
403 | | #if defined(_WIN32) || defined(__APPLE__) |
404 | | bool exists = false; |
405 | | if (Policy::ActualCase == Options::ActualCase::Yes) { |
406 | | std::string name; |
407 | | std::string path = P.substr(0, next_slash); |
408 | | if (cmsys::Status status = this->OS.ReadName(path, name)) { |
409 | | exists = true; |
410 | | if (!name.empty()) { |
411 | | // Rename this component: |
412 | | // "*/name/" => "*/Name/" |
413 | | // ^slash ^slash |
414 | | P.replace(slash + 1, next_slash - slash - 1, name); |
415 | | next_slash = slash + 1 + name.length(); |
416 | | } |
417 | | } else if (Policy::Existence == Options::Existence::Required) { |
418 | | P = std::move(path); |
419 | | return Control::Error(status); |
420 | | } |
421 | | } |
422 | | #endif |
423 | | |
424 | 34 | if (Policy::Existence == Options::Existence::Required |
425 | | #if defined(_WIN32) || defined(__APPLE__) |
426 | | && !exists |
427 | | #endif |
428 | 34 | ) { |
429 | 0 | std::string path = P.substr(0, next_slash); |
430 | 0 | if (!this->OS.PathExists(path)) { |
431 | 0 | P = std::move(path); |
432 | 0 | return Control::Error(cmsys::Status::POSIX(ENOENT)); |
433 | 0 | } |
434 | 0 | } |
435 | | |
436 | | // Keep this component: |
437 | | // "*/name/" => "*/name/" |
438 | | // ^slash ^slash |
439 | 34 | return Control::Continue(next_slash); |
440 | 34 | } cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::LogicalPath>::ResolveComponent(cm::PathResolver::(anonymous namespace)::Root, unsigned long, unsigned long) Line | Count | Source | 321 | 34 | { | 322 | | // Look for the '/' or end-of-input that ends this component. | 323 | | // The sample paths in comments below show the trailing slash | 324 | | // even if it is actually beyond the end of the path. | 325 | 34 | std::string::size_type next_slash = P.find('/', slash + 1); | 326 | 34 | if (next_slash == std::string::npos) { | 327 | 11 | next_slash = P.size(); | 328 | 11 | } | 329 | 34 | cm::string_view c = | 330 | 34 | cm::string_view(P).substr(slash + 1, next_slash - (slash + 1)); | 331 | | | 332 | 34 | if (slash == root_slash) { | 333 | 11 | if (c.empty() || c == "."_s || c == ".."_s) { | 334 | | // This is an empty, '.', or '..' component at the root. | 335 | | // Drop the component and its trailing slash, if any, | 336 | | // while preserving the root slash: | 337 | | // "//" => "/" | 338 | | // "/./" => "/" | 339 | | // "/../" => "/" | 340 | | // ^slash ^slash | 341 | 0 | P.erase(slash + 1, next_slash - slash); | 342 | 0 | return Control::Continue(slash); | 343 | 0 | } | 344 | 23 | } else { | 345 | 23 | if (c.empty() || c == "."_s) { | 346 | | // This is an empty or '.' component not at the root. | 347 | | // Drop the component and its leading slash: | 348 | | // "*//" => "*/" | 349 | | // "*/./" => "*/" | 350 | | // ^slash ^slash | 351 | 0 | P.erase(slash, next_slash - slash); | 352 | 0 | return Control::Continue(slash); | 353 | 0 | } | 354 | | | 355 | 23 | if (c == ".."_s) { | 356 | | // This is a '..' component not at the root. | 357 | | // Rewind to the previous component: | 358 | | // "*/prev/../" => "*/prev/../" | 359 | | // ^slash ^slash | 360 | 0 | next_slash = slash; | 361 | 0 | slash = P.rfind('/', slash - 1); | 362 | |
| 363 | 0 | if (Policy::Symlinks == Options::Symlinks::Lazy) { | 364 | 0 | cmsys::Status status; | 365 | 0 | std::string path = P.substr(0, next_slash); | 366 | 0 | if (cm::optional<std::string> maybe_symlink_target = | 367 | 0 | this->ReadSymlink(path, status)) { | 368 | 0 | return this->ResolveSymlink(root, slash, next_slash, | 369 | 0 | std::move(*maybe_symlink_target)); | 370 | 0 | } | 371 | 0 | if (!status && Policy::Existence == Options::Existence::Required) { | 372 | 0 | P = std::move(path); | 373 | 0 | return Control::Error(status); | 374 | 0 | } | 375 | 0 | } | 376 | | | 377 | | // This is not a symlink. | 378 | | // Drop the component, the following '..', and its trailing slash, | 379 | | // if any, while preserving the (possibly root) leading slash: | 380 | | // "*/dir/../" => "*/" | 381 | | // ^slash ^slash | 382 | 0 | P.erase(slash + 1, next_slash + 3 - slash); | 383 | 0 | return Control::Continue(slash); | 384 | 0 | } | 385 | 23 | } | 386 | | | 387 | | // This is a named component. | 388 | | | 389 | 34 | if (Policy::Symlinks == Options::Symlinks::Eager) { | 390 | 0 | cmsys::Status status; | 391 | 0 | std::string path = P.substr(0, next_slash); | 392 | 0 | if (cm::optional<std::string> maybe_symlink_target = | 393 | 0 | this->ReadSymlink(path, status)) { | 394 | 0 | return this->ResolveSymlink(root, slash, next_slash, | 395 | 0 | std::move(*maybe_symlink_target)); | 396 | 0 | } | 397 | 0 | if (!status && Policy::Existence == Options::Existence::Required) { | 398 | 0 | P = std::move(path); | 399 | 0 | return Control::Error(status); | 400 | 0 | } | 401 | 0 | } | 402 | | | 403 | | #if defined(_WIN32) || defined(__APPLE__) | 404 | | bool exists = false; | 405 | | if (Policy::ActualCase == Options::ActualCase::Yes) { | 406 | | std::string name; | 407 | | std::string path = P.substr(0, next_slash); | 408 | | if (cmsys::Status status = this->OS.ReadName(path, name)) { | 409 | | exists = true; | 410 | | if (!name.empty()) { | 411 | | // Rename this component: | 412 | | // "*/name/" => "*/Name/" | 413 | | // ^slash ^slash | 414 | | P.replace(slash + 1, next_slash - slash - 1, name); | 415 | | next_slash = slash + 1 + name.length(); | 416 | | } | 417 | | } else if (Policy::Existence == Options::Existence::Required) { | 418 | | P = std::move(path); | 419 | | return Control::Error(status); | 420 | | } | 421 | | } | 422 | | #endif | 423 | | | 424 | 34 | if (Policy::Existence == Options::Existence::Required | 425 | | #if defined(_WIN32) || defined(__APPLE__) | 426 | | && !exists | 427 | | #endif | 428 | 34 | ) { | 429 | 0 | std::string path = P.substr(0, next_slash); | 430 | 0 | if (!this->OS.PathExists(path)) { | 431 | 0 | P = std::move(path); | 432 | 0 | return Control::Error(cmsys::Status::POSIX(ENOENT)); | 433 | 0 | } | 434 | 0 | } | 435 | | | 436 | | // Keep this component: | 437 | | // "*/name/" => "*/name/" | 438 | | // ^slash ^slash | 439 | 34 | return Control::Continue(next_slash); | 440 | 34 | } |
Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::RealPath>::ResolveComponent(cm::PathResolver::(anonymous namespace)::Root, unsigned long, unsigned long) Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::CasePath>::ResolveComponent(cm::PathResolver::(anonymous namespace)::Root, unsigned long, unsigned long) Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::NaivePath>::ResolveComponent(cm::PathResolver::(anonymous namespace)::Root, unsigned long, unsigned long) |
441 | | |
442 | | template <class Policy> |
443 | | Control Impl<Policy>::ResolvePath() |
444 | 11 | { |
445 | 11 | Root const root = ClassifyRoot(P); |
446 | | |
447 | | // Resolve the root component. It always ends in a slash. |
448 | 11 | Control control = this->ResolveRoot(root); |
449 | 11 | if (control.tag != Control::Tag::Continue) { |
450 | 0 | return control; |
451 | 0 | } |
452 | 11 | std::string::size_type const root_slash = control.slash; |
453 | | |
454 | | // Resolve later components. Every iteration that finishes |
455 | | // the loop body makes progress either by removing a component |
456 | | // or advancing the slash past it. |
457 | 11 | for (std::string::size_type slash = root_slash; |
458 | 45 | P.size() > root_slash + 1 && slash < P.size();) { |
459 | 34 | control = this->ResolveComponent(root, root_slash, slash); |
460 | 34 | if (control.tag != Control::Tag::Continue) { |
461 | 0 | return control; |
462 | 0 | } |
463 | 34 | slash = control.slash; |
464 | 34 | } |
465 | 11 | return Control::Continue(P.size()); |
466 | 11 | } cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::LogicalPath>::ResolvePath() Line | Count | Source | 444 | 11 | { | 445 | 11 | Root const root = ClassifyRoot(P); | 446 | | | 447 | | // Resolve the root component. It always ends in a slash. | 448 | 11 | Control control = this->ResolveRoot(root); | 449 | 11 | if (control.tag != Control::Tag::Continue) { | 450 | 0 | return control; | 451 | 0 | } | 452 | 11 | std::string::size_type const root_slash = control.slash; | 453 | | | 454 | | // Resolve later components. Every iteration that finishes | 455 | | // the loop body makes progress either by removing a component | 456 | | // or advancing the slash past it. | 457 | 11 | for (std::string::size_type slash = root_slash; | 458 | 45 | P.size() > root_slash + 1 && slash < P.size();) { | 459 | 34 | control = this->ResolveComponent(root, root_slash, slash); | 460 | 34 | if (control.tag != Control::Tag::Continue) { | 461 | 0 | return control; | 462 | 0 | } | 463 | 34 | slash = control.slash; | 464 | 34 | } | 465 | 11 | return Control::Continue(P.size()); | 466 | 11 | } |
Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::RealPath>::ResolvePath() Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::CasePath>::ResolvePath() Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::NaivePath>::ResolvePath() |
467 | | |
468 | | template <class Policy> |
469 | | cmsys::Status Impl<Policy>::Resolve(std::string in, std::string& out) |
470 | 11 | { |
471 | 11 | P = std::move(in); |
472 | 11 | std::replace(P.begin(), P.end(), '\\', '/'); |
473 | 11 | for (;;) { |
474 | 11 | Control control = this->ResolvePath(); |
475 | 11 | switch (control.tag) { |
476 | 11 | case Control::Tag::Continue: |
477 | 11 | out = std::move(P); |
478 | 11 | return cmsys::Status::Success(); |
479 | 0 | case Control::Tag::Restart: |
480 | 0 | continue; |
481 | 0 | case Control::Tag::Error: |
482 | 0 | out = std::move(P); |
483 | 0 | return control.error; |
484 | 11 | }; |
485 | 0 | } |
486 | 11 | } cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::LogicalPath>::Resolve(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) Line | Count | Source | 470 | 11 | { | 471 | 11 | P = std::move(in); | 472 | 11 | std::replace(P.begin(), P.end(), '\\', '/'); | 473 | 11 | for (;;) { | 474 | 11 | Control control = this->ResolvePath(); | 475 | 11 | switch (control.tag) { | 476 | 11 | case Control::Tag::Continue: | 477 | 11 | out = std::move(P); | 478 | 11 | return cmsys::Status::Success(); | 479 | 0 | case Control::Tag::Restart: | 480 | 0 | continue; | 481 | 0 | case Control::Tag::Error: | 482 | 0 | out = std::move(P); | 483 | 0 | return control.error; | 484 | 11 | }; | 485 | 0 | } | 486 | 11 | } |
Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::RealPath>::Resolve(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::CasePath>::Resolve(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) Unexecuted instantiation: cmPathResolver.cxx:cm::PathResolver::(anonymous namespace)::Impl<cm::PathResolver::Policies::NaivePath>::Resolve(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) |
487 | | |
488 | | } |
489 | | |
490 | | namespace Policies { |
491 | | struct NaivePath |
492 | | { |
493 | | #if defined(_WIN32) || defined(__APPLE__) |
494 | | static constexpr Options::ActualCase ActualCase = Options::ActualCase::No; |
495 | | #endif |
496 | | static constexpr Options::Symlinks Symlinks = Options::Symlinks::None; |
497 | | static constexpr Options::Existence Existence = Options::Existence::Agnostic; |
498 | | }; |
499 | | struct CasePath |
500 | | { |
501 | | #if defined(_WIN32) || defined(__APPLE__) |
502 | | static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes; |
503 | | #endif |
504 | | static constexpr Options::Symlinks Symlinks = Options::Symlinks::None; |
505 | | static constexpr Options::Existence Existence = Options::Existence::Agnostic; |
506 | | }; |
507 | | struct RealPath |
508 | | { |
509 | | #if defined(_WIN32) || defined(__APPLE__) |
510 | | static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes; |
511 | | #endif |
512 | | static constexpr Options::Symlinks Symlinks = Options::Symlinks::Eager; |
513 | | static constexpr Options::Existence Existence = Options::Existence::Required; |
514 | | }; |
515 | | struct LogicalPath |
516 | | { |
517 | | #if defined(_WIN32) || defined(__APPLE__) |
518 | | static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes; |
519 | | #endif |
520 | | static constexpr Options::Symlinks Symlinks = Options::Symlinks::Lazy; |
521 | | static constexpr Options::Existence Existence = Options::Existence::Agnostic; |
522 | | }; |
523 | | |
524 | | #if defined(__SUNPRO_CC) |
525 | | constexpr Options::Symlinks NaivePath::Symlinks; |
526 | | constexpr Options::Existence NaivePath::Existence; |
527 | | constexpr Options::Symlinks CasePath::Symlinks; |
528 | | constexpr Options::Existence CasePath::Existence; |
529 | | constexpr Options::Symlinks RealPath::Symlinks; |
530 | | constexpr Options::Existence RealPath::Existence; |
531 | | constexpr Options::Symlinks LogicalPath::Symlinks; |
532 | | constexpr Options::Existence LogicalPath::Existence; |
533 | | #endif |
534 | | } |
535 | | |
536 | | template <class Policy> |
537 | | Resolver<Policy>::Resolver(System& os) |
538 | 10 | : OS(os) |
539 | 10 | { |
540 | 10 | } cm::PathResolver::Resolver<cm::PathResolver::Policies::LogicalPath>::Resolver(cm::PathResolver::System&) Line | Count | Source | 538 | 10 | : OS(os) | 539 | 10 | { | 540 | 10 | } |
Unexecuted instantiation: cm::PathResolver::Resolver<cm::PathResolver::Policies::RealPath>::Resolver(cm::PathResolver::System&) Unexecuted instantiation: cm::PathResolver::Resolver<cm::PathResolver::Policies::CasePath>::Resolver(cm::PathResolver::System&) Unexecuted instantiation: cm::PathResolver::Resolver<cm::PathResolver::Policies::NaivePath>::Resolver(cm::PathResolver::System&) |
541 | | template <class Policy> |
542 | | cmsys::Status Resolver<Policy>::Resolve(std::string in, std::string& out) const |
543 | 11 | { |
544 | 11 | return Impl<Policy>(OS).Resolve(std::move(in), out); |
545 | 11 | } cm::PathResolver::Resolver<cm::PathResolver::Policies::LogicalPath>::Resolve(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) const Line | Count | Source | 543 | 11 | { | 544 | 11 | return Impl<Policy>(OS).Resolve(std::move(in), out); | 545 | 11 | } |
Unexecuted instantiation: cm::PathResolver::Resolver<cm::PathResolver::Policies::RealPath>::Resolve(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) const Unexecuted instantiation: cm::PathResolver::Resolver<cm::PathResolver::Policies::CasePath>::Resolve(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) const Unexecuted instantiation: cm::PathResolver::Resolver<cm::PathResolver::Policies::NaivePath>::Resolve(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) const |
546 | | |
547 | 10 | System::System() = default; |
548 | 0 | System::~System() = default; |
549 | | |
550 | | template class Resolver<Policies::LogicalPath>; |
551 | | template class Resolver<Policies::RealPath>; |
552 | | template class Resolver<Policies::CasePath>; |
553 | | template class Resolver<Policies::NaivePath>; |
554 | | |
555 | | } |
556 | | } |