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