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