226a5691 |
1 | /* Copyright (C) 2010-2020 The RetroArch team |
2 | * |
3 | * --------------------------------------------------------------------------------------- |
4 | * The following license statement only applies to this file (file_path.c). |
5 | * --------------------------------------------------------------------------------------- |
6 | * |
7 | * Permission is hereby granted, free of charge, |
8 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), |
9 | * to deal in the Software without restriction, including without limitation the rights to |
10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, |
11 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
12 | * |
13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
14 | * |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
21 | */ |
22 | |
23 | #include <stdio.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include <time.h> |
27 | #include <errno.h> |
28 | |
29 | #include <sys/stat.h> |
30 | |
31 | #include <boolean.h> |
32 | #include <file/file_path.h> |
33 | #include <retro_assert.h> |
34 | #include <string/stdstring.h> |
35 | #include <time/rtime.h> |
36 | |
37 | /* TODO: There are probably some unnecessary things on this huge include list now but I'm too afraid to touch it */ |
38 | #ifdef __APPLE__ |
39 | #include <CoreFoundation/CoreFoundation.h> |
40 | #endif |
41 | #ifdef __HAIKU__ |
42 | #include <kernel/image.h> |
43 | #endif |
44 | #ifndef __MACH__ |
45 | #include <compat/strl.h> |
46 | #include <compat/posix_string.h> |
47 | #endif |
48 | #include <retro_miscellaneous.h> |
49 | #include <encodings/utf.h> |
50 | |
51 | #if defined(_WIN32) |
52 | #ifdef _MSC_VER |
53 | #define setmode _setmode |
54 | #endif |
55 | #include <sys/stat.h> |
56 | #ifdef _XBOX |
57 | #include <xtl.h> |
58 | #define INVALID_FILE_ATTRIBUTES -1 |
59 | #else |
60 | #include <io.h> |
61 | #include <fcntl.h> |
62 | #include <direct.h> |
63 | #include <windows.h> |
64 | #if defined(_MSC_VER) && _MSC_VER <= 1200 |
65 | #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) |
66 | #endif |
67 | #endif |
68 | #elif defined(VITA) |
69 | #define SCE_ERROR_ERRNO_EEXIST 0x80010011 |
70 | #include <psp2/io/fcntl.h> |
71 | #include <psp2/io/dirent.h> |
72 | #include <psp2/io/stat.h> |
73 | #else |
74 | #include <sys/types.h> |
75 | #include <sys/stat.h> |
76 | #include <unistd.h> |
77 | #endif |
78 | |
79 | #if defined(PSP) |
80 | #include <pspkernel.h> |
81 | #endif |
82 | |
83 | #if defined(__CELLOS_LV2__) && !defined(__PSL1GHT__) |
84 | #include <cell/cell_fs.h> |
85 | #endif |
86 | |
87 | #if defined(VITA) |
88 | #define FIO_S_ISDIR SCE_S_ISDIR |
89 | #endif |
90 | |
91 | #if (defined(__CELLOS_LV2__) && !defined(__PSL1GHT__)) || defined(__QNX__) || defined(PSP) |
92 | #include <unistd.h> /* stat() is defined here */ |
93 | #endif |
94 | |
95 | #if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL) |
96 | #ifdef __WINRT__ |
97 | #include <uwp/uwp_func.h> |
98 | #endif |
99 | #endif |
100 | |
101 | /* Assume W-functions do not work below Win2K and Xbox platforms */ |
102 | #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX) |
103 | |
104 | #ifndef LEGACY_WIN32 |
105 | #define LEGACY_WIN32 |
106 | #endif |
107 | |
108 | #endif |
109 | |
110 | /** |
111 | * path_get_archive_delim: |
112 | * @path : path |
113 | * |
114 | * Find delimiter of an archive file. Only the first '#' |
115 | * after a compression extension is considered. |
116 | * |
117 | * Returns: pointer to the delimiter in the path if it contains |
118 | * a path inside a compressed file, otherwise NULL. |
119 | */ |
120 | const char *path_get_archive_delim(const char *path) |
121 | { |
122 | const char *last_slash = find_last_slash(path); |
123 | const char *delim = NULL; |
124 | char buf[5]; |
125 | |
126 | buf[0] = '\0'; |
127 | |
128 | if (!last_slash) |
129 | return NULL; |
130 | |
131 | /* Find delimiter position */ |
132 | delim = strrchr(last_slash, '#'); |
133 | |
134 | if (!delim) |
135 | return NULL; |
136 | |
137 | /* Check whether this is a known archive type |
138 | * > Note: The code duplication here is |
139 | * deliberate, to maximise performance */ |
140 | if (delim - last_slash > 4) |
141 | { |
142 | strlcpy(buf, delim - 4, sizeof(buf)); |
143 | buf[4] = '\0'; |
144 | |
145 | string_to_lower(buf); |
146 | |
147 | /* Check if this is a '.zip', '.apk' or '.7z' file */ |
148 | if (string_is_equal(buf, ".zip") || |
149 | string_is_equal(buf, ".apk") || |
150 | string_is_equal(buf + 1, ".7z")) |
151 | return delim; |
152 | } |
153 | else if (delim - last_slash > 3) |
154 | { |
155 | strlcpy(buf, delim - 3, sizeof(buf)); |
156 | buf[3] = '\0'; |
157 | |
158 | string_to_lower(buf); |
159 | |
160 | /* Check if this is a '.7z' file */ |
161 | if (string_is_equal(buf, ".7z")) |
162 | return delim; |
163 | } |
164 | |
165 | return NULL; |
166 | } |
167 | |
168 | /** |
169 | * path_get_extension: |
170 | * @path : path |
171 | * |
172 | * Gets extension of file. Only '.'s |
173 | * after the last slash are considered. |
174 | * |
175 | * Returns: extension part from the path. |
176 | */ |
177 | const char *path_get_extension(const char *path) |
178 | { |
179 | const char *ext; |
180 | if (!string_is_empty(path) && ((ext = strrchr(path_basename(path), '.')))) |
181 | return ext + 1; |
182 | return ""; |
183 | } |
184 | |
185 | /** |
186 | * path_remove_extension: |
187 | * @path : path |
188 | * |
189 | * Mutates path by removing its extension. Removes all |
190 | * text after and including the last '.'. |
191 | * Only '.'s after the last slash are considered. |
192 | * |
193 | * Returns: |
194 | * 1) If path has an extension, returns path with the |
195 | * extension removed. |
196 | * 2) If there is no extension, returns NULL. |
197 | * 3) If path is empty or NULL, returns NULL |
198 | */ |
199 | char *path_remove_extension(char *path) |
200 | { |
201 | char *last = !string_is_empty(path) |
202 | ? (char*)strrchr(path_basename(path), '.') : NULL; |
203 | if (!last) |
204 | return NULL; |
205 | if (*last) |
206 | *last = '\0'; |
207 | return path; |
208 | } |
209 | |
210 | /** |
211 | * path_is_compressed_file: |
212 | * @path : path |
213 | * |
214 | * Checks if path is a compressed file. |
215 | * |
216 | * Returns: true (1) if path is a compressed file, otherwise false (0). |
217 | **/ |
218 | bool path_is_compressed_file(const char* path) |
219 | { |
220 | const char *ext = path_get_extension(path); |
221 | |
222 | if (string_is_empty(ext)) |
223 | return false; |
224 | |
225 | if (string_is_equal_noncase(ext, "zip") || |
226 | string_is_equal_noncase(ext, "apk") || |
227 | string_is_equal_noncase(ext, "7z")) |
228 | return true; |
229 | |
230 | return false; |
231 | } |
232 | |
233 | /** |
234 | * fill_pathname: |
235 | * @out_path : output path |
236 | * @in_path : input path |
237 | * @replace : what to replace |
238 | * @size : buffer size of output path |
239 | * |
240 | * FIXME: Verify |
241 | * |
242 | * Replaces filename extension with 'replace' and outputs result to out_path. |
243 | * The extension here is considered to be the string from the last '.' |
244 | * to the end. |
245 | * |
246 | * Only '.'s after the last slash are considered as extensions. |
247 | * If no '.' is present, in_path and replace will simply be concatenated. |
248 | * 'size' is buffer size of 'out_path'. |
249 | * E.g.: in_path = "/foo/bar/baz/boo.c", replace = ".asm" => |
250 | * out_path = "/foo/bar/baz/boo.asm" |
251 | * E.g.: in_path = "/foo/bar/baz/boo.c", replace = "" => |
252 | * out_path = "/foo/bar/baz/boo" |
253 | */ |
254 | void fill_pathname(char *out_path, const char *in_path, |
255 | const char *replace, size_t size) |
256 | { |
257 | char tmp_path[PATH_MAX_LENGTH]; |
258 | char *tok = NULL; |
259 | |
260 | tmp_path[0] = '\0'; |
261 | |
262 | strlcpy(tmp_path, in_path, sizeof(tmp_path)); |
263 | if ((tok = (char*)strrchr(path_basename(tmp_path), '.'))) |
264 | *tok = '\0'; |
265 | |
266 | fill_pathname_noext(out_path, tmp_path, replace, size); |
267 | } |
268 | |
269 | /** |
270 | * fill_pathname_noext: |
271 | * @out_path : output path |
272 | * @in_path : input path |
273 | * @replace : what to replace |
274 | * @size : buffer size of output path |
275 | * |
276 | * Appends a filename extension 'replace' to 'in_path', and outputs |
277 | * result in 'out_path'. |
278 | * |
279 | * Assumes in_path has no extension. If an extension is still |
280 | * present in 'in_path', it will be ignored. |
281 | * |
282 | */ |
283 | size_t fill_pathname_noext(char *out_path, const char *in_path, |
284 | const char *replace, size_t size) |
285 | { |
286 | strlcpy(out_path, in_path, size); |
287 | return strlcat(out_path, replace, size); |
288 | } |
289 | |
290 | char *find_last_slash(const char *str) |
291 | { |
292 | const char *slash = strrchr(str, '/'); |
293 | #ifdef _WIN32 |
294 | const char *backslash = strrchr(str, '\\'); |
295 | |
296 | if (!slash || (backslash > slash)) |
297 | return (char*)backslash; |
298 | #endif |
299 | return (char*)slash; |
300 | } |
301 | |
302 | /** |
303 | * fill_pathname_slash: |
304 | * @path : path |
305 | * @size : size of path |
306 | * |
307 | * Assumes path is a directory. Appends a slash |
308 | * if not already there. |
309 | **/ |
310 | void fill_pathname_slash(char *path, size_t size) |
311 | { |
312 | size_t path_len; |
313 | const char *last_slash = find_last_slash(path); |
314 | |
315 | if (!last_slash) |
316 | { |
317 | strlcat(path, PATH_DEFAULT_SLASH(), size); |
318 | return; |
319 | } |
320 | |
321 | path_len = strlen(path); |
322 | /* Try to preserve slash type. */ |
323 | if (last_slash != (path + path_len - 1)) |
324 | { |
325 | path[path_len] = last_slash[0]; |
326 | path[path_len+1] = '\0'; |
327 | } |
328 | } |
329 | |
330 | /** |
331 | * fill_pathname_dir: |
332 | * @in_dir : input directory path |
333 | * @in_basename : input basename to be appended to @in_dir |
334 | * @replace : replacement to be appended to @in_basename |
335 | * @size : size of buffer |
336 | * |
337 | * Appends basename of 'in_basename', to 'in_dir', along with 'replace'. |
338 | * Basename of in_basename is the string after the last '/' or '\\', |
339 | * i.e the filename without directories. |
340 | * |
341 | * If in_basename has no '/' or '\\', the whole 'in_basename' will be used. |
342 | * 'size' is buffer size of 'in_dir'. |
343 | * |
344 | * E.g..: in_dir = "/tmp/some_dir", in_basename = "/some_content/foo.c", |
345 | * replace = ".asm" => in_dir = "/tmp/some_dir/foo.c.asm" |
346 | **/ |
347 | size_t fill_pathname_dir(char *in_dir, const char *in_basename, |
348 | const char *replace, size_t size) |
349 | { |
350 | const char *base = NULL; |
351 | |
352 | fill_pathname_slash(in_dir, size); |
353 | base = path_basename(in_basename); |
354 | strlcat(in_dir, base, size); |
355 | return strlcat(in_dir, replace, size); |
356 | } |
357 | |
358 | /** |
359 | * fill_pathname_base: |
360 | * @out : output path |
361 | * @in_path : input path |
362 | * @size : size of output path |
363 | * |
364 | * Copies basename of @in_path into @out_path. |
365 | **/ |
366 | size_t fill_pathname_base(char *out, const char *in_path, size_t size) |
367 | { |
368 | const char *ptr = path_basename(in_path); |
369 | |
370 | if (!ptr) |
371 | ptr = in_path; |
372 | |
373 | return strlcpy(out, ptr, size); |
374 | } |
375 | |
376 | void fill_pathname_base_noext(char *out, |
377 | const char *in_path, size_t size) |
378 | { |
379 | fill_pathname_base(out, in_path, size); |
380 | path_remove_extension(out); |
381 | } |
382 | |
383 | size_t fill_pathname_base_ext(char *out, |
384 | const char *in_path, const char *ext, |
385 | size_t size) |
386 | { |
387 | fill_pathname_base_noext(out, in_path, size); |
388 | return strlcat(out, ext, size); |
389 | } |
390 | |
391 | /** |
392 | * fill_pathname_basedir: |
393 | * @out_dir : output directory |
394 | * @in_path : input path |
395 | * @size : size of output directory |
396 | * |
397 | * Copies base directory of @in_path into @out_path. |
398 | * If in_path is a path without any slashes (relative current directory), |
399 | * @out_path will get path "./". |
400 | **/ |
401 | void fill_pathname_basedir(char *out_dir, |
402 | const char *in_path, size_t size) |
403 | { |
404 | if (out_dir != in_path) |
405 | strlcpy(out_dir, in_path, size); |
406 | path_basedir(out_dir); |
407 | } |
408 | |
409 | void fill_pathname_basedir_noext(char *out_dir, |
410 | const char *in_path, size_t size) |
411 | { |
412 | fill_pathname_basedir(out_dir, in_path, size); |
413 | path_remove_extension(out_dir); |
414 | } |
415 | |
416 | /** |
417 | * fill_pathname_parent_dir_name: |
418 | * @out_dir : output directory |
419 | * @in_dir : input directory |
420 | * @size : size of output directory |
421 | * |
422 | * Copies only the parent directory name of @in_dir into @out_dir. |
423 | * The two buffers must not overlap. Removes trailing '/'. |
424 | * Returns true on success, false if a slash was not found in the path. |
425 | **/ |
426 | bool fill_pathname_parent_dir_name(char *out_dir, |
427 | const char *in_dir, size_t size) |
428 | { |
429 | bool success = false; |
430 | char *temp = strdup(in_dir); |
431 | char *last = find_last_slash(temp); |
432 | |
433 | if (last && last[1] == 0) |
434 | { |
435 | *last = '\0'; |
436 | last = find_last_slash(temp); |
437 | } |
438 | |
439 | if (last) |
440 | *last = '\0'; |
441 | |
442 | in_dir = find_last_slash(temp); |
443 | |
444 | success = in_dir && in_dir[1]; |
445 | |
446 | if (success) |
447 | strlcpy(out_dir, in_dir + 1, size); |
448 | |
449 | free(temp); |
450 | return success; |
451 | } |
452 | |
453 | /** |
454 | * fill_pathname_parent_dir: |
455 | * @out_dir : output directory |
456 | * @in_dir : input directory |
457 | * @size : size of output directory |
458 | * |
459 | * Copies parent directory of @in_dir into @out_dir. |
460 | * Assumes @in_dir is a directory. Keeps trailing '/'. |
461 | * If the path was already at the root directory, @out_dir will be an empty string. |
462 | **/ |
463 | void fill_pathname_parent_dir(char *out_dir, |
464 | const char *in_dir, size_t size) |
465 | { |
466 | if (out_dir != in_dir) |
467 | strlcpy(out_dir, in_dir, size); |
468 | path_parent_dir(out_dir); |
469 | } |
470 | |
471 | /** |
472 | * fill_dated_filename: |
473 | * @out_filename : output filename |
474 | * @ext : extension of output filename |
475 | * @size : buffer size of output filename |
476 | * |
477 | * Creates a 'dated' filename prefixed by 'RetroArch', and |
478 | * concatenates extension (@ext) to it. |
479 | * |
480 | * E.g.: |
481 | * out_filename = "RetroArch-{month}{day}-{Hours}{Minutes}.{@ext}" |
482 | **/ |
483 | size_t fill_dated_filename(char *out_filename, |
484 | const char *ext, size_t size) |
485 | { |
486 | time_t cur_time = time(NULL); |
487 | struct tm tm_; |
488 | |
489 | rtime_localtime(&cur_time, &tm_); |
490 | |
491 | strftime(out_filename, size, |
492 | "RetroArch-%m%d-%H%M%S", &tm_); |
493 | return strlcat(out_filename, ext, size); |
494 | } |
495 | |
496 | /** |
497 | * fill_str_dated_filename: |
498 | * @out_filename : output filename |
499 | * @in_str : input string |
500 | * @ext : extension of output filename |
501 | * @size : buffer size of output filename |
502 | * |
503 | * Creates a 'dated' filename prefixed by the string @in_str, and |
504 | * concatenates extension (@ext) to it. |
505 | * |
506 | * E.g.: |
507 | * out_filename = "RetroArch-{year}{month}{day}-{Hour}{Minute}{Second}.{@ext}" |
508 | **/ |
509 | void fill_str_dated_filename(char *out_filename, |
510 | const char *in_str, const char *ext, size_t size) |
511 | { |
512 | char format[256]; |
513 | struct tm tm_; |
514 | time_t cur_time = time(NULL); |
515 | |
516 | format[0] = '\0'; |
517 | |
518 | rtime_localtime(&cur_time, &tm_); |
519 | |
520 | if (string_is_empty(ext)) |
521 | { |
522 | strftime(format, sizeof(format), "-%y%m%d-%H%M%S", &tm_); |
523 | fill_pathname_noext(out_filename, in_str, format, size); |
524 | } |
525 | else |
526 | { |
527 | strftime(format, sizeof(format), "-%y%m%d-%H%M%S.", &tm_); |
528 | |
529 | fill_pathname_join_concat_noext(out_filename, |
530 | in_str, format, ext, |
531 | size); |
532 | } |
533 | } |
534 | |
535 | /** |
536 | * path_basedir: |
537 | * @path : path |
538 | * |
539 | * Extracts base directory by mutating path. |
540 | * Keeps trailing '/'. |
541 | **/ |
542 | void path_basedir(char *path) |
543 | { |
544 | char *last = NULL; |
545 | |
546 | if (strlen(path) < 2) |
547 | return; |
548 | |
549 | last = find_last_slash(path); |
550 | |
551 | if (last) |
552 | last[1] = '\0'; |
553 | else |
554 | snprintf(path, 3, "." PATH_DEFAULT_SLASH()); |
555 | } |
556 | |
557 | /** |
558 | * path_parent_dir: |
559 | * @path : path |
560 | * |
561 | * Extracts parent directory by mutating path. |
562 | * Assumes that path is a directory. Keeps trailing '/'. |
563 | * If the path was already at the root directory, returns empty string |
564 | **/ |
565 | void path_parent_dir(char *path) |
566 | { |
567 | size_t len = 0; |
568 | |
569 | if (!path) |
570 | return; |
571 | |
572 | len = strlen(path); |
573 | |
574 | if (len && PATH_CHAR_IS_SLASH(path[len - 1])) |
575 | { |
576 | bool path_was_absolute = path_is_absolute(path); |
577 | |
578 | path[len - 1] = '\0'; |
579 | |
580 | if (path_was_absolute && !find_last_slash(path)) |
581 | { |
582 | /* We removed the only slash from what used to be an absolute path. |
583 | * On Linux, this goes from "/" to an empty string and everything works fine, |
584 | * but on Windows, we went from C:\ to C:, which is not a valid path and that later |
585 | * gets errornously treated as a relative one by path_basedir and returns "./". |
586 | * What we really wanted is an empty string. */ |
587 | path[0] = '\0'; |
588 | return; |
589 | } |
590 | } |
591 | path_basedir(path); |
592 | } |
593 | |
594 | /** |
595 | * path_basename: |
596 | * @path : path |
597 | * |
598 | * Get basename from @path. |
599 | * |
600 | * Returns: basename from path. |
601 | **/ |
602 | const char *path_basename(const char *path) |
603 | { |
604 | /* We cut at the first compression-related hash */ |
605 | const char *delim = path_get_archive_delim(path); |
606 | if (delim) |
607 | return delim + 1; |
608 | |
609 | { |
610 | /* We cut at the last slash */ |
611 | const char *last = find_last_slash(path); |
612 | if (last) |
613 | return last + 1; |
614 | } |
615 | |
616 | return path; |
617 | } |
618 | |
619 | /** |
620 | * path_is_absolute: |
621 | * @path : path |
622 | * |
623 | * Checks if @path is an absolute path or a relative path. |
624 | * |
625 | * Returns: true if path is absolute, false if path is relative. |
626 | **/ |
627 | bool path_is_absolute(const char *path) |
628 | { |
629 | if (string_is_empty(path)) |
630 | return false; |
631 | |
632 | if (path[0] == '/') |
633 | return true; |
634 | |
635 | #if defined(_WIN32) |
636 | /* Many roads lead to Rome... |
637 | * Note: Drive letter can only be 1 character long */ |
638 | if (string_starts_with_size(path, "\\\\", STRLEN_CONST("\\\\")) || |
639 | string_starts_with_size(path + 1, ":/", STRLEN_CONST(":/")) || |
640 | string_starts_with_size(path + 1, ":\\", STRLEN_CONST(":\\"))) |
641 | return true; |
642 | #elif defined(__wiiu__) || defined(VITA) |
643 | { |
644 | const char *seperator = strchr(path, ':'); |
645 | if (seperator && (seperator[1] == '/')) |
646 | return true; |
647 | } |
648 | #endif |
649 | |
650 | return false; |
651 | } |
652 | |
653 | /** |
654 | * path_resolve_realpath: |
655 | * @buf : input and output buffer for path |
656 | * @size : size of buffer |
657 | * @resolve_symlinks : whether to resolve symlinks or not |
658 | * |
659 | * Resolves use of ".", "..", multiple slashes etc in absolute paths. |
660 | * |
661 | * Relative paths are rebased on the current working dir. |
662 | * |
663 | * Returns: @buf if successful, NULL otherwise. |
664 | * Note: Not implemented on consoles |
665 | * Note: Symlinks are only resolved on Unix-likes |
666 | * Note: The current working dir might not be what you expect, |
667 | * e.g. on Android it is "/" |
668 | * Use of fill_pathname_resolve_relative() should be prefered |
669 | **/ |
670 | char *path_resolve_realpath(char *buf, size_t size, bool resolve_symlinks) |
671 | { |
672 | #if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL) |
673 | char tmp[PATH_MAX_LENGTH]; |
674 | #ifdef _WIN32 |
675 | strlcpy(tmp, buf, sizeof(tmp)); |
676 | if (!_fullpath(buf, tmp, size)) |
677 | { |
678 | strlcpy(buf, tmp, size); |
679 | return NULL; |
680 | } |
681 | return buf; |
682 | #else |
683 | size_t t; |
684 | char *p; |
685 | const char *next; |
686 | const char *buf_end; |
687 | |
688 | if (resolve_symlinks) |
689 | { |
690 | strlcpy(tmp, buf, sizeof(tmp)); |
691 | |
692 | /* NOTE: realpath() expects at least PATH_MAX_LENGTH bytes in buf. |
693 | * Technically, PATH_MAX_LENGTH needn't be defined, but we rely on it anyways. |
694 | * POSIX 2008 can automatically allocate for you, |
695 | * but don't rely on that. */ |
696 | if (!realpath(tmp, buf)) |
697 | { |
698 | strlcpy(buf, tmp, size); |
699 | return NULL; |
700 | } |
701 | |
702 | return buf; |
703 | } |
704 | |
705 | t = 0; /* length of output */ |
706 | buf_end = buf + strlen(buf); |
707 | |
708 | if (!path_is_absolute(buf)) |
709 | { |
710 | size_t len; |
711 | /* rebase on working directory */ |
712 | if (!getcwd(tmp, PATH_MAX_LENGTH-1)) |
713 | return NULL; |
714 | |
715 | len = strlen(tmp); |
716 | t += len; |
717 | |
718 | if (tmp[len-1] != '/') |
719 | tmp[t++] = '/'; |
720 | |
721 | if (string_is_empty(buf)) |
722 | goto end; |
723 | |
724 | p = buf; |
725 | } |
726 | else |
727 | { |
728 | /* UNIX paths can start with multiple '/', copy those */ |
729 | for (p = buf; *p == '/'; p++) |
730 | tmp[t++] = '/'; |
731 | } |
732 | |
733 | /* p points to just after a slash while 'next' points to the next slash |
734 | * if there are no slashes, they point relative to where one would be */ |
735 | do |
736 | { |
737 | next = strchr(p, '/'); |
738 | if (!next) |
739 | next = buf_end; |
740 | |
741 | if ((next - p == 2 && p[0] == '.' && p[1] == '.')) |
742 | { |
743 | p += 3; |
744 | |
745 | /* fail for illegal /.., //.. etc */ |
746 | if (t == 1 || tmp[t-2] == '/') |
747 | return NULL; |
748 | |
749 | /* delete previous segment in tmp by adjusting size t |
750 | * tmp[t-1] == '/', find '/' before that */ |
751 | t = t-2; |
752 | while (tmp[t] != '/') |
753 | t--; |
754 | t++; |
755 | } |
756 | else if (next - p == 1 && p[0] == '.') |
757 | p += 2; |
758 | else if (next - p == 0) |
759 | p += 1; |
760 | else |
761 | { |
762 | /* fail when truncating */ |
763 | if (t + next-p+1 > PATH_MAX_LENGTH-1) |
764 | return NULL; |
765 | |
766 | while (p <= next) |
767 | tmp[t++] = *p++; |
768 | } |
769 | |
770 | } |
771 | while (next < buf_end); |
772 | |
773 | end: |
774 | tmp[t] = '\0'; |
775 | strlcpy(buf, tmp, size); |
776 | return buf; |
777 | #endif |
778 | #endif |
779 | return NULL; |
780 | } |
781 | |
782 | /** |
783 | * path_relative_to: |
784 | * @out : buffer to write the relative path to |
785 | * @path : path to be expressed relatively |
786 | * @base : base directory to start out on |
787 | * @size : size of output buffer |
788 | * |
789 | * Turns @path into a path relative to @base and writes it to @out. |
790 | * |
791 | * @base is assumed to be a base directory, i.e. a path ending with '/' or '\'. |
792 | * Both @path and @base are assumed to be absolute paths without "." or "..". |
793 | * |
794 | * E.g. path /a/b/e/f.cg with base /a/b/c/d/ turns into ../../e/f.cg |
795 | **/ |
796 | size_t path_relative_to(char *out, |
797 | const char *path, const char *base, size_t size) |
798 | { |
799 | size_t i, j; |
800 | const char *trimmed_path, *trimmed_base; |
801 | |
802 | #ifdef _WIN32 |
803 | /* For different drives, return absolute path */ |
804 | if (strlen(path) >= 2 && strlen(base) >= 2 |
805 | && path[1] == ':' && base[1] == ':' |
806 | && path[0] != base[0]) |
807 | return strlcpy(out, path, size); |
808 | #endif |
809 | |
810 | /* Trim common beginning */ |
811 | for (i = 0, j = 0; path[i] && base[i] && path[i] == base[i]; i++) |
812 | if (path[i] == PATH_DEFAULT_SLASH_C()) |
813 | j = i + 1; |
814 | |
815 | trimmed_path = path+j; |
816 | trimmed_base = base+i; |
817 | |
818 | /* Each segment of base turns into ".." */ |
819 | out[0] = '\0'; |
820 | for (i = 0; trimmed_base[i]; i++) |
821 | if (trimmed_base[i] == PATH_DEFAULT_SLASH_C()) |
822 | strlcat(out, ".." PATH_DEFAULT_SLASH(), size); |
823 | |
824 | return strlcat(out, trimmed_path, size); |
825 | } |
826 | |
827 | /** |
828 | * fill_pathname_resolve_relative: |
829 | * @out_path : output path |
830 | * @in_refpath : input reference path |
831 | * @in_path : input path |
832 | * @size : size of @out_path |
833 | * |
834 | * Joins basedir of @in_refpath together with @in_path. |
835 | * If @in_path is an absolute path, out_path = in_path. |
836 | * E.g.: in_refpath = "/foo/bar/baz.a", in_path = "foobar.cg", |
837 | * out_path = "/foo/bar/foobar.cg". |
838 | **/ |
839 | void fill_pathname_resolve_relative(char *out_path, |
840 | const char *in_refpath, const char *in_path, size_t size) |
841 | { |
842 | if (path_is_absolute(in_path)) |
843 | { |
844 | strlcpy(out_path, in_path, size); |
845 | return; |
846 | } |
847 | |
848 | fill_pathname_basedir(out_path, in_refpath, size); |
849 | strlcat(out_path, in_path, size); |
850 | path_resolve_realpath(out_path, size, false); |
851 | } |
852 | |
853 | /** |
854 | * fill_pathname_join: |
855 | * @out_path : output path |
856 | * @dir : directory |
857 | * @path : path |
858 | * @size : size of output path |
859 | * |
860 | * Joins a directory (@dir) and path (@path) together. |
861 | * Makes sure not to get two consecutive slashes |
862 | * between directory and path. |
863 | **/ |
864 | size_t fill_pathname_join(char *out_path, |
865 | const char *dir, const char *path, size_t size) |
866 | { |
867 | if (out_path != dir) |
868 | strlcpy(out_path, dir, size); |
869 | |
870 | if (*out_path) |
871 | fill_pathname_slash(out_path, size); |
872 | |
873 | return strlcat(out_path, path, size); |
874 | } |
875 | |
876 | size_t fill_pathname_join_special_ext(char *out_path, |
877 | const char *dir, const char *path, |
878 | const char *last, const char *ext, |
879 | size_t size) |
880 | { |
881 | fill_pathname_join(out_path, dir, path, size); |
882 | if (*out_path) |
883 | fill_pathname_slash(out_path, size); |
884 | |
885 | strlcat(out_path, last, size); |
886 | return strlcat(out_path, ext, size); |
887 | } |
888 | |
889 | size_t fill_pathname_join_concat_noext(char *out_path, |
890 | const char *dir, const char *path, |
891 | const char *concat, |
892 | size_t size) |
893 | { |
894 | fill_pathname_noext(out_path, dir, path, size); |
895 | return strlcat(out_path, concat, size); |
896 | } |
897 | |
898 | size_t fill_pathname_join_concat(char *out_path, |
899 | const char *dir, const char *path, |
900 | const char *concat, |
901 | size_t size) |
902 | { |
903 | fill_pathname_join(out_path, dir, path, size); |
904 | return strlcat(out_path, concat, size); |
905 | } |
906 | |
907 | void fill_pathname_join_noext(char *out_path, |
908 | const char *dir, const char *path, size_t size) |
909 | { |
910 | fill_pathname_join(out_path, dir, path, size); |
911 | path_remove_extension(out_path); |
912 | } |
913 | |
914 | /** |
915 | * fill_pathname_join_delim: |
916 | * @out_path : output path |
917 | * @dir : directory |
918 | * @path : path |
919 | * @delim : delimiter |
920 | * @size : size of output path |
921 | * |
922 | * Joins a directory (@dir) and path (@path) together |
923 | * using the given delimiter (@delim). |
924 | **/ |
925 | size_t fill_pathname_join_delim(char *out_path, const char *dir, |
926 | const char *path, const char delim, size_t size) |
927 | { |
928 | size_t copied; |
929 | /* behavior of strlcpy is undefined if dst and src overlap */ |
930 | if (out_path == dir) |
931 | copied = strlen(dir); |
932 | else |
933 | copied = strlcpy(out_path, dir, size); |
934 | |
935 | out_path[copied] = delim; |
936 | out_path[copied+1] = '\0'; |
937 | |
938 | if (path) |
939 | copied = strlcat(out_path, path, size); |
940 | return copied; |
941 | } |
942 | |
943 | size_t fill_pathname_join_delim_concat(char *out_path, const char *dir, |
944 | const char *path, const char delim, const char *concat, |
945 | size_t size) |
946 | { |
947 | fill_pathname_join_delim(out_path, dir, path, delim, size); |
948 | return strlcat(out_path, concat, size); |
949 | } |
950 | |
951 | /** |
952 | * fill_short_pathname_representation: |
953 | * @out_rep : output representation |
954 | * @in_path : input path |
955 | * @size : size of output representation |
956 | * |
957 | * Generates a short representation of path. It should only |
958 | * be used for displaying the result; the output representation is not |
959 | * binding in any meaningful way (for a normal path, this is the same as basename) |
960 | * In case of more complex URLs, this should cut everything except for |
961 | * the main image file. |
962 | * |
963 | * E.g.: "/path/to/game.img" -> game.img |
964 | * "/path/to/myarchive.7z#folder/to/game.img" -> game.img |
965 | */ |
966 | size_t fill_short_pathname_representation(char* out_rep, |
967 | const char *in_path, size_t size) |
968 | { |
969 | char path_short[PATH_MAX_LENGTH]; |
970 | |
971 | path_short[0] = '\0'; |
972 | |
973 | fill_pathname(path_short, path_basename(in_path), "", |
974 | sizeof(path_short)); |
975 | |
976 | return strlcpy(out_rep, path_short, size); |
977 | } |
978 | |
979 | void fill_short_pathname_representation_noext(char* out_rep, |
980 | const char *in_path, size_t size) |
981 | { |
982 | fill_short_pathname_representation(out_rep, in_path, size); |
983 | path_remove_extension(out_rep); |
984 | } |
985 | |
986 | void fill_pathname_expand_special(char *out_path, |
987 | const char *in_path, size_t size) |
988 | { |
989 | #if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL) |
990 | if (in_path[0] == '~') |
991 | { |
992 | char *home_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); |
993 | |
994 | home_dir[0] = '\0'; |
995 | |
996 | fill_pathname_home_dir(home_dir, |
997 | PATH_MAX_LENGTH * sizeof(char)); |
998 | |
999 | if (*home_dir) |
1000 | { |
1001 | size_t src_size = strlcpy(out_path, home_dir, size); |
1002 | retro_assert(src_size < size); |
1003 | |
1004 | out_path += src_size; |
1005 | size -= src_size; |
1006 | |
1007 | if (!PATH_CHAR_IS_SLASH(out_path[-1])) |
1008 | { |
1009 | src_size = strlcpy(out_path, PATH_DEFAULT_SLASH(), size); |
1010 | retro_assert(src_size < size); |
1011 | |
1012 | out_path += src_size; |
1013 | size -= src_size; |
1014 | } |
1015 | |
1016 | in_path += 2; |
1017 | } |
1018 | |
1019 | free(home_dir); |
1020 | } |
1021 | else if (in_path[0] == ':') |
1022 | { |
1023 | char *application_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); |
1024 | |
1025 | application_dir[0] = '\0'; |
1026 | |
1027 | fill_pathname_application_dir(application_dir, |
1028 | PATH_MAX_LENGTH * sizeof(char)); |
1029 | |
1030 | if (*application_dir) |
1031 | { |
1032 | size_t src_size = strlcpy(out_path, application_dir, size); |
1033 | retro_assert(src_size < size); |
1034 | |
1035 | out_path += src_size; |
1036 | size -= src_size; |
1037 | |
1038 | if (!PATH_CHAR_IS_SLASH(out_path[-1])) |
1039 | { |
1040 | src_size = strlcpy(out_path, PATH_DEFAULT_SLASH(), size); |
1041 | retro_assert(src_size < size); |
1042 | |
1043 | out_path += src_size; |
1044 | size -= src_size; |
1045 | } |
1046 | |
1047 | in_path += 2; |
1048 | } |
1049 | |
1050 | free(application_dir); |
1051 | } |
1052 | #endif |
1053 | |
1054 | retro_assert(strlcpy(out_path, in_path, size) < size); |
1055 | } |
1056 | |
1057 | void fill_pathname_abbreviate_special(char *out_path, |
1058 | const char *in_path, size_t size) |
1059 | { |
1060 | #if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL) |
1061 | unsigned i; |
1062 | const char *candidates[3]; |
1063 | const char *notations[3]; |
1064 | char application_dir[PATH_MAX_LENGTH]; |
1065 | char home_dir[PATH_MAX_LENGTH]; |
1066 | |
1067 | application_dir[0] = '\0'; |
1068 | home_dir[0] = '\0'; |
1069 | |
1070 | /* application_dir could be zero-string. Safeguard against this. |
1071 | * |
1072 | * Keep application dir in front of home, moving app dir to a |
1073 | * new location inside home would break otherwise. */ |
1074 | |
1075 | /* ugly hack - use application_dir pointer |
1076 | * before filling it in. C89 reasons */ |
1077 | candidates[0] = application_dir; |
1078 | candidates[1] = home_dir; |
1079 | candidates[2] = NULL; |
1080 | |
1081 | notations [0] = ":"; |
1082 | notations [1] = "~"; |
1083 | notations [2] = NULL; |
1084 | |
1085 | fill_pathname_application_dir(application_dir, sizeof(application_dir)); |
1086 | fill_pathname_home_dir(home_dir, sizeof(home_dir)); |
1087 | |
1088 | for (i = 0; candidates[i]; i++) |
1089 | { |
1090 | if (!string_is_empty(candidates[i]) && |
1091 | string_starts_with(in_path, candidates[i])) |
1092 | { |
1093 | size_t src_size = strlcpy(out_path, notations[i], size); |
1094 | |
1095 | retro_assert(src_size < size); |
1096 | |
1097 | out_path += src_size; |
1098 | size -= src_size; |
1099 | in_path += strlen(candidates[i]); |
1100 | |
1101 | if (!PATH_CHAR_IS_SLASH(*in_path)) |
1102 | { |
1103 | strcpy_literal(out_path, PATH_DEFAULT_SLASH()); |
1104 | out_path++; |
1105 | size--; |
1106 | } |
1107 | |
1108 | break; /* Don't allow more abbrevs to take place. */ |
1109 | } |
1110 | } |
1111 | |
1112 | #endif |
1113 | |
1114 | retro_assert(strlcpy(out_path, in_path, size) < size); |
1115 | } |
1116 | |
1117 | /** |
1118 | * path_basedir: |
1119 | * @path : path |
1120 | * |
1121 | * Extracts base directory by mutating path. |
1122 | * Keeps trailing '/'. |
1123 | **/ |
1124 | void path_basedir_wrapper(char *path) |
1125 | { |
1126 | char *last = NULL; |
1127 | if (strlen(path) < 2) |
1128 | return; |
1129 | |
1130 | #ifdef HAVE_COMPRESSION |
1131 | /* We want to find the directory with the archive in basedir. */ |
1132 | last = (char*)path_get_archive_delim(path); |
1133 | if (last) |
1134 | *last = '\0'; |
1135 | #endif |
1136 | |
1137 | last = find_last_slash(path); |
1138 | |
1139 | if (last) |
1140 | last[1] = '\0'; |
1141 | else |
1142 | snprintf(path, 3, "." PATH_DEFAULT_SLASH()); |
1143 | } |
1144 | |
1145 | #if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL) |
1146 | void fill_pathname_application_path(char *s, size_t len) |
1147 | { |
1148 | size_t i; |
1149 | #ifdef __APPLE__ |
1150 | CFBundleRef bundle = CFBundleGetMainBundle(); |
1151 | #endif |
1152 | #ifdef _WIN32 |
1153 | DWORD ret = 0; |
1154 | wchar_t wstr[PATH_MAX_LENGTH] = {0}; |
1155 | #endif |
1156 | #ifdef __HAIKU__ |
1157 | image_info info; |
1158 | int32_t cookie = 0; |
1159 | #endif |
1160 | (void)i; |
1161 | |
1162 | if (!len) |
1163 | return; |
1164 | |
1165 | #if defined(_WIN32) |
1166 | #ifdef LEGACY_WIN32 |
1167 | ret = GetModuleFileNameA(NULL, s, len); |
1168 | #else |
1169 | ret = GetModuleFileNameW(NULL, wstr, ARRAY_SIZE(wstr)); |
1170 | |
1171 | if (*wstr) |
1172 | { |
1173 | char *str = utf16_to_utf8_string_alloc(wstr); |
1174 | |
1175 | if (str) |
1176 | { |
1177 | strlcpy(s, str, len); |
1178 | free(str); |
1179 | } |
1180 | } |
1181 | #endif |
1182 | s[ret] = '\0'; |
1183 | #elif defined(__APPLE__) |
1184 | if (bundle) |
1185 | { |
1186 | CFURLRef bundle_url = CFBundleCopyBundleURL(bundle); |
1187 | CFStringRef bundle_path = CFURLCopyPath(bundle_url); |
1188 | CFStringGetCString(bundle_path, s, len, kCFStringEncodingUTF8); |
1189 | #ifdef HAVE_COCOATOUCH |
1190 | { |
1191 | /* This needs to be done so that the path becomes |
1192 | * /private/var/... and this |
1193 | * is used consistently throughout for the iOS bundle path */ |
1194 | char resolved_bundle_dir_buf[PATH_MAX_LENGTH] = {0}; |
1195 | if (realpath(s, resolved_bundle_dir_buf)) |
1196 | { |
1197 | strlcpy(s, resolved_bundle_dir_buf, len - 1); |
1198 | strlcat(s, "/", len); |
1199 | } |
1200 | } |
1201 | #endif |
1202 | |
1203 | CFRelease(bundle_path); |
1204 | CFRelease(bundle_url); |
1205 | #ifndef HAVE_COCOATOUCH |
1206 | /* Not sure what this does but it breaks |
1207 | * stuff for iOS, so skipping */ |
1208 | retro_assert(strlcat(s, "nobin", len) < len); |
1209 | #endif |
1210 | return; |
1211 | } |
1212 | #elif defined(__HAIKU__) |
1213 | while (get_next_image_info(0, &cookie, &info) == B_OK) |
1214 | { |
1215 | if (info.type == B_APP_IMAGE) |
1216 | { |
1217 | strlcpy(s, info.name, len); |
1218 | return; |
1219 | } |
1220 | } |
1221 | #elif defined(__QNX__) |
1222 | char *buff = malloc(len); |
1223 | |
1224 | if (_cmdname(buff)) |
1225 | strlcpy(s, buff, len); |
1226 | |
1227 | free(buff); |
1228 | #else |
1229 | { |
1230 | pid_t pid; |
1231 | static const char *exts[] = { "exe", "file", "path/a.out" }; |
1232 | char link_path[255]; |
1233 | |
1234 | link_path[0] = *s = '\0'; |
1235 | pid = getpid(); |
1236 | |
1237 | /* Linux, BSD and Solaris paths. Not standardized. */ |
1238 | for (i = 0; i < ARRAY_SIZE(exts); i++) |
1239 | { |
1240 | ssize_t ret; |
1241 | |
1242 | snprintf(link_path, sizeof(link_path), "/proc/%u/%s", |
1243 | (unsigned)pid, exts[i]); |
1244 | ret = readlink(link_path, s, len - 1); |
1245 | |
1246 | if (ret >= 0) |
1247 | { |
1248 | s[ret] = '\0'; |
1249 | return; |
1250 | } |
1251 | } |
1252 | } |
1253 | #endif |
1254 | } |
1255 | |
1256 | void fill_pathname_application_dir(char *s, size_t len) |
1257 | { |
1258 | #ifdef __WINRT__ |
1259 | strlcpy(s, uwp_dir_install, len); |
1260 | #else |
1261 | fill_pathname_application_path(s, len); |
1262 | path_basedir_wrapper(s); |
1263 | #endif |
1264 | } |
1265 | |
1266 | void fill_pathname_home_dir(char *s, size_t len) |
1267 | { |
1268 | #ifdef __WINRT__ |
1269 | const char *home = uwp_dir_data; |
1270 | #else |
1271 | const char *home = getenv("HOME"); |
1272 | #endif |
1273 | if (home) |
1274 | strlcpy(s, home, len); |
1275 | else |
1276 | *s = 0; |
1277 | } |
1278 | #endif |
1279 | |
1280 | bool is_path_accessible_using_standard_io(const char *path) |
1281 | { |
1282 | #ifdef __WINRT__ |
1283 | char relative_path_abbrev[PATH_MAX_LENGTH]; |
1284 | fill_pathname_abbreviate_special(relative_path_abbrev, |
1285 | path, sizeof(relative_path_abbrev)); |
1286 | return (strlen(relative_path_abbrev) >= 2 ) |
1287 | && ( relative_path_abbrev[0] == ':' |
1288 | || relative_path_abbrev[0] == '~') |
1289 | && PATH_CHAR_IS_SLASH(relative_path_abbrev[1]); |
1290 | #else |
1291 | return true; |
1292 | #endif |
1293 | } |