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