1 // Copyright 2020 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "aemu/base/files/PathUtils.h"
16
17 #include <string.h> // for size_t, strncmp
18 #include <iterator> // for reverse_iterator, operator!=
19 #include <numeric> // for accumulate
20 #include <type_traits> // for enable_if<>::type
21
22 #ifndef _WIN32
23 #include <unistd.h>
24 #endif
25
26 #ifdef _WIN32
27 #include "aemu/base/system/Win32UnicodeString.h"
28 #endif
29
sIsEmpty(const char * str)30 static inline bool sIsEmpty(const char* str) {
31 return !str || str[0] == '\0';
32 }
33
34 namespace android {
35 namespace base {
36
37 const char* const PathUtils::kExeNameSuffixes[kHostTypeCount] = {"", ".exe"};
38
39 const char* const PathUtils::kExeNameSuffix =
40 PathUtils::kExeNameSuffixes[PathUtils::HOST_TYPE];
41
toExecutableName(const char * baseName,HostType hostType)42 std::string PathUtils::toExecutableName(const char* baseName,
43 HostType hostType) {
44 return static_cast<std::string>(baseName).append(
45 kExeNameSuffixes[hostType]);
46 }
47
48 // static
isDirSeparator(int ch,HostType hostType)49 bool PathUtils::isDirSeparator(int ch, HostType hostType) {
50 return (ch == '/') || (hostType == HOST_WIN32 && ch == '\\');
51 }
52
53 // static
isPathSeparator(int ch,HostType hostType)54 bool PathUtils::isPathSeparator(int ch, HostType hostType) {
55 return (hostType == HOST_POSIX && ch == ':') ||
56 (hostType == HOST_WIN32 && ch == ';');
57 }
58
59 // static
rootPrefixSize(const std::string & path,HostType hostType)60 size_t PathUtils::rootPrefixSize(const std::string& path, HostType hostType) {
61 if (path.empty()) return 0;
62
63 if (hostType != HOST_WIN32)
64 return (path[0] == '/') ? 1U : 0U;
65
66 size_t result = 0;
67 if (path[1] == ':') {
68 int ch = path[0];
69 if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
70 result = 2U;
71 } else if (!strncmp(path.c_str(), "\\\\.\\", 4) ||
72 !strncmp(path.c_str(), "\\\\?\\", 4)) {
73 // UNC prefixes.
74 return 4U;
75 } else if (isDirSeparator(path[0], hostType)) {
76 result = 1;
77 if (isDirSeparator(path[1], hostType)) {
78 result = 2;
79 while (path[result] && !isDirSeparator(path[result], HOST_WIN32))
80 result++;
81 }
82 }
83 if (result && path[result] && isDirSeparator(path[result], HOST_WIN32))
84 result++;
85
86 return result;
87 }
88
89 // static
isAbsolute(const char * path,HostType hostType)90 bool PathUtils::isAbsolute(const char* path, HostType hostType) {
91 size_t prefixSize = rootPrefixSize(path, hostType);
92 if (!prefixSize) {
93 return false;
94 }
95 if (hostType != HOST_WIN32) {
96 return true;
97 }
98 return isDirSeparator(path[prefixSize - 1], HOST_WIN32);
99 }
100
101 // static
extension(const std::string & path,HostType hostType)102 std::string_view PathUtils::extension(const std::string& path,
103 HostType hostType) {
104 std::string_view tmp = path;
105 using riter = std::reverse_iterator<std::string_view::const_iterator>;
106
107 for (auto it = riter(tmp.end()), itEnd = riter(tmp.begin()); it != itEnd;
108 ++it) {
109 if (*it == '.') {
110 // reverse iterator stores a base+1, so decrement it when returning
111 // MSVC doesn't have string_view constructor with iterators
112 return std::string_view(&*std::prev(it.base()), (it - riter(tmp.end()) + 1));
113 }
114 if (isDirSeparator(*it, hostType)) {
115 // no extension here - we've found the end of file name already
116 break;
117 }
118 }
119
120 // either there's no dot in the whole path, or we found directory separator
121 // first - anyway, there's no extension in this name
122 return "";
123 }
124 // static
removeTrailingDirSeparator(const char * path,HostType hostType)125 std::string PathUtils::removeTrailingDirSeparator(const char* path,
126 HostType hostType) {
127 size_t pathLen = strlen(path);
128 // NOTE: Don't remove initial path separator for absolute paths.
129 while (pathLen > 1U && isDirSeparator(path[pathLen - 1U], hostType)) {
130 pathLen--;
131 }
132 return std::string(path, pathLen);
133 }
134
135 // static
addTrailingDirSeparator(const char * path,HostType hostType)136 std::string PathUtils::addTrailingDirSeparator(const char* path,
137 HostType hostType) {
138 std::string result = path;
139 if (result.size() > 0 && !isDirSeparator(result[result.size() - 1U])) {
140 result += getDirSeparator(hostType);
141 }
142 return result;
143 }
144
145 // static
split(const char * path,HostType hostType,std::string * dirName,std::string * baseName)146 bool PathUtils::split(const char* path,
147 HostType hostType,
148 std::string* dirName,
149 std::string* baseName) {
150 if (sIsEmpty(path)) {
151 return false;
152 }
153
154 // If there is a trailing directory separator, return an error.
155 size_t end = strlen(path);
156 if (isDirSeparator(path[end - 1U], hostType)) {
157 return false;
158 }
159
160 // Find last separator.
161 size_t prefixLen = rootPrefixSize(path, hostType);
162 size_t pos = end;
163 while (pos > prefixLen && !isDirSeparator(path[pos - 1U], hostType)) {
164 pos--;
165 }
166
167 // Handle common case.
168 if (pos > prefixLen) {
169 if (dirName) {
170 *dirName = std::string(path, pos);
171 }
172 if (baseName) {
173 *baseName = path + pos;
174 }
175 return true;
176 }
177
178 // If there is no directory separator, the path is a single file name.
179 if (dirName) {
180 if (!prefixLen) {
181 *dirName = ".";
182 } else {
183 *dirName = std::string(path, prefixLen);
184 }
185 }
186 if (baseName) {
187 *baseName = path + prefixLen;
188 }
189 return true;
190 }
191
192 // static
join(const std::string & path1,const std::string & path2,HostType hostType)193 std::string PathUtils::join(const std::string& path1,
194 const std::string& path2,
195 HostType hostType) {
196 if (path1.empty()) {
197 return path2;
198 }
199 if (path2.empty()) {
200 return path1;
201 }
202 if (isAbsolute(path2.c_str(), hostType)) {
203 return path2;
204 }
205 size_t prefixLen = rootPrefixSize(path1, hostType);
206 std::string result(path1);
207 size_t end = result.size();
208 if (end > prefixLen && !isDirSeparator(result[end - 1U], hostType)) {
209 result += getDirSeparator(hostType);
210 }
211 result += path2;
212 return result;
213 }
214
215 template <class String>
decomposeImpl(const String & path,PathUtils::HostType hostType)216 static std::vector<String> decomposeImpl(const String& path, PathUtils::HostType hostType) {
217 std::vector<String> result;
218 if (path.empty())
219 return result;
220
221 size_t prefixLen = PathUtils::rootPrefixSize(path, hostType);
222 auto it = path.begin();
223 if (prefixLen) {
224 result.emplace_back(it, it + prefixLen);
225 it += prefixLen;
226 }
227 for (;;) {
228 auto p = it;
229 while (p != path.end() && !PathUtils::isDirSeparator(*p, hostType))
230 p++;
231 if (p > it) {
232 result.emplace_back(it, p);
233 }
234 if (p == path.end()) {
235 break;
236 }
237 it = p + 1;
238 }
239 return result;
240 }
241
decompose(std::string && path,HostType hostType)242 std::vector<std::string> PathUtils::decompose(std::string&& path,
243 HostType hostType) {
244 return decomposeImpl<std::string>(path, hostType);
245 }
246
decompose(const std::string & path,HostType hostType)247 std::vector<std::string> PathUtils::decompose(const std::string& path,
248 HostType hostType) {
249 return decomposeImpl<std::string>(path, hostType);
250 }
251
252 template <class String>
recompose(const std::vector<String> & components,HostType hostType)253 std::string PathUtils::recompose(const std::vector<String>& components,
254 HostType hostType) {
255 if (components.empty()) {
256 return {};
257 }
258
259 const char dirSeparator = getDirSeparator(hostType);
260 std::string result;
261 // To reduce memory allocations, compute capacity before doing the
262 // real append.
263 const size_t capacity =
264 components.size() - 1 +
265 std::accumulate(components.begin(), components.end(), size_t(0),
266 [](size_t val, const String& next) {
267 return val + next.size();
268 });
269
270 result.reserve(capacity);
271 bool addSeparator = false;
272 for (size_t n = 0; n < components.size(); ++n) {
273 const auto& component = components[n];
274 if (addSeparator)
275 result += dirSeparator;
276 addSeparator = true;
277 if (n == 0) {
278 size_t prefixLen = rootPrefixSize(component, hostType);
279 if (prefixLen == component.size()) {
280 addSeparator = false;
281 }
282 }
283 result += component;
284 }
285 return result;
286 }
287
288 // static
recompose(const std::vector<std::string> & components,HostType hostType)289 std::string PathUtils::recompose(const std::vector<std::string>& components,
290 HostType hostType) {
291 return recompose<std::string>(components, hostType);
292 }
293
294 // static
295 template <class String>
simplifyComponents(std::vector<String> * components)296 void PathUtils::simplifyComponents(std::vector<String>* components) {
297 std::vector<String> stack;
298 for (auto& component : *components) {
299 if (component == ".") {
300 // Ignore any instance of '.' from the list.
301 continue;
302 }
303 if (component == "..") {
304 // Handling of '..' is specific: if there is a item on the
305 // stack that is not '..', then remove it, otherwise push
306 // the '..'.
307 if (!stack.empty() && stack.back() != "..") {
308 stack.pop_back();
309 } else {
310 stack.push_back(std::move(component));
311 }
312 continue;
313 }
314 // If not a '..', just push on the stack.
315 stack.push_back(std::move(component));
316 }
317 if (stack.empty()) {
318 stack.push_back(".");
319 }
320 components->swap(stack);
321 }
322
simplifyComponents(std::vector<std::string> * components)323 void PathUtils::simplifyComponents(std::vector<std::string>* components) {
324 simplifyComponents<std::string>(components);
325 }
326
327 // static
relativeTo(const std::string & base,const std::string & path,HostType hostType)328 std::string PathUtils::relativeTo(const std::string& base,
329 const std::string& path,
330 HostType hostType) {
331 auto baseDecomposed = decompose(base, hostType);
332 auto pathDecomposed = decompose(path, hostType);
333
334 if (baseDecomposed.size() > pathDecomposed.size())
335 return path;
336
337 for (size_t i = 0; i < baseDecomposed.size(); i++) {
338 if (baseDecomposed[i] != pathDecomposed[i])
339 return path;
340 }
341
342 std::string result =
343 recompose(std::vector<std::string>(
344 pathDecomposed.begin() + baseDecomposed.size(),
345 pathDecomposed.end()),
346 hostType);
347
348 return result;
349 }
350
move(const std::string & from,const std::string & to)351 bool PathUtils::move(const std::string& from, const std::string& to) {
352 // std::rename returns 0 on success.
353 if (std::rename(from.data(), to.data())) {
354 #ifdef _SUPPORT_FILESYSTEM
355 // Rename can fail if files are on different disks
356 if (std::filesystem::copy_file(from.data(), to.data())) {
357 std::filesystem::remove(from.data());
358 return true;
359 } else {
360 return false;
361 }
362 #else // _SUPPORT_FILESYSTEM
363 return false;
364 #endif // _SUPPORT_FILESYSTEM
365 }
366 return true;
367 }
368
369 #ifdef _WIN32
370
371 // Return |path| as a Unicode string, while discarding trailing separators.
win32Path(const char * path)372 Win32UnicodeString win32Path(const char* path) {
373 Win32UnicodeString wpath(path);
374 // Get rid of trailing directory separators, Windows doesn't like them.
375 size_t size = wpath.size();
376 while (size > 0U &&
377 (wpath[size - 1U] == L'\\' || wpath[size - 1U] == L'/')) {
378 size--;
379 }
380 if (size < wpath.size()) {
381 wpath.resize(size);
382 }
383 return wpath;
384 }
385
386 /* access function */
387 #define F_OK 0 /* test for existence of file */
388 #define X_OK 0x01 /* test for execute or search permission */
389 #define W_OK 0x02 /* test for write permission */
390 #define R_OK 0x04 /* test for read permission */
391
GetWin32Mode(int mode)392 static int GetWin32Mode(int mode) {
393 // Convert |mode| to win32 permission bits.
394 int win32mode = 0x0;
395
396 if ((mode & R_OK) || (mode & X_OK)) {
397 win32mode |= 0x4;
398 }
399 if (mode & W_OK) {
400 win32mode |= 0x2;
401 }
402
403 return win32mode;
404 }
405
406 #endif
407
pathExists(const char * path)408 bool pathExists(const char* path) {
409 #ifdef _WIN32
410 return _waccess(win32Path(path).c_str(), GetWin32Mode(F_OK));
411 #else
412 return 0 == access(path, F_OK);
413 #endif
414 }
415
pj(const std::string & path1,const std::string & path2)416 std::string pj(const std::string& path1, const std::string& path2) {
417 return PathUtils::join(path1, path2);
418 }
419
pj(const std::vector<std::string> & paths)420 std::string pj(const std::vector<std::string>& paths) {
421 std::string res;
422
423 if (paths.size() == 0)
424 return "";
425
426 if (paths.size() == 1)
427 return paths[0];
428
429 res = paths[0];
430
431 for (size_t i = 1; i < paths.size(); i++) {
432 res = PathUtils::join(res, paths[i]);
433 }
434
435 return res;
436 }
437
addTrailingDirSeparator(const std::string & path,HostType hostType)438 std::string PathUtils::addTrailingDirSeparator(const std::string& path,
439 HostType hostType) {
440 std::string result = path;
441 if (result.size() > 0 && !isDirSeparator(result[result.size() - 1U])) {
442 result += getDirSeparator(hostType);
443 }
444 return result;
445 }
446
447 } // namespace base
448 } // namespace android
449