| 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 | } |