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