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