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