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             retro_assert(strlcpy(out_path,
1100                      PATH_DEFAULT_SLASH(), size) < size);
1101             out_path++;
1102             size--;
1103          }
1104 
1105          break; /* Don't allow more abbrevs to take place. */
1106       }
1107    }
1108 
1109 #endif
1110 
1111    retro_assert(strlcpy(out_path, in_path, size) < size);
1112 }
1113 
1114 /**
1115  * path_basedir:
1116  * @path               : path
1117  *
1118  * Extracts base directory by mutating path.
1119  * Keeps trailing '/'.
1120  **/
path_basedir_wrapper(char * path)1121 void path_basedir_wrapper(char *path)
1122 {
1123    char *last = NULL;
1124    if (strlen(path) < 2)
1125       return;
1126 
1127 #ifdef HAVE_COMPRESSION
1128    /* We want to find the directory with the archive in basedir. */
1129    last = (char*)path_get_archive_delim(path);
1130    if (last)
1131       *last = '\0';
1132 #endif
1133 
1134    last = find_last_slash(path);
1135 
1136    if (last)
1137       last[1] = '\0';
1138    else
1139       snprintf(path, 3, "." PATH_DEFAULT_SLASH());
1140 }
1141 
1142 #if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
fill_pathname_application_path(char * s,size_t len)1143 void fill_pathname_application_path(char *s, size_t len)
1144 {
1145    size_t i;
1146 #ifdef __APPLE__
1147   CFBundleRef bundle = CFBundleGetMainBundle();
1148 #endif
1149 #ifdef _WIN32
1150    DWORD ret = 0;
1151    wchar_t wstr[PATH_MAX_LENGTH] = {0};
1152 #endif
1153 #ifdef __HAIKU__
1154    image_info info;
1155    int32_t cookie = 0;
1156 #endif
1157    (void)i;
1158 
1159    if (!len)
1160       return;
1161 
1162 #if defined(_WIN32)
1163 #ifdef LEGACY_WIN32
1164    ret    = GetModuleFileNameA(NULL, s, len);
1165 #else
1166    ret    = GetModuleFileNameW(NULL, wstr, ARRAY_SIZE(wstr));
1167 
1168    if (*wstr)
1169    {
1170       char *str = utf16_to_utf8_string_alloc(wstr);
1171 
1172       if (str)
1173       {
1174          strlcpy(s, str, len);
1175          free(str);
1176       }
1177    }
1178 #endif
1179    s[ret] = '\0';
1180 #elif defined(__APPLE__)
1181    if (bundle)
1182    {
1183       CFURLRef bundle_url     = CFBundleCopyBundleURL(bundle);
1184       CFStringRef bundle_path = CFURLCopyPath(bundle_url);
1185       CFStringGetCString(bundle_path, s, len, kCFStringEncodingUTF8);
1186 #ifdef HAVE_COCOATOUCH
1187       {
1188          /* This needs to be done so that the path becomes
1189           * /private/var/... and this
1190           * is used consistently throughout for the iOS bundle path */
1191          char resolved_bundle_dir_buf[PATH_MAX_LENGTH] = {0};
1192          if (realpath(s, resolved_bundle_dir_buf))
1193          {
1194             strlcpy(s, resolved_bundle_dir_buf, len - 1);
1195             strlcat(s, "/", len);
1196          }
1197       }
1198 #endif
1199 
1200       CFRelease(bundle_path);
1201       CFRelease(bundle_url);
1202 #ifndef HAVE_COCOATOUCH
1203       /* Not sure what this does but it breaks
1204        * stuff for iOS, so skipping */
1205       retro_assert(strlcat(s, "nobin", len) < len);
1206 #endif
1207       return;
1208    }
1209 #elif defined(__HAIKU__)
1210    while (get_next_image_info(0, &cookie, &info) == B_OK)
1211    {
1212       if (info.type == B_APP_IMAGE)
1213       {
1214          strlcpy(s, info.name, len);
1215          return;
1216       }
1217    }
1218 #elif defined(__QNX__)
1219    char *buff = malloc(len);
1220 
1221    if (_cmdname(buff))
1222       strlcpy(s, buff, len);
1223 
1224    free(buff);
1225 #else
1226    {
1227       pid_t pid;
1228       static const char *exts[] = { "exe", "file", "path/a.out" };
1229       char link_path[255];
1230 
1231       link_path[0] = *s = '\0';
1232       pid       = getpid();
1233 
1234       /* Linux, BSD and Solaris paths. Not standardized. */
1235       for (i = 0; i < ARRAY_SIZE(exts); i++)
1236       {
1237          ssize_t ret;
1238 
1239          snprintf(link_path, sizeof(link_path), "/proc/%u/%s",
1240                (unsigned)pid, exts[i]);
1241          ret = readlink(link_path, s, len - 1);
1242 
1243          if (ret >= 0)
1244          {
1245             s[ret] = '\0';
1246             return;
1247          }
1248       }
1249    }
1250 #endif
1251 }
1252 
fill_pathname_application_dir(char * s,size_t len)1253 void fill_pathname_application_dir(char *s, size_t len)
1254 {
1255 #ifdef __WINRT__
1256    strlcpy(s, uwp_dir_install, len);
1257 #else
1258    fill_pathname_application_path(s, len);
1259    path_basedir_wrapper(s);
1260 #endif
1261 }
1262 
fill_pathname_home_dir(char * s,size_t len)1263 void fill_pathname_home_dir(char *s, size_t len)
1264 {
1265 #ifdef __WINRT__
1266    strlcpy(s, uwp_dir_data, len);
1267 #else
1268    const char *home = getenv("HOME");
1269    if (home)
1270       strlcpy(s, home, len);
1271    else
1272       *s = 0;
1273 #endif
1274 }
1275 #endif
1276 
is_path_accessible_using_standard_io(const char * path)1277 bool is_path_accessible_using_standard_io(const char *path)
1278 {
1279    bool result                = true;
1280 #ifdef __WINRT__
1281    size_t         path_sizeof = PATH_MAX_LENGTH * sizeof(char);
1282    char *relative_path_abbrev = (char*)malloc(path_sizeof);
1283    fill_pathname_abbreviate_special(relative_path_abbrev, path, path_sizeof);
1284 
1285    result = (strlen(relative_path_abbrev) >= 2 )
1286       && (relative_path_abbrev[0] == ':' || relative_path_abbrev[0] == '~')
1287       && PATH_CHAR_IS_SLASH(relative_path_abbrev[1]);
1288 
1289    free(relative_path_abbrev);
1290 #endif
1291    return result;
1292 }
1293