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