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