path.c (72880B)
1 #include <assert.h> 2 #include <limits.h> 3 #include <stdbool.h> 4 #include <stddef.h> 5 #include <stdint.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #ifdef MSWIN 9 # include <direct.h> 10 #endif 11 12 #include "auto/config.h" 13 #include "nvim/ascii_defs.h" 14 #include "nvim/buffer_defs.h" 15 #include "nvim/charset.h" 16 #include "nvim/cmdexpand.h" 17 #include "nvim/eval.h" 18 #include "nvim/ex_docmd.h" 19 #include "nvim/fileio.h" 20 #include "nvim/garray.h" 21 #include "nvim/globals.h" 22 #include "nvim/macros_defs.h" 23 #include "nvim/mbyte.h" 24 #include "nvim/memory.h" 25 #include "nvim/option.h" 26 #include "nvim/option_vars.h" 27 #include "nvim/os/fs.h" 28 #include "nvim/os/fs_defs.h" 29 #include "nvim/os/input.h" 30 #include "nvim/os/os.h" 31 #include "nvim/os/os_defs.h" 32 #include "nvim/os/shell.h" 33 #include "nvim/path.h" 34 #include "nvim/regexp.h" 35 #include "nvim/regexp_defs.h" 36 #include "nvim/strings.h" 37 #include "nvim/vim_defs.h" 38 39 enum { 40 URL_SLASH = 1, // path_is_url() has found ":/" 41 URL_BACKSLASH = 2, // path_is_url() has found ":\\" 42 }; 43 44 #ifdef gen_expand_wildcards 45 # undef gen_expand_wildcards 46 #endif 47 48 #include "path.c.generated.h" 49 50 /// Compare two file names. 51 /// 52 /// @param s1 First file name. Environment variables in this name will be expanded. 53 /// @param s2 Second file name. 54 /// @param checkname When both files don't exist, only compare their names. 55 /// @param expandenv Whether to expand environment variables in file names. 56 /// @return Enum of type FileComparison. @see FileComparison. 57 FileComparison path_full_compare(char *const s1, char *const s2, const bool checkname, 58 const bool expandenv) 59 FUNC_ATTR_NONNULL_ALL 60 { 61 char expanded1[MAXPATHL]; 62 char full1[MAXPATHL]; 63 char full2[MAXPATHL]; 64 FileID file_id_1, file_id_2; 65 66 if (expandenv) { 67 expand_env(s1, expanded1, MAXPATHL); 68 } else { 69 xstrlcpy(expanded1, s1, MAXPATHL); 70 } 71 bool id_ok_1 = os_fileid(expanded1, &file_id_1); 72 bool id_ok_2 = os_fileid(s2, &file_id_2); 73 if (!id_ok_1 && !id_ok_2) { 74 // If os_fileid() doesn't work, may compare the names. 75 if (checkname) { 76 vim_FullName(expanded1, full1, MAXPATHL, false); 77 vim_FullName(s2, full2, MAXPATHL, false); 78 if (path_fnamecmp(full1, full2) == 0) { 79 return kEqualFileNames; 80 } 81 } 82 return kBothFilesMissing; 83 } 84 if (!id_ok_1 || !id_ok_2) { 85 return kOneFileMissing; 86 } 87 if (os_fileid_equal(&file_id_1, &file_id_2)) { 88 return kEqualFiles; 89 } 90 return kDifferentFiles; 91 } 92 93 /// Gets the tail (filename segment) of path `fname`. 94 /// 95 /// Examples: 96 /// - "dir/file.txt" => "file.txt" 97 /// - "file.txt" => "file.txt" 98 /// - "dir/" => "" 99 /// 100 /// @return pointer just past the last path separator (empty string, if fname 101 /// ends in a slash), or empty string if fname is NULL. 102 char *path_tail(const char *fname) 103 FUNC_ATTR_NONNULL_RET 104 { 105 if (fname == NULL) { 106 return ""; 107 } 108 109 const char *tail = get_past_head(fname); 110 const char *p = tail; 111 // Find last part of path. 112 while (*p != NUL) { 113 if (vim_ispathsep_nocolon(*p)) { 114 tail = p + 1; 115 } 116 MB_PTR_ADV(p); 117 } 118 return (char *)tail; 119 } 120 121 /// Get pointer to tail of "fname", including path separators. 122 /// 123 /// Takes care of "c:/" and "//". That means `path_tail_with_sep("dir///file.txt")` 124 /// will return a pointer to `"///file.txt"`. 125 /// @param fname A file path. (Must be != NULL.) 126 /// @return 127 /// - Pointer to the last path separator of `fname`, if there is any. 128 /// - `fname` if it contains no path separator. 129 /// - Never NULL. 130 char *path_tail_with_sep(char *fname) 131 FUNC_ATTR_NONNULL_ALL 132 { 133 // Don't remove the '/' from "c:/file". 134 char *past_head = get_past_head(fname); 135 char *tail = path_tail(fname); 136 while (tail > past_head && after_pathsep(fname, tail)) { 137 tail--; 138 } 139 return tail; 140 } 141 142 /// Finds the path tail (or executable) in an invocation. 143 /// 144 /// @param[in] invocation A program invocation in the form: 145 /// "path/to/exe [args]". 146 /// @param[out] len Stores the length of the executable name. 147 /// 148 /// @post if `len` is not null, stores the length of the executable name. 149 /// 150 /// @return The position of the last path separator + 1. 151 const char *invocation_path_tail(const char *invocation, size_t *len) 152 FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) 153 { 154 const char *tail = get_past_head(invocation); 155 const char *p = tail; 156 while (*p != NUL && *p != ' ') { 157 bool was_sep = vim_ispathsep_nocolon(*p); 158 MB_PTR_ADV(p); 159 if (was_sep) { 160 tail = p; // Now tail points one past the separator. 161 } 162 } 163 164 if (len != NULL) { 165 *len = (size_t)(p - tail); 166 } 167 168 return tail; 169 } 170 171 /// Get the next path component of a path name. 172 /// 173 /// @param fname A file path. (Must be != NULL.) 174 /// @return Pointer to first found path separator + 1. 175 /// An empty string, if `fname` doesn't contain a path separator, 176 const char *path_next_component(const char *fname) 177 FUNC_ATTR_NONNULL_ALL 178 { 179 while (*fname != NUL && !vim_ispathsep(*fname)) { 180 MB_PTR_ADV(fname); 181 } 182 if (*fname != NUL) { 183 fname++; 184 } 185 return fname; 186 } 187 188 /// Returns the length of the path head on the current platform. 189 /// @return 190 /// - 3 on windows 191 /// - 1 otherwise 192 int path_head_length(void) 193 { 194 #ifdef MSWIN 195 return 3; 196 #else 197 return 1; 198 #endif 199 } 200 201 /// Returns true if path begins with characters denoting the head of a path 202 /// (e.g. '/' on linux and 'D:' on windows). 203 /// @param path The path to be checked. 204 /// @return 205 /// - True if path begins with a path head 206 /// - False otherwise 207 bool is_path_head(const char *path) 208 FUNC_ATTR_NONNULL_ALL 209 { 210 #ifdef MSWIN 211 return isalpha((uint8_t)path[0]) && path[1] == ':'; 212 #else 213 return vim_ispathsep(*path); 214 #endif 215 } 216 217 /// Get a pointer to one character past the head of a path name. 218 /// Unix: after "/"; Win: after "c:\" 219 /// If there is no head, path is returned. 220 char *get_past_head(const char *path) 221 FUNC_ATTR_NONNULL_ALL 222 { 223 const char *retval = path; 224 225 #ifdef MSWIN 226 // May skip "c:" 227 if (is_path_head(path)) { 228 retval = path + 2; 229 } 230 #endif 231 232 while (vim_ispathsep(*retval)) { 233 retval++; 234 } 235 236 return (char *)retval; 237 } 238 239 /// @return true if 'c' is a path separator. 240 /// Note that for MS-Windows this includes the colon. 241 bool vim_ispathsep(int c) 242 { 243 #ifdef UNIX 244 return c == '/'; // Unix has ':' inside file names 245 #else 246 # ifdef BACKSLASH_IN_FILENAME 247 return c == ':' || c == '/' || c == '\\'; 248 # else 249 return c == ':' || c == '/'; 250 # endif 251 #endif 252 } 253 254 // Like vim_ispathsep(c), but exclude the colon for MS-Windows. 255 bool vim_ispathsep_nocolon(int c) 256 { 257 return vim_ispathsep(c) 258 #ifdef BACKSLASH_IN_FILENAME 259 && c != ':' 260 #endif 261 ; 262 } 263 264 /// @return true if 'c' is a path list separator. 265 bool vim_ispathlistsep(int c) 266 { 267 #ifdef UNIX 268 return c == ':'; 269 #else 270 return c == ';'; // might not be right for every system... 271 #endif 272 } 273 274 /// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" 275 /// "trim_len" specifies how many characters to keep for each directory. 276 /// Must be 1 or more. 277 /// It's done in-place. 278 void shorten_dir_len(char *str, int trim_len) 279 FUNC_ATTR_NONNULL_ALL 280 { 281 char *tail = path_tail(str); 282 char *d = str; 283 bool skip = false; 284 int dirchunk_len = 0; 285 for (char *s = str;; s++) { 286 if (s >= tail) { // copy the whole tail 287 *d++ = *s; 288 if (*s == NUL) { 289 break; 290 } 291 } else if (vim_ispathsep(*s)) { // copy '/' and next char 292 *d++ = *s; 293 skip = false; 294 dirchunk_len = 0; 295 } else if (!skip) { 296 *d++ = *s; // copy next char 297 if (*s != '~' && *s != '.') { // and leading "~" and "." 298 dirchunk_len++; // only count word chars for the size 299 // keep copying chars until we have our preferred length (or 300 // until the above if/else branches move us along) 301 if (dirchunk_len >= trim_len) { 302 skip = true; 303 } 304 } 305 int l = utfc_ptr2len(s); 306 while (--l > 0) { 307 *d++ = *++s; 308 } 309 } 310 } 311 } 312 313 /// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" 314 /// It's done in-place. 315 void shorten_dir(char *str) 316 FUNC_ATTR_NONNULL_ALL 317 { 318 shorten_dir_len(str, 1); 319 } 320 321 /// Return true if the directory of "fname" exists, false otherwise. 322 /// Also returns true if there is no directory name. 323 /// "fname" must be writable!. 324 bool dir_of_file_exists(char *fname) 325 FUNC_ATTR_NONNULL_ALL 326 { 327 char *p = path_tail_with_sep(fname); 328 if (p == fname) { 329 return true; 330 } 331 char c = *p; 332 *p = NUL; 333 bool retval = os_isdir(fname); 334 *p = c; 335 return retval; 336 } 337 338 /// Compare two file names 339 /// 340 /// On some systems case in a file name does not matter, on others it does. 341 /// 342 /// @note Does not account for maximum name lengths and things like "../dir", 343 /// thus it is not 100% accurate. OS may also use different algorithm for 344 /// case-insensitive comparison. 345 /// 346 /// Handles '/' and '\\' correctly and deals with &fileignorecase option. 347 /// 348 /// @param[in] fname1 First file name. 349 /// @param[in] fname2 Second file name. 350 /// 351 /// @return 0 if they are equal, non-zero otherwise. 352 int path_fnamecmp(const char *fname1, const char *fname2) 353 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT 354 { 355 #ifdef BACKSLASH_IN_FILENAME 356 const size_t len1 = strlen(fname1); 357 const size_t len2 = strlen(fname2); 358 return path_fnamencmp(fname1, fname2, MAX(len1, len2)); 359 #else 360 return mb_strcmp_ic((bool)p_fic, fname1, fname2); 361 #endif 362 } 363 364 /// Compare two file names 365 /// 366 /// Handles '/' and '\\' correctly and deals with &fileignorecase option. 367 /// 368 /// @param[in] fname1 First file name. 369 /// @param[in] fname2 Second file name. 370 /// @param[in] len Compare at most len bytes. 371 /// 372 /// @return 0 if they are equal, non-zero otherwise. 373 int path_fnamencmp(const char *const fname1, const char *const fname2, size_t len) 374 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT 375 { 376 #ifdef BACKSLASH_IN_FILENAME 377 int c1 = NUL; 378 int c2 = NUL; 379 380 const char *p1 = fname1; 381 const char *p2 = fname2; 382 383 # ifdef MSWIN 384 // To allow proper comparison of absolute paths: 385 // - one with explicit drive letter C:\xxx 386 // - another with implicit drive letter \xxx 387 // advance the pointer, of the explicit one, to skip the drive 388 for (int swap = 0, drive = NUL; swap < 2; swap++) { 389 // Handle absolute paths with implicit drive letter 390 c1 = utf_ptr2char(p1); 391 c2 = utf_ptr2char(p2); 392 393 if ((c1 == '/' || c1 == '\\') && ASCII_ISALPHA(c2)) { 394 drive = mb_toupper(c2) - 'A' + 1; 395 396 // Check for the colon 397 p2 += utfc_ptr2len(p2); 398 c2 = utf_ptr2char(p2); 399 if (c2 == ':' && drive == _getdrive()) { // skip the drive for comparison 400 p2 += utfc_ptr2len(p2); 401 break; 402 } else { // ignore 403 p2 -= utfc_ptr2len(p2); 404 } 405 } 406 407 // swap pointers 408 const char *tmp = p1; 409 p1 = p2; 410 p2 = tmp; 411 } 412 # endif 413 414 while (len > 0) { 415 c1 = utf_ptr2char(p1); 416 c2 = utf_ptr2char(p2); 417 if ((c1 == NUL || c2 == NUL 418 || (!((c1 == '/' || c1 == '\\') && (c2 == '\\' || c2 == '/')))) 419 && (p_fic ? (c1 != c2 && utf_fold(c1) != utf_fold(c2)) : c1 != c2)) { 420 break; 421 } 422 len -= (size_t)utfc_ptr2len(p1); 423 p1 += utfc_ptr2len(p1); 424 p2 += utfc_ptr2len(p2); 425 } 426 return p_fic ? utf_fold(c1) - utf_fold(c2) : c1 - c2; 427 #else 428 if (p_fic) { 429 return mb_strnicmp(fname1, fname2, len); 430 } 431 return strncmp(fname1, fname2, len); 432 #endif 433 } 434 435 /// Append fname2 to fname1 436 /// 437 /// @param[in] fname1 First fname to append to. 438 /// @param[in] len1 Length of fname1. 439 /// @param[in] fname2 Second part of the file name. 440 /// @param[in] len2 Length of fname2. 441 /// @param[in] sep If true and fname1 does not end with a path separator, 442 /// add a path separator before fname2. 443 /// 444 /// @return fname1 445 static inline char *do_concat_fnames(char *fname1, const size_t len1, const char *fname2, 446 const size_t len2, const bool sep) 447 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET 448 { 449 if (sep && *fname1 && !after_pathsep(fname1, fname1 + len1)) { 450 fname1[len1] = PATHSEP; 451 memmove(fname1 + len1 + 1, fname2, len2 + 1); 452 } else { 453 memmove(fname1 + len1, fname2, len2 + 1); 454 } 455 456 return fname1; 457 } 458 459 /// Concatenate file names fname1 and fname2 into allocated memory. 460 /// 461 /// Only add a '/' or '\\' when 'sep' is true and it is necessary. 462 /// 463 /// @param fname1 is the first part of the path or filename 464 /// @param fname2 is the second half of the path or filename 465 /// @param sep is a flag to indicate a path separator should be added 466 /// if necessary 467 /// @return [allocated] Concatenation of fname1 and fname2. 468 char *concat_fnames(const char *fname1, const char *fname2, bool sep) 469 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET 470 { 471 const size_t len1 = strlen(fname1); 472 const size_t len2 = strlen(fname2); 473 char *dest = xmalloc(len1 + len2 + 3); 474 memmove(dest, fname1, len1 + 1); 475 return do_concat_fnames(dest, len1, fname2, len2, sep); 476 } 477 478 /// Concatenate file names fname1 and fname2 479 /// 480 /// Like concat_fnames(), but in place of allocating new memory it reallocates 481 /// fname1. For this reason fname1 must be allocated with xmalloc, and can no 482 /// longer be used after running concat_fnames_realloc. 483 /// 484 /// @param fname1 is the first part of the path or filename 485 /// @param fname2 is the second half of the path or filename 486 /// @param sep is a flag to indicate a path separator should be added 487 /// if necessary 488 /// @return [allocated] Concatenation of fname1 and fname2. 489 char *concat_fnames_realloc(char *fname1, const char *fname2, bool sep) 490 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET 491 { 492 const size_t len1 = strlen(fname1); 493 const size_t len2 = strlen(fname2); 494 return do_concat_fnames(xrealloc(fname1, len1 + len2 + 3), len1, 495 fname2, len2, sep); 496 } 497 498 /// Adds a path separator to a filename, unless it already ends in one. 499 /// 500 /// @return `true` if the path separator was added or already existed. 501 /// `false` if the filename is too long. 502 bool add_pathsep(char *p) 503 FUNC_ATTR_NONNULL_ALL 504 { 505 const size_t len = strlen(p); 506 if (*p != NUL && !after_pathsep(p, p + len)) { 507 const size_t pathsep_len = sizeof(PATHSEPSTR); 508 if (len > MAXPATHL - pathsep_len) { 509 return false; 510 } 511 memcpy(p + len, PATHSEPSTR, pathsep_len); 512 } 513 return true; 514 } 515 516 /// Get an allocated copy of the full path to a file. 517 /// 518 /// @param fname is the filename to save 519 /// @param force is a flag to expand `fname` even if it looks absolute 520 /// 521 /// @return [allocated] Copy of absolute path to `fname` or NULL when 522 /// `fname` is NULL. 523 char *FullName_save(const char *fname, bool force) 524 FUNC_ATTR_MALLOC 525 { 526 if (fname == NULL) { 527 return NULL; 528 } 529 530 char *buf = xmalloc(MAXPATHL); 531 if (vim_FullName(fname, buf, MAXPATHL, force) == FAIL) { 532 xfree(buf); 533 return xstrdup(fname); 534 } 535 return buf; 536 } 537 538 /// Saves the absolute path. 539 /// @param name An absolute or relative path. 540 /// @return The absolute path of `name`. 541 char *save_abs_path(const char *name) 542 FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL 543 { 544 if (!path_is_absolute(name)) { 545 return FullName_save(name, true); 546 } 547 return xstrdup(name); 548 } 549 550 /// Checks if a path has a wildcard character including '~', unless at the end. 551 /// @param p The path to expand. 552 /// @returns Unix: True if it contains one of "?[{`'$". 553 /// @returns Windows: True if it contains one of "*?$[". 554 bool path_has_wildcard(const char *p) 555 FUNC_ATTR_NONNULL_ALL 556 { 557 for (; *p; MB_PTR_ADV(p)) { 558 #if defined(UNIX) 559 if (p[0] == '\\' && p[1] != NUL) { 560 p++; 561 continue; 562 } 563 564 const char *wildcards = "*?[{`'$"; 565 #else 566 // Windows: 567 const char *wildcards = "?*$[`"; 568 #endif 569 if (vim_strchr(wildcards, (uint8_t)(*p)) != NULL 570 || (p[0] == '~' && p[1] != NUL)) { 571 return true; 572 } 573 } 574 return false; 575 } 576 577 static int pstrcmp(const void *a, const void *b) 578 { 579 return pathcmp(*(char **)a, *(char **)b, -1); 580 } 581 582 /// Checks if a path has a character path_expand can expand. 583 /// @param p The path to expand. 584 /// @returns Unix: True if it contains one of *?[{. 585 /// @returns Windows: True if it contains one of *?[. 586 bool path_has_exp_wildcard(const char *p) 587 FUNC_ATTR_NONNULL_ALL 588 { 589 for (; *p != NUL; MB_PTR_ADV(p)) { 590 #if defined(UNIX) 591 if (p[0] == '\\' && p[1] != NUL) { 592 p++; 593 continue; 594 } 595 596 const char *wildcards = "*?[{"; 597 #else 598 const char *wildcards = "*?["; // Windows. 599 #endif 600 if (vim_strchr(wildcards, (uint8_t)(*p)) != NULL) { 601 return true; 602 } 603 } 604 return false; 605 } 606 607 /// Recursively expands one path component into all matching files and/or 608 /// directories. Handles "*", "?", "[a-z]", "**", etc. 609 /// @remark "**" in `path` requests recursive expansion. 610 /// 611 /// @param[out] gap The matches found. 612 /// @param path The path to search. 613 /// @param flags Flags for regexp expansion. 614 /// - EW_ICASE: Ignore case. 615 /// - EW_NOERROR: Silence error messages. 616 /// - EW_NOTWILD: Add matches literally. 617 /// @returns the number of matches found. 618 static size_t path_expand(garray_T *gap, const char *path, int flags) 619 FUNC_ATTR_NONNULL_ALL 620 { 621 return do_path_expand(gap, path, 0, flags, false); 622 } 623 624 static const char *scandir_next_with_dots(Directory *dir) 625 { 626 static int count = 0; 627 if (dir == NULL) { // initialize 628 count = 0; 629 return NULL; 630 } 631 632 count += 1; 633 if (count == 1 || count == 2) { 634 return (count == 1) ? "." : ".."; 635 } 636 return os_scandir_next(dir); 637 } 638 639 /// Implementation of path_expand(). 640 /// 641 /// Chars before `path + wildoff` do not get expanded. 642 static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, int flags, 643 bool didstar) 644 FUNC_ATTR_NONNULL_ALL 645 { 646 int start_len = gap->ga_len; 647 bool starstar = false; 648 static int stardepth = 0; // depth for "**" expansion 649 650 // Expanding "**" may take a long time, check for CTRL-C. 651 if (stardepth > 0 && !(flags & EW_NOBREAK)) { 652 os_breakcheck(); 653 if (got_int) { 654 return 0; 655 } 656 } 657 658 // Make room for file name (a bit too much to stay on the safe side). 659 const size_t buflen = strlen(path) + MAXPATHL; 660 char *buf = xmalloc(buflen); 661 662 // Find the first part in the path name that contains a wildcard. 663 // When EW_ICASE is set every letter is considered to be a wildcard. 664 // Copy it into "buf", including the preceding characters. 665 char *p = buf; 666 char *s = buf; 667 char *e = NULL; 668 const char *path_end = path; 669 while (*path_end != NUL) { 670 // May ignore a wildcard that has a backslash before it; it will 671 // be removed by rem_backslash() or file_pat_to_reg_pat() below. 672 if (path_end >= path + wildoff && rem_backslash(path_end)) { 673 *p++ = *path_end++; 674 } else if (vim_ispathsep_nocolon(*path_end)) { 675 if (e != NULL) { 676 break; 677 } 678 s = p + 1; 679 } else if (path_end >= path + wildoff 680 #ifdef MSWIN 681 && vim_strchr("*?[~", (uint8_t)(*path_end)) != NULL 682 #else 683 && (vim_strchr("*?[{~$", (uint8_t)(*path_end)) != NULL 684 || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char(path_end)))) 685 #endif 686 ) { 687 e = p; 688 } 689 int charlen = utfc_ptr2len(path_end); 690 memcpy(p, path_end, (size_t)charlen); 691 p += charlen; 692 path_end += charlen; 693 } 694 e = p; 695 *e = NUL; 696 697 // Now we have one wildcard component between "s" and "e". 698 // Remove backslashes between "wildoff" and the start of the wildcard 699 // component. 700 for (p = buf + wildoff; p < s; p++) { 701 if (rem_backslash(p)) { 702 STRMOVE(p, p + 1); 703 e--; 704 s--; 705 } 706 } 707 708 // Check for "**" between "s" and "e". 709 for (p = s; p < e; p++) { 710 if (p[0] == '*' && p[1] == '*') { 711 starstar = true; 712 } 713 } 714 715 // convert the file pattern to a regexp pattern 716 int starts_with_dot = *s == '.'; 717 char *pat = file_pat_to_reg_pat(s, e, NULL, false); 718 if (pat == NULL) { 719 xfree(buf); 720 return 0; 721 } 722 723 // compile the regexp into a program 724 regmatch_T regmatch; 725 #if defined(UNIX) 726 // Ignore case if given 'wildignorecase', else respect 'fileignorecase'. 727 regmatch.rm_ic = (flags & EW_ICASE) || p_fic; 728 #else 729 regmatch.rm_ic = true; // Always ignore case on Windows. 730 #endif 731 if (flags & (EW_NOERROR | EW_NOTWILD)) { 732 emsg_silent++; 733 } 734 bool nobreak = (flags & EW_NOBREAK); 735 regmatch.regprog = vim_regcomp(pat, RE_MAGIC | (nobreak ? RE_NOBREAK : 0)); 736 if (flags & (EW_NOERROR | EW_NOTWILD)) { 737 emsg_silent--; 738 } 739 xfree(pat); 740 741 if (regmatch.regprog == NULL && (flags & EW_NOTWILD) == 0) { 742 xfree(buf); 743 return 0; 744 } 745 746 size_t len = (size_t)(s - buf); 747 // If "**" is by itself, this is the first time we encounter it and more 748 // is following then find matches without any directory. 749 if (!didstar && stardepth < 100 && starstar && e - s == 2 750 && *path_end == '/') { 751 vim_snprintf(s, buflen - len, "%s", path_end + 1); 752 stardepth++; 753 do_path_expand(gap, buf, len, flags, true); 754 stardepth--; 755 } 756 *s = NUL; 757 758 Directory dir; 759 char *dirpath = (*buf == NUL ? "." : buf); 760 if (os_file_is_readable(dirpath) && os_scandir(&dir, dirpath)) { 761 // Find all matching entries. 762 const char *name; 763 scandir_next_with_dots(NULL); // initialize 764 while (!got_int && (name = scandir_next_with_dots(&dir)) != NULL) { 765 len = (size_t)(s - buf); 766 if ((name[0] != '.' 767 || starts_with_dot 768 || ((flags & EW_DODOT) 769 && name[1] != NUL 770 && (name[1] != '.' || name[2] != NUL))) 771 && ((regmatch.regprog != NULL && vim_regexec(®match, name, 0)) 772 || ((flags & EW_NOTWILD) 773 && path_fnamencmp(path + len, name, (size_t)(e - s)) == 0))) { 774 len += (size_t)vim_snprintf(s, buflen - len, "%s", name); 775 if (len + 1 >= buflen) { 776 continue; 777 } 778 779 if (starstar && stardepth < 100) { 780 // For "**" in the pattern first go deeper in the tree to 781 // find matches. 782 vim_snprintf(buf + len, buflen - len, "/**%s", path_end); // NOLINT 783 stardepth++; 784 do_path_expand(gap, buf, len + 1, flags, true); 785 stardepth--; 786 } 787 788 vim_snprintf(buf + len, buflen - len, "%s", path_end); 789 if (path_has_exp_wildcard(path_end)) { // handle more wildcards 790 if (stardepth < 100) { 791 stardepth++; 792 // need to expand another component of the path 793 // remove backslashes for the remaining components only 794 do_path_expand(gap, buf, len + 1, flags, false); 795 stardepth--; 796 } 797 } else { 798 FileInfo file_info; 799 800 // no more wildcards, check if there is a match 801 // remove backslashes for the remaining components only 802 if (*path_end != NUL) { 803 backslash_halve(buf + len + 1); 804 } 805 // add existing file or symbolic link 806 if ((flags & EW_ALLLINKS) 807 ? os_fileinfo_link(buf, &file_info) 808 : os_path_exists(buf)) { 809 addfile(gap, buf, flags); 810 } 811 } 812 } 813 } 814 os_closedir(&dir); 815 } 816 817 xfree(buf); 818 vim_regfree(regmatch.regprog); 819 820 // When interrupted the matches probably won't be used and sorting can be 821 // slow, thus skip it. 822 size_t matches = (size_t)(gap->ga_len - start_len); 823 if (matches > 0 && !got_int) { 824 qsort(((char **)gap->ga_data) + start_len, matches, 825 sizeof(char *), pstrcmp); 826 } 827 return matches; 828 } 829 830 // Moves "*psep" back to the previous path separator in "path". 831 // Returns FAIL is "*psep" ends up at the beginning of "path". 832 static int find_previous_pathsep(char *path, char **psep) 833 FUNC_ATTR_NONNULL_ALL 834 { 835 // skip the current separator 836 if (*psep > path && vim_ispathsep(**psep)) { 837 (*psep)--; 838 } 839 840 // find the previous separator 841 while (*psep > path) { 842 if (vim_ispathsep(**psep)) { 843 return OK; 844 } 845 MB_PTR_BACK(path, *psep); 846 } 847 848 return FAIL; 849 } 850 851 /// Returns true if "maybe_unique" is unique wrt other_paths in "gap". 852 /// "maybe_unique" is the end portion of "((char **)gap->ga_data)[i]". 853 static bool is_unique(char *maybe_unique, garray_T *gap, int i) 854 FUNC_ATTR_NONNULL_ALL 855 { 856 size_t candidate_len = strlen(maybe_unique); 857 char **other_paths = gap->ga_data; 858 859 for (int j = 0; j < gap->ga_len; j++) { 860 if (j == i) { 861 continue; // don't compare it with itself 862 } 863 size_t other_path_len = strlen(other_paths[j]); 864 if (other_path_len < candidate_len) { 865 continue; // it's different when it's shorter 866 } 867 char *rival = other_paths[j] + other_path_len - candidate_len; 868 if (path_fnamecmp(maybe_unique, rival) == 0 869 && (rival == other_paths[j] || vim_ispathsep(*(rival - 1)))) { 870 return false; // match 871 } 872 } 873 return true; // no match found 874 } 875 876 /// Split the 'path' option into an array of strings in garray_T. Relative 877 /// paths are expanded to their equivalent fullpath. This includes the "." 878 /// (relative to current buffer directory) and empty path (relative to current 879 /// directory) notations. 880 /// 881 /// @param path_option p_path or p_cdpath 882 /// 883 /// TODO(vim): handle upward search (;) and path limiter (**N) notations by 884 /// expanding each into their equivalent path(s). 885 static void expand_path_option(char *curdir, char *path_option, garray_T *gap) 886 FUNC_ATTR_NONNULL_ALL 887 { 888 char *buf = xmalloc(MAXPATHL); 889 size_t curdirlen = 0; 890 891 while (*path_option != NUL) { 892 size_t buflen = copy_option_part(&path_option, buf, MAXPATHL, " ,"); 893 894 if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1]))) { 895 // Relative to current buffer: 896 // "/path/file" + "." -> "/path/" 897 // "/path/file" + "./subdir" -> "/path/subdir" 898 if (curbuf->b_ffname == NULL) { 899 continue; 900 } 901 char *p = path_tail(curbuf->b_ffname); 902 size_t plen = (size_t)(p - curbuf->b_ffname); 903 if (plen + strlen(buf) >= MAXPATHL) { 904 continue; 905 } 906 if (buf[1] == NUL) { 907 buf[plen] = NUL; 908 } else { 909 memmove(buf + plen, buf + 2, (buflen - 2) + 1); // +1 for NUL 910 } 911 memmove(buf, curbuf->b_ffname, plen); 912 buflen = simplify_filename(buf); 913 } else if (buf[0] == NUL) { 914 STRCPY(buf, curdir); // relative to current directory 915 if (curdirlen == 0) { 916 curdirlen = strlen(curdir); 917 } 918 buflen = curdirlen; 919 } else if (path_with_url(buf)) { 920 continue; // URL can't be used here 921 } else if (!path_is_absolute(buf)) { 922 // Expand relative path to their full path equivalent 923 if (curdirlen == 0) { 924 curdirlen = strlen(curdir); 925 } 926 if (curdirlen + buflen + 3 > MAXPATHL) { 927 continue; 928 } 929 memmove(buf + curdirlen + 1, buf, buflen + 1); // +1 for NUL 930 STRCPY(buf, curdir); 931 buf[curdirlen] = PATHSEP; 932 buflen = simplify_filename(buf); 933 } 934 935 GA_APPEND(char *, gap, xmemdupz(buf, buflen)); 936 } 937 938 xfree(buf); 939 } 940 941 // Returns a pointer to the file or directory name in "fname" that matches the 942 // longest path in "ga"p, or NULL if there is no match. For example: 943 // 944 // path: /foo/bar/baz 945 // fname: /foo/bar/baz/quux.txt 946 // returns: ^this 947 static char *get_path_cutoff(char *fname, garray_T *gap) 948 FUNC_ATTR_NONNULL_ALL 949 { 950 int maxlen = 0; 951 char **path_part = gap->ga_data; 952 char *cutoff = NULL; 953 954 for (int i = 0; i < gap->ga_len; i++) { 955 int j = 0; 956 957 while ((fname[j] == path_part[i][j] 958 #ifdef MSWIN 959 || (vim_ispathsep(fname[j]) && vim_ispathsep(path_part[i][j])) 960 #endif 961 ) 962 && fname[j] != NUL && path_part[i][j] != NUL) { 963 j++; 964 } 965 if (j > maxlen) { 966 maxlen = j; 967 cutoff = &fname[j]; 968 } 969 } 970 971 // skip to the file or directory name 972 if (cutoff != NULL) { 973 while (vim_ispathsep(*cutoff)) { 974 MB_PTR_ADV(cutoff); 975 } 976 } 977 978 return cutoff; 979 } 980 981 /// Sorts, removes duplicates and modifies all the fullpath names in "gap" so 982 /// that they are unique with respect to each other while conserving the part 983 /// that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len". 984 /// 985 /// @param path_option p_path or p_cdpath 986 static void uniquefy_paths(garray_T *gap, char *pattern, char *path_option) 987 FUNC_ATTR_NONNULL_ALL 988 { 989 char **fnames = gap->ga_data; 990 bool sort_again = false; 991 regmatch_T regmatch; 992 garray_T path_ga; 993 char **in_curdir = NULL; 994 char *short_name; 995 996 ga_remove_duplicate_strings(gap); 997 ga_init(&path_ga, (int)sizeof(char *), 1); 998 999 // We need to prepend a '*' at the beginning of file_pattern so that the 1000 // regex matches anywhere in the path. FIXME: is this valid for all 1001 // possible patterns? 1002 size_t len = strlen(pattern); 1003 char *file_pattern = xmalloc(len + 2); 1004 file_pattern[0] = '*'; 1005 file_pattern[1] = NUL; 1006 STRCPY(file_pattern + 1, pattern); 1007 char *pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, false); 1008 xfree(file_pattern); 1009 if (pat == NULL) { 1010 return; 1011 } 1012 1013 regmatch.rm_ic = true; // always ignore case 1014 regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); 1015 xfree(pat); 1016 if (regmatch.regprog == NULL) { 1017 return; 1018 } 1019 1020 char *curdir = xmalloc(MAXPATHL); 1021 os_dirname(curdir, MAXPATHL); 1022 expand_path_option(curdir, path_option, &path_ga); 1023 1024 in_curdir = xcalloc((size_t)gap->ga_len, sizeof(char *)); 1025 1026 for (int i = 0; i < gap->ga_len && !got_int; i++) { 1027 char *path = fnames[i]; 1028 const char *dir_end = gettail_dir(path); 1029 1030 len = strlen(path); 1031 bool is_in_curdir = path_fnamencmp(curdir, path, (size_t)(dir_end - path)) == 0 1032 && curdir[dir_end - path] == NUL; 1033 if (is_in_curdir) { 1034 in_curdir[i] = xmemdupz(path, len); 1035 } 1036 1037 // Shorten the filename while maintaining its uniqueness 1038 char *path_cutoff = get_path_cutoff(path, &path_ga); 1039 1040 // Don't assume all files can be reached without path when search 1041 // pattern starts with **/, so only remove path_cutoff 1042 // when possible. 1043 if (pattern[0] == '*' && pattern[1] == '*' 1044 && vim_ispathsep_nocolon(pattern[2]) 1045 && path_cutoff != NULL 1046 && vim_regexec(®match, path_cutoff, 0) 1047 && is_unique(path_cutoff, gap, i)) { 1048 sort_again = true; 1049 memmove(path, path_cutoff, strlen(path_cutoff) + 1); 1050 } else { 1051 // Here all files can be reached without path, so get shortest 1052 // unique path. We start at the end of the path. 1053 char *pathsep_p = path + len - 1; 1054 while (find_previous_pathsep(path, &pathsep_p)) { 1055 if (vim_regexec(®match, pathsep_p + 1, 0) 1056 && is_unique(pathsep_p + 1, gap, i) 1057 && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff) { 1058 sort_again = true; 1059 memmove(path, pathsep_p + 1, 1060 (size_t)((path + len) - (pathsep_p + 1)) + 1); // +1 for NUL 1061 break; 1062 } 1063 } 1064 } 1065 1066 if (path_is_absolute(path)) { 1067 // Last resort: shorten relative to curdir if possible. 1068 // 'possible' means: 1069 // 1. It is under the current directory. 1070 // 2. The result is actually shorter than the original. 1071 // 1072 // Before curdir After 1073 // /foo/bar/file.txt /foo/bar ./file.txt 1074 // c:\foo\bar\file.txt c:\foo\bar .\file.txt 1075 // /file.txt / /file.txt 1076 // c:\file.txt c:\ .\file.txt 1077 short_name = path_shorten_fname(path, curdir); 1078 if (short_name != NULL && short_name > path + 1) { 1079 vim_snprintf(path, MAXPATHL, ".%s%s", PATHSEPSTR, short_name); 1080 } 1081 } 1082 os_breakcheck(); 1083 } 1084 1085 // Shorten filenames in /in/current/directory/{filename} 1086 for (int i = 0; i < gap->ga_len && !got_int; i++) { 1087 char *path = in_curdir[i]; 1088 1089 if (path == NULL) { 1090 continue; 1091 } 1092 1093 // If the {filename} is not unique, change it to ./{filename}. 1094 // Else reduce it to {filename} 1095 short_name = path_shorten_fname(path, curdir); 1096 if (short_name == NULL) { 1097 short_name = path; 1098 } 1099 if (is_unique(short_name, gap, i)) { 1100 STRCPY(fnames[i], short_name); 1101 continue; 1102 } 1103 1104 size_t rel_pathsize = 1 + STRLEN_LITERAL(PATHSEPSTR) + strlen(short_name) + 1; 1105 char *rel_path = xmalloc(rel_pathsize); 1106 1107 vim_snprintf(rel_path, rel_pathsize, ".%s%s", PATHSEPSTR, short_name); 1108 1109 xfree(fnames[i]); 1110 fnames[i] = rel_path; 1111 sort_again = true; 1112 os_breakcheck(); 1113 } 1114 1115 xfree(curdir); 1116 for (int i = 0; i < gap->ga_len; i++) { 1117 xfree(in_curdir[i]); 1118 } 1119 xfree(in_curdir); 1120 ga_clear_strings(&path_ga); 1121 vim_regfree(regmatch.regprog); 1122 1123 if (sort_again) { 1124 ga_remove_duplicate_strings(gap); 1125 } 1126 } 1127 1128 /// Find end of the directory name 1129 /// 1130 /// @param[in] fname File name to process. 1131 /// 1132 /// @return end of the directory name, on the first path separator: 1133 /// 1134 /// "/path/file", "/path/dir/", "/path//dir", "/file" 1135 /// ^ ^ ^ ^ 1136 const char *gettail_dir(const char *const fname) 1137 FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL 1138 { 1139 const char *dir_end = fname; 1140 const char *next_dir_end = fname; 1141 bool look_for_sep = true; 1142 1143 for (const char *p = fname; *p != NUL;) { 1144 if (vim_ispathsep(*p)) { 1145 if (look_for_sep) { 1146 next_dir_end = p; 1147 look_for_sep = false; 1148 } 1149 } else { 1150 if (!look_for_sep) { 1151 dir_end = next_dir_end; 1152 } 1153 look_for_sep = true; 1154 } 1155 MB_PTR_ADV(p); 1156 } 1157 return dir_end; 1158 } 1159 1160 /// Calls globpath() with 'path' values for the given pattern and stores the 1161 /// result in "gap". 1162 /// Returns the total number of matches. 1163 /// 1164 /// @param flags EW_* flags 1165 static int expand_in_path(garray_T *const gap, char *const pattern, const int flags) 1166 FUNC_ATTR_NONNULL_ALL 1167 { 1168 garray_T path_ga; 1169 char *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; 1170 1171 char *const curdir = xmalloc(MAXPATHL); 1172 os_dirname(curdir, MAXPATHL); 1173 1174 ga_init(&path_ga, (int)sizeof(char *), 1); 1175 if (flags & EW_CDPATH) { 1176 expand_path_option(curdir, p_cdpath, &path_ga); 1177 } else { 1178 expand_path_option(curdir, path_option, &path_ga); 1179 } 1180 xfree(curdir); 1181 if (GA_EMPTY(&path_ga)) { 1182 return 0; 1183 } 1184 1185 char *const paths = ga_concat_strings(&path_ga, ","); 1186 ga_clear_strings(&path_ga); 1187 1188 int glob_flags = 0; 1189 if (flags & EW_ICASE) { 1190 glob_flags |= WILD_ICASE; 1191 } 1192 if (flags & EW_ADDSLASH) { 1193 glob_flags |= WILD_ADD_SLASH; 1194 } 1195 globpath(paths, pattern, gap, glob_flags, !!(flags & EW_CDPATH)); 1196 xfree(paths); 1197 1198 return gap->ga_len; 1199 } 1200 1201 /// Return true if "p" contains what looks like an environment variable. 1202 /// Allowing for escaping. 1203 static bool has_env_var(char *p) 1204 FUNC_ATTR_NONNULL_ALL 1205 { 1206 for (; *p; MB_PTR_ADV(p)) { 1207 if (*p == '\\' && p[1] != NUL) { 1208 p++; 1209 } else if (vim_strchr("$", (uint8_t)(*p)) != NULL) { 1210 return true; 1211 } 1212 } 1213 return false; 1214 } 1215 1216 #ifdef SPECIAL_WILDCHAR 1217 1218 // Return true if "p" contains a special wildcard character, one that Vim 1219 // cannot expand, requires using a shell. 1220 static bool has_special_wildchar(char *p, int flags) 1221 FUNC_ATTR_NONNULL_ALL 1222 { 1223 for (; *p; MB_PTR_ADV(p)) { 1224 // Disallow line break characters. 1225 if (*p == '\r' || *p == '\n') { 1226 break; 1227 } 1228 // Allow for escaping. 1229 if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') { 1230 p++; 1231 } else if (vim_strchr(SPECIAL_WILDCHAR, (uint8_t)(*p)) != NULL) { 1232 // Need a shell for curly braces only when including non-existing files. 1233 if (*p == '{' && !(flags & EW_NOTFOUND)) { 1234 continue; 1235 } 1236 // A { must be followed by a matching }. 1237 if (*p == '{' && vim_strchr(p, '}') == NULL) { 1238 continue; 1239 } 1240 // A quote and backtick must be followed by another one. 1241 if ((*p == '`' || *p == '\'') && vim_strchr(p, (uint8_t)(*p)) == NULL) { 1242 continue; 1243 } 1244 return true; 1245 } 1246 } 1247 return false; 1248 } 1249 #endif 1250 1251 /// Generic wildcard expansion code. 1252 /// 1253 /// Characters in pat that should not be expanded must be preceded with a 1254 /// backslash. E.g., "/path\ with\ spaces/my\*star*". 1255 /// 1256 /// @param num_pat is number of input patterns. 1257 /// @param pat is an array of pointers to input patterns. 1258 /// @param[out] num_file is pointer to number of matched file names. 1259 /// @param[out] file is pointer to array of pointers to matched file names. 1260 /// @param flags is a combination of EW_* flags used in 1261 /// expand_wildcards(). 1262 /// 1263 /// @returns OK when some files were found. *num_file is set to the 1264 /// number of matches, *file to the allocated array of 1265 /// matches. Call FreeWild() later. 1266 /// If FAIL is returned, *num_file and *file are either 1267 /// unchanged or *num_file is set to 0 and *file is set 1268 /// to NULL or points to "". 1269 int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, int flags) 1270 { 1271 garray_T ga; 1272 char *p; 1273 static bool recursive = false; 1274 int add_pat; 1275 bool did_expand_in_path = false; 1276 char *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; 1277 1278 // expand_env() is called to expand things like "~user". If this fails, 1279 // it calls ExpandOne(), which brings us back here. In this case, always 1280 // call the machine specific expansion function, if possible. Otherwise, 1281 // return FAIL. 1282 if (recursive) { 1283 #ifdef SPECIAL_WILDCHAR 1284 return os_expand_wildcards(num_pat, pat, num_file, file, flags); 1285 #else 1286 return FAIL; 1287 #endif 1288 } 1289 1290 #ifdef SPECIAL_WILDCHAR 1291 // If there are any special wildcard characters which we cannot handle 1292 // here, call machine specific function for all the expansion. This 1293 // avoids starting the shell for each argument separately. 1294 // For `=expr` do use the internal function. 1295 for (int i = 0; i < num_pat; i++) { 1296 if (has_special_wildchar(pat[i], flags) 1297 && !(vim_backtick(pat[i]) && pat[i][1] == '=')) { 1298 return os_expand_wildcards(num_pat, pat, num_file, file, flags); 1299 } 1300 } 1301 #endif 1302 1303 recursive = true; 1304 1305 // The matching file names are stored in a growarray. Init it empty. 1306 ga_init(&ga, (int)sizeof(char *), 30); 1307 1308 for (int i = 0; i < num_pat && !got_int; i++) { 1309 add_pat = -1; 1310 p = pat[i]; 1311 1312 if (vim_backtick(p)) { 1313 add_pat = expand_backtick(&ga, p, flags); 1314 if (add_pat == -1) { 1315 recursive = false; 1316 ga_clear_strings(&ga); 1317 *num_file = 0; 1318 *file = NULL; 1319 return FAIL; 1320 } 1321 } else { 1322 // First expand environment variables, "~/" and "~user/". 1323 if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~') { 1324 p = expand_env_save_opt(p, true); 1325 if (p == NULL) { 1326 p = pat[i]; 1327 } else { 1328 #ifdef UNIX 1329 // On Unix, if expand_env() can't expand an environment 1330 // variable, use the shell to do that. Discard previously 1331 // found file names and start all over again. 1332 if (has_env_var(p) || *p == '~') { 1333 xfree(p); 1334 ga_clear_strings(&ga); 1335 i = os_expand_wildcards(num_pat, pat, num_file, file, 1336 flags | EW_KEEPDOLLAR); 1337 recursive = false; 1338 return i; 1339 } 1340 #endif 1341 } 1342 } 1343 1344 // If there are wildcards or case-insensitive expansion is 1345 // required: Expand file names and add each match to the list. If 1346 // there is no match, and EW_NOTFOUND is given, add the pattern. 1347 // Otherwise: Add the file name if it exists or when EW_NOTFOUND is 1348 // given. 1349 if (path_has_exp_wildcard(p) || (flags & EW_ICASE)) { 1350 if ((flags & (EW_PATH | EW_CDPATH)) 1351 && !path_is_absolute(p) 1352 && !(p[0] == '.' 1353 && (vim_ispathsep(p[1]) 1354 || (p[1] == '.' 1355 && vim_ispathsep(p[2]))))) { 1356 // :find completion where 'path' is used. 1357 // Recursiveness is OK here. 1358 recursive = false; 1359 add_pat = expand_in_path(&ga, p, flags); 1360 recursive = true; 1361 did_expand_in_path = true; 1362 } else { 1363 // Recursive gen_expand_wildcards() can only happen here when called from an 1364 // event handler in os_breakcheck(), in which case it should be allowed. 1365 recursive = false; 1366 size_t tmp_add_pat = path_expand(&ga, p, flags); 1367 recursive = true; 1368 assert(tmp_add_pat <= INT_MAX); 1369 add_pat = (int)tmp_add_pat; 1370 } 1371 } 1372 } 1373 1374 if (add_pat == -1 || (add_pat == 0 && (flags & EW_NOTFOUND))) { 1375 char *t = backslash_halve_save(p); 1376 1377 // When EW_NOTFOUND is used, always add files and dirs. Makes 1378 // "vim c:/" work. 1379 if (flags & EW_NOTFOUND) { 1380 addfile(&ga, t, flags | EW_DIR | EW_FILE); 1381 } else { 1382 addfile(&ga, t, flags); 1383 } 1384 1385 if (t != p) { 1386 xfree(t); 1387 } 1388 } 1389 1390 if (did_expand_in_path && !GA_EMPTY(&ga) && (flags & (EW_PATH | EW_CDPATH))) { 1391 // Recursive gen_expand_wildcards() can only happen here when called from an 1392 // event handler in os_breakcheck(), in which case it should be allowed. 1393 recursive = false; 1394 uniquefy_paths(&ga, p, path_option); 1395 recursive = true; 1396 } 1397 if (p != pat[i]) { 1398 xfree(p); 1399 } 1400 } 1401 1402 *num_file = ga.ga_len; 1403 *file = (ga.ga_data != NULL) ? ga.ga_data : NULL; 1404 1405 recursive = false; 1406 1407 return ((flags & EW_EMPTYOK) || ga.ga_data != NULL) ? OK : FAIL; 1408 } 1409 1410 /// Free the list of files returned by expand_wildcards() or other expansion functions. 1411 void FreeWild(int count, char **files) 1412 { 1413 if (count <= 0 || files == NULL) { 1414 return; 1415 } 1416 while (count--) { 1417 xfree(files[count]); 1418 } 1419 xfree(files); 1420 } 1421 1422 /// @return true if we can expand this backtick thing here. 1423 static bool vim_backtick(char *p) 1424 FUNC_ATTR_NONNULL_ALL 1425 { 1426 return *p == '`' && *(p + 1) != NUL && *(p + strlen(p) - 1) == '`'; 1427 } 1428 1429 /// Expand an item in `backticks` by executing it as a command. 1430 /// Currently only works when pat[] starts and ends with a `. 1431 /// Returns number of file names found, -1 if an error is encountered. 1432 /// 1433 /// @param flags EW_* flags 1434 static int expand_backtick(garray_T *gap, char *pat, int flags) 1435 FUNC_ATTR_NONNULL_ALL 1436 { 1437 char *p; 1438 char *buffer; 1439 int cnt = 0; 1440 1441 // Create the command: lop off the backticks. 1442 char *cmd = xmemdupz(pat + 1, strlen(pat) - 2); 1443 1444 if (*cmd == '=') { // `={expr}`: Expand expression 1445 buffer = eval_to_string(cmd + 1, true, false); 1446 } else { 1447 buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL); 1448 } 1449 xfree(cmd); 1450 if (buffer == NULL) { 1451 return -1; 1452 } 1453 1454 cmd = buffer; 1455 while (*cmd != NUL) { 1456 cmd = skipwhite(cmd); // skip over white space 1457 p = cmd; 1458 while (*p != NUL && *p != '\r' && *p != '\n') { // skip over entry 1459 p++; 1460 } 1461 // add an entry if it is not empty 1462 if (p > cmd) { 1463 char i = *p; 1464 *p = NUL; 1465 addfile(gap, cmd, flags); 1466 *p = i; 1467 cnt++; 1468 } 1469 cmd = p; 1470 while (*cmd != NUL && (*cmd == '\r' || *cmd == '\n')) { 1471 cmd++; 1472 } 1473 } 1474 1475 xfree(buffer); 1476 return cnt; 1477 } 1478 1479 #ifdef BACKSLASH_IN_FILENAME 1480 /// Replace all slashes by backslashes. 1481 /// This used to be the other way around, but MS-DOS sometimes has problems 1482 /// with slashes (e.g. in a command name). We can't have mixed slashes and 1483 /// backslashes, because comparing file names will not work correctly. The 1484 /// commands that use a file name should try to avoid the need to type a 1485 /// backslash twice. 1486 /// When 'shellslash' set do it the other way around. 1487 /// When the path looks like a URL leave it unmodified. 1488 void slash_adjust(char *p) 1489 FUNC_ATTR_NONNULL_ALL 1490 { 1491 if (path_with_url(p)) { 1492 return; 1493 } 1494 1495 if (*p == '`') { 1496 // don't replace backslash in backtick quoted strings 1497 const size_t len = strlen(p); 1498 if (len > 2 && *(p + len - 1) == '`') { 1499 return; 1500 } 1501 } 1502 1503 while (*p) { 1504 if (*p == psepcN) { 1505 *p = psepc; 1506 } 1507 MB_PTR_ADV(p); 1508 } 1509 } 1510 #endif 1511 1512 /// Add a file to a file list. Accepted flags: 1513 /// EW_DIR add directories 1514 /// EW_FILE add files 1515 /// EW_EXEC add executable files 1516 /// EW_NOTFOUND add even when it doesn't exist 1517 /// EW_ADDSLASH add slash after directory name 1518 /// EW_ALLLINKS add symlink also when the referred file does not exist 1519 /// 1520 /// @param f filename 1521 void addfile(garray_T *gap, char *f, int flags) 1522 FUNC_ATTR_NONNULL_ALL 1523 { 1524 bool isdir; 1525 FileInfo file_info; 1526 1527 // if the file/dir/link doesn't exist, may not add it 1528 if (!(flags & EW_NOTFOUND) 1529 && ((flags & EW_ALLLINKS) 1530 ? !os_fileinfo_link(f, &file_info) 1531 : !os_path_exists(f))) { 1532 return; 1533 } 1534 1535 #ifdef FNAME_ILLEGAL 1536 // if the file/dir contains illegal characters, don't add it 1537 if (strpbrk(f, FNAME_ILLEGAL) != NULL) { 1538 return; 1539 } 1540 #endif 1541 1542 isdir = os_isdir(f); 1543 if ((isdir && !(flags & EW_DIR)) || (!isdir && !(flags & EW_FILE))) { 1544 return; 1545 } 1546 1547 // If the file isn't executable, may not add it. Do accept directories. 1548 // When invoked from expand_shellcmd() do not use $PATH. 1549 if (!isdir && (flags & EW_EXEC) 1550 && !os_can_exe(f, NULL, !(flags & EW_SHELLCMD))) { 1551 return; 1552 } 1553 1554 char *p = xmalloc(strlen(f) + 1 + isdir); 1555 1556 STRCPY(p, f); 1557 #ifdef BACKSLASH_IN_FILENAME 1558 slash_adjust(p); 1559 #endif 1560 // Append a slash or backslash after directory names if none is present. 1561 if (isdir && (flags & EW_ADDSLASH)) { 1562 add_pathsep(p); 1563 } 1564 GA_APPEND(char *, gap, p); 1565 } 1566 1567 // Converts a file name into a canonical form. It simplifies a file name into 1568 // its simplest form by stripping out unneeded components, if any. The 1569 // resulting file name is simplified in place and will either be the same 1570 // length as that supplied, or shorter. 1571 size_t simplify_filename(char *filename) 1572 FUNC_ATTR_NONNULL_ALL 1573 { 1574 int components = 0; 1575 bool stripping_disabled = false; 1576 bool relative = true; 1577 1578 char *p = filename; 1579 #ifdef BACKSLASH_IN_FILENAME 1580 if (p[0] != NUL && p[1] == ':') { // skip "x:" 1581 p += 2; 1582 } 1583 #endif 1584 1585 if (vim_ispathsep(*p)) { 1586 relative = false; 1587 do { 1588 p++; 1589 } while (vim_ispathsep(*p)); 1590 } 1591 char *start = p; // remember start after "c:/" or "/" or "///" 1592 char *p_end = p + strlen(p); // point to NUL at end of string "p" 1593 #ifdef UNIX 1594 // Posix says that "//path" is unchanged but "///path" is "/path". 1595 if (start > filename + 2) { 1596 memmove(filename + 1, p, (size_t)(p_end - p) + 1); // +1 for NUL 1597 p_end -= (size_t)(p - (filename + 1)); 1598 start = p = filename + 1; 1599 } 1600 #endif 1601 1602 do { 1603 // At this point "p" is pointing to the char following a single "/" 1604 // or "p" is at the "start" of the (absolute or relative) path name. 1605 if (vim_ispathsep(*p)) { 1606 memmove(p, p + 1, (size_t)(p_end - (p + 1)) + 1); // remove duplicate "/" 1607 p_end--; 1608 } else if (p[0] == '.' 1609 && (vim_ispathsep(p[1]) || p[1] == NUL)) { 1610 if (p == start && relative) { 1611 p += 1 + (p[1] != NUL); // keep single "." or leading "./" 1612 } else { 1613 // Strip "./" or ".///". If we are at the end of the file name 1614 // and there is no trailing path separator, either strip "/." if 1615 // we are after "start", or strip "." if we are at the beginning 1616 // of an absolute path name. 1617 char *tail = p + 1; 1618 if (p[1] != NUL) { 1619 while (vim_ispathsep(*tail)) { 1620 MB_PTR_ADV(tail); 1621 } 1622 } else if (p > start) { 1623 p--; // strip preceding path separator 1624 } 1625 memmove(p, tail, (size_t)(p_end - tail) + 1); 1626 p_end -= (size_t)(tail - p); 1627 } 1628 } else if (p[0] == '.' && p[1] == '.' 1629 && (vim_ispathsep(p[2]) || p[2] == NUL)) { 1630 // Skip to after ".." or "../" or "..///". 1631 char *tail = p + 2; 1632 while (vim_ispathsep(*tail)) { 1633 MB_PTR_ADV(tail); 1634 } 1635 1636 if (components > 0) { // strip one preceding component 1637 bool do_strip = false; 1638 1639 // Don't strip for an erroneous file name. 1640 if (!stripping_disabled) { 1641 // If the preceding component does not exist in the file 1642 // system, we strip it. On Unix, we don't accept a symbolic 1643 // link that refers to a non-existent file. 1644 char saved_char = p[-1]; 1645 p[-1] = NUL; 1646 FileInfo file_info; 1647 if (!os_fileinfo_link(filename, &file_info)) { 1648 do_strip = true; 1649 } 1650 p[-1] = saved_char; 1651 1652 p--; 1653 // Skip back to after previous '/'. 1654 while (p > start && !after_pathsep(start, p)) { 1655 MB_PTR_BACK(start, p); 1656 } 1657 1658 if (!do_strip) { 1659 // If the component exists in the file system, check 1660 // that stripping it won't change the meaning of the 1661 // file name. First get information about the 1662 // unstripped file name. This may fail if the component 1663 // to strip is not a searchable directory (but a regular 1664 // file, for instance), since the trailing "/.." cannot 1665 // be applied then. We don't strip it then since we 1666 // don't want to replace an erroneous file name by 1667 // a valid one, and we disable stripping of later 1668 // components. 1669 saved_char = *tail; 1670 *tail = NUL; 1671 if (os_fileinfo(filename, &file_info)) { 1672 do_strip = true; 1673 } else { 1674 stripping_disabled = true; 1675 } 1676 *tail = saved_char; 1677 if (do_strip) { 1678 // The check for the unstripped file name 1679 // above works also for a symbolic link pointing to 1680 // a searchable directory. But then the parent of 1681 // the directory pointed to by the link must be the 1682 // same as the stripped file name. (The latter 1683 // exists in the file system since it is the 1684 // component's parent directory.) 1685 FileInfo new_file_info; 1686 if (p == start && relative) { 1687 os_fileinfo(".", &new_file_info); 1688 } else { 1689 saved_char = *p; 1690 *p = NUL; 1691 os_fileinfo(filename, &new_file_info); 1692 *p = saved_char; 1693 } 1694 1695 if (!os_fileinfo_id_equal(&file_info, &new_file_info)) { 1696 do_strip = false; 1697 // We don't disable stripping of later 1698 // components since the unstripped path name is 1699 // still valid. 1700 } 1701 } 1702 } 1703 } 1704 1705 if (!do_strip) { 1706 // Skip the ".." or "../" and reset the counter for the 1707 // components that might be stripped later on. 1708 p = tail; 1709 components = 0; 1710 } else { 1711 // Strip previous component. If the result would get empty 1712 // and there is no trailing path separator, leave a single 1713 // "." instead. If we are at the end of the file name and 1714 // there is no trailing path separator and a preceding 1715 // component is left after stripping, strip its trailing 1716 // path separator as well. 1717 if (p == start && relative && tail[-1] == '.') { 1718 *p++ = '.'; 1719 *p = NUL; 1720 } else { 1721 if (p > start && tail[-1] == '.') { 1722 p--; 1723 } 1724 memmove(p, tail, (size_t)(p_end - tail) + 1); // strip previous component 1725 p_end -= (size_t)(tail - p); 1726 } 1727 1728 components--; 1729 } 1730 } else if (p == start && !relative) { // leading "/.." or "/../" 1731 memmove(p, tail, (size_t)(p_end - tail) + 1); // strip ".." or "../" 1732 p_end -= (size_t)(tail - p); 1733 } else { 1734 if (p == start + 2 && p[-2] == '.') { // leading "./../" 1735 memmove(p - 2, p, (size_t)(p_end - p) + 1); // strip leading "./" 1736 p_end -= 2; 1737 tail -= 2; 1738 } 1739 p = tail; // skip to char after ".." or "../" 1740 } 1741 } else { 1742 components++; // Simple path component. 1743 p = (char *)path_next_component(p); 1744 } 1745 } while (*p != NUL); 1746 1747 return (size_t)(p_end - filename); 1748 } 1749 1750 /// Checks for a Windows drive letter ("C:/") at the start of the path. 1751 /// 1752 /// @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter 1753 bool path_has_drive_letter(const char *p, size_t path_len) 1754 FUNC_ATTR_NONNULL_ALL 1755 { 1756 return path_len >= 2 1757 && ASCII_ISALPHA(p[0]) 1758 && (p[1] == ':' || p[1] == '|') 1759 && (path_len == 2 || ((p[2] == '/') | (p[2] == '\\') | (p[2] == '?') | (p[2] == '#'))); 1760 } 1761 1762 // Check if the ":/" of a URL is at the pointer, return URL_SLASH. 1763 // Also check for ":\\", which MS Internet Explorer accepts, return 1764 // URL_BACKSLASH. 1765 int path_is_url(const char *p) 1766 FUNC_ATTR_NONNULL_ALL 1767 { 1768 // In the spec ':' is enough to recognize a scheme 1769 // https://url.spec.whatwg.org/#scheme-state 1770 if (strncmp(p, ":/", 2) == 0) { 1771 return URL_SLASH; 1772 } else if (strncmp(p, ":\\\\", 3) == 0) { 1773 return URL_BACKSLASH; 1774 } 1775 return 0; 1776 } 1777 1778 /// Check if "fname" starts with "name://" or "name:\\". 1779 /// 1780 /// @param fname is the filename to test 1781 /// @return URL_SLASH for "name://", URL_BACKSLASH for "name:\\", zero otherwise. 1782 int path_with_url(const char *fname) 1783 FUNC_ATTR_NONNULL_ALL 1784 { 1785 const char *p; 1786 1787 // first character must be alpha 1788 if (!ASCII_ISALPHA(*fname)) { 1789 return 0; 1790 } 1791 1792 if (path_has_drive_letter(fname, strlen(fname))) { 1793 return 0; 1794 } 1795 1796 // check body: (alpha, digit, '+', '-', '.') following RFC3986 1797 for (p = fname + 1; (ASCII_ISALNUM(*p) || (*p == '+') || (*p == '-') || (*p == '.')); p++) {} 1798 1799 // check last char is not '+', '-', or '.' 1800 if ((p[-1] == '+') || (p[-1] == '-') || (p[-1] == '.')) { 1801 return 0; 1802 } 1803 1804 // ":/" or ":\\" must follow 1805 return path_is_url(p); 1806 } 1807 1808 bool path_with_extension(const char *path, const char *extension) 1809 FUNC_ATTR_NONNULL_ALL 1810 { 1811 const char *last_dot = strrchr(path, '.'); 1812 if (!last_dot) { 1813 return false; 1814 } 1815 return mb_strcmp_ic((bool)p_fic, last_dot + 1, extension) == 0; 1816 } 1817 1818 /// Return true if "name" is a full (absolute) path name or URL. 1819 bool vim_isAbsName(const char *name) 1820 FUNC_ATTR_NONNULL_ALL 1821 { 1822 return path_with_url(name) != 0 || path_is_absolute(name); 1823 } 1824 1825 /// Save absolute file name to "buf[len]". 1826 /// 1827 /// @param fname filename to evaluate 1828 /// @param[out] buf contains `fname` absolute path, or: 1829 /// - truncated `fname` if longer than `len` 1830 /// - unmodified `fname` if absolute path fails or is a URL 1831 /// @param len length of `buf` 1832 /// @param force flag to force expanding even if the path is absolute 1833 /// 1834 /// @return FAIL for failure, OK otherwise 1835 int vim_FullName(const char *fname, char *buf, size_t len, bool force) 1836 FUNC_ATTR_NONNULL_ARG(2) 1837 { 1838 *buf = NUL; 1839 if (fname == NULL) { 1840 return FAIL; 1841 } 1842 1843 if (strlen(fname) > (len - 1)) { 1844 xstrlcpy(buf, fname, len); // truncate 1845 #ifdef MSWIN 1846 slash_adjust(buf); 1847 #endif 1848 return FAIL; 1849 } 1850 1851 if (path_with_url(fname)) { 1852 xstrlcpy(buf, fname, len); 1853 return OK; 1854 } 1855 1856 int rv = path_to_absolute(fname, buf, len, force); 1857 if (rv == FAIL) { 1858 xstrlcpy(buf, fname, len); // something failed; use the filename 1859 } 1860 #ifdef MSWIN 1861 slash_adjust(buf); 1862 #endif 1863 return rv; 1864 } 1865 1866 /// Get the full resolved path for `fname` 1867 /// 1868 /// Even filenames that appear to be absolute based on starting from 1869 /// the root may have relative paths (like dir/../subdir) or symlinks 1870 /// embedded, or even extra separators (//). This function addresses 1871 /// those possibilities, returning a resolved absolute path. 1872 /// For MS-Windows, this also provides drive letter for all absolute paths. 1873 /// 1874 /// @param fname is the filename to expand 1875 /// @return [allocated] Full path (NULL for failure). 1876 char *fix_fname(const char *fname) 1877 { 1878 #ifdef UNIX 1879 return FullName_save(fname, true); 1880 #else 1881 if (!vim_isAbsName(fname) 1882 || strstr(fname, "..") != NULL 1883 || strstr(fname, "//") != NULL 1884 # ifdef BACKSLASH_IN_FILENAME 1885 || strstr(fname, "\\\\") != NULL 1886 # endif 1887 # ifdef MSWIN 1888 || fname[0] == '/' 1889 || fname[0] == '\\' 1890 # endif 1891 ) { 1892 return FullName_save(fname, false); 1893 } 1894 1895 fname = xstrdup(fname); 1896 1897 # ifdef CASE_INSENSITIVE_FILENAME 1898 path_fix_case((char *)fname); // set correct case for file name 1899 # endif 1900 1901 return (char *)fname; 1902 #endif 1903 } 1904 1905 /// Set the case of the file name, if it already exists. This will cause the 1906 /// file name to remain exactly the same. 1907 /// Only required for file systems where case is ignored and preserved. 1908 // TODO(SplinterOfChaos): Could also be used when mounting case-insensitive 1909 // file systems. 1910 void path_fix_case(char *name) 1911 FUNC_ATTR_NONNULL_ALL 1912 { 1913 FileInfo file_info; 1914 if (!os_fileinfo_link(name, &file_info)) { 1915 return; 1916 } 1917 1918 // Open the directory where the file is located. 1919 char *slash = strrchr(name, '/'); 1920 char *tail; 1921 Directory dir; 1922 bool ok; 1923 if (slash == NULL) { 1924 ok = os_scandir(&dir, "."); 1925 tail = name; 1926 } else { 1927 *slash = NUL; 1928 ok = os_scandir(&dir, name); 1929 *slash = '/'; 1930 tail = slash + 1; 1931 } 1932 1933 if (!ok) { 1934 return; 1935 } 1936 1937 size_t taillen = strlen(tail); 1938 const char *entry; 1939 while ((entry = os_scandir_next(&dir))) { 1940 // Only accept names that differ in case and are the same byte 1941 // length. TODO: accept different length name. 1942 if (STRICMP(tail, entry) == 0 && taillen == strlen(entry)) { 1943 char newname[MAXPATHL + 1]; 1944 1945 // Verify the inode is equal. 1946 xstrlcpy(newname, name, MAXPATHL + 1); 1947 xstrlcpy(newname + (tail - name), entry, 1948 (size_t)(MAXPATHL - (tail - name) + 1)); 1949 FileInfo file_info_new; 1950 if (os_fileinfo_link(newname, &file_info_new) 1951 && os_fileinfo_id_equal(&file_info, &file_info_new)) { 1952 STRCPY(tail, entry); 1953 break; 1954 } 1955 } 1956 } 1957 1958 os_closedir(&dir); 1959 } 1960 1961 /// Return true if "p" points to just after a path separator. 1962 /// Takes care of multi-byte characters. 1963 /// "b" must point to the start of the file name 1964 int after_pathsep(const char *b, const char *p) 1965 FUNC_ATTR_NONNULL_ALL 1966 { 1967 return p > b && vim_ispathsep(p[-1]) 1968 && utf_head_off(b, p - 1) == 0; 1969 } 1970 1971 /// Return true if file names "f1" and "f2" are in the same directory. 1972 /// "f1" may be a short name, "f2" must be a full path. 1973 bool same_directory(char *f1, char *f2) 1974 { 1975 char ffname[MAXPATHL]; 1976 char *t1; 1977 char *t2; 1978 1979 // safety check 1980 if (f1 == NULL || f2 == NULL) { 1981 return false; 1982 } 1983 1984 vim_FullName(f1, ffname, MAXPATHL, false); 1985 t1 = path_tail_with_sep(ffname); 1986 t2 = path_tail_with_sep(f2); 1987 return t1 - ffname == t2 - f2 1988 && pathcmp(ffname, f2, (int)(t1 - ffname)) == 0; 1989 } 1990 1991 // Compare path "p[]" to "q[]". 1992 // If `maxlen` >= 0 compare `p[maxlen]` to `q[maxlen]` 1993 // Return value like strcmp(p, q), but consider path separators. 1994 // 1995 // See also `path_full_compare`. 1996 int pathcmp(const char *p, const char *q, int maxlen) 1997 { 1998 int i, j; 1999 const char *s = NULL; 2000 2001 for (i = 0, j = 0; maxlen < 0 || (i < maxlen && j < maxlen);) { 2002 int c1 = utf_ptr2char(p + i); 2003 int c2 = utf_ptr2char(q + j); 2004 2005 // End of "p": check if "q" also ends or just has a slash. 2006 if (c1 == NUL) { 2007 if (c2 == NUL) { // full match 2008 return 0; 2009 } 2010 s = q; 2011 i = j; 2012 break; 2013 } 2014 2015 // End of "q": check if "p" just has a slash. 2016 if (c2 == NUL) { 2017 s = p; 2018 break; 2019 } 2020 2021 if ((p_fic ? mb_toupper(c1) != mb_toupper(c2) : c1 != c2) 2022 #ifdef BACKSLASH_IN_FILENAME 2023 // consider '/' and '\\' to be equal 2024 && !((c1 == '/' && c2 == '\\') 2025 || (c1 == '\\' && c2 == '/')) 2026 #endif 2027 ) { 2028 if (vim_ispathsep(c1)) { 2029 return -1; 2030 } 2031 if (vim_ispathsep(c2)) { 2032 return 1; 2033 } 2034 return p_fic ? mb_toupper(c1) - mb_toupper(c2) 2035 : c1 - c2; // no match 2036 } 2037 2038 i += utfc_ptr2len(p + i); 2039 j += utfc_ptr2len(q + j); 2040 } 2041 if (s == NULL) { // "i" or "j" ran into "maxlen" 2042 return 0; 2043 } 2044 2045 int c1 = utf_ptr2char(s + i); 2046 int c2 = utf_ptr2char(s + i + utfc_ptr2len(s + i)); 2047 // ignore a trailing slash, but not "//" or ":/" 2048 if (c2 == NUL 2049 && i > 0 2050 && !after_pathsep(s, s + i) 2051 #ifdef BACKSLASH_IN_FILENAME 2052 && (c1 == '/' || c1 == '\\') 2053 #else 2054 && c1 == '/' 2055 #endif 2056 ) { 2057 return 0; // match with trailing slash 2058 } 2059 if (s == q) { 2060 return -1; // no match 2061 } 2062 return 1; 2063 } 2064 2065 /// Try to find a shortname by comparing the fullname with the current 2066 /// directory. 2067 /// 2068 /// @param full_path The full path of the file. 2069 /// @return 2070 /// - Pointer into `full_path` if shortened. 2071 /// - `full_path` unchanged if no shorter name is possible. 2072 /// - NULL if `full_path` is NULL. 2073 char *path_try_shorten_fname(char *full_path) 2074 { 2075 char *dirname = xmalloc(MAXPATHL); 2076 char *p = full_path; 2077 2078 if (os_dirname(dirname, MAXPATHL) == OK) { 2079 p = path_shorten_fname(full_path, dirname); 2080 if (p == NULL || *p == NUL) { 2081 p = full_path; 2082 } 2083 } 2084 xfree(dirname); 2085 return p; 2086 } 2087 2088 /// Try to find a shortname by comparing the fullname with `dir_name`. 2089 /// 2090 /// @param full_path The full path of the file. 2091 /// @param dir_name The directory to shorten relative to. 2092 /// @return 2093 /// - Pointer into `full_path` if shortened. 2094 /// - NULL if no shorter name is possible. 2095 char *path_shorten_fname(char *full_path, char *dir_name) 2096 { 2097 if (full_path == NULL) { 2098 return NULL; 2099 } 2100 2101 assert(dir_name != NULL); 2102 size_t len = strlen(dir_name); 2103 2104 // If full_path and dir_name do not match, it's impossible to make one 2105 // relative to the other. 2106 if (path_fnamencmp(dir_name, full_path, len) != 0) { 2107 return NULL; 2108 } 2109 2110 // If dir_name is a path head, full_path can always be made relative. 2111 if (len == (size_t)path_head_length() && is_path_head(dir_name)) { 2112 return full_path + len; 2113 } 2114 2115 char *p = full_path + len; 2116 2117 // If *p is not pointing to a path separator, this means that full_path's 2118 // last directory name is longer than *dir_name's last directory, so they 2119 // don't actually match. 2120 if (!vim_ispathsep(*p)) { 2121 return NULL; 2122 } 2123 2124 do { 2125 p++; 2126 } while (vim_ispathsep_nocolon(*p)); 2127 return p; 2128 } 2129 2130 /// Invoke expand_wildcards() for one pattern 2131 /// 2132 /// One should expand items like "%:h" before the expansion. 2133 /// 2134 /// @param[in] pat Pointer to the input pattern. 2135 /// @param[out] num_file Resulting number of files. 2136 /// @param[out] file Array of resulting files. 2137 /// @param[in] flags Flags passed to expand_wildcards(). 2138 /// 2139 /// @returns OK when *file is set to allocated array of matches 2140 /// and *num_file (can be zero) to the number of matches. 2141 /// If FAIL is returned, *num_file and *file are either 2142 /// unchanged or *num_file is set to 0 and *file is set 2143 /// to NULL or points to "". 2144 int expand_wildcards_eval(char **pat, int *num_file, char ***file, int flags) 2145 { 2146 int ret = FAIL; 2147 char *eval_pat = NULL; 2148 char *exp_pat = *pat; 2149 const char *ignored_msg; 2150 size_t usedlen; 2151 const bool is_cur_alt_file = *exp_pat == '%' || *exp_pat == '#'; 2152 bool star_follows = false; 2153 2154 if (is_cur_alt_file || *exp_pat == '<') { 2155 emsg_off++; 2156 eval_pat = eval_vars(exp_pat, exp_pat, &usedlen, NULL, &ignored_msg, 2157 NULL, 2158 true); 2159 emsg_off--; 2160 if (eval_pat != NULL) { 2161 star_follows = strcmp(exp_pat + usedlen, "*") == 0; 2162 exp_pat = concat_str(eval_pat, exp_pat + usedlen); 2163 } 2164 } 2165 2166 if (exp_pat != NULL) { 2167 ret = expand_wildcards(1, &exp_pat, num_file, file, flags); 2168 } 2169 2170 if (eval_pat != NULL) { 2171 if (*num_file == 0 && is_cur_alt_file && star_follows) { 2172 // Expanding "%" or "#" and the file does not exist: Add the 2173 // pattern anyway (without the star) so that this works for remote 2174 // files and non-file buffer names. 2175 *file = xmalloc(sizeof(char *)); 2176 **file = eval_pat; 2177 eval_pat = NULL; 2178 *num_file = 1; 2179 ret = OK; 2180 } 2181 xfree(exp_pat); 2182 xfree(eval_pat); 2183 } 2184 2185 return ret; 2186 } 2187 2188 /// Expand wildcards. Calls gen_expand_wildcards() and removes files matching 2189 /// 'wildignore'. 2190 /// 2191 /// @param num_pat is number of input patterns. 2192 /// @param pat is an array of pointers to input patterns. 2193 /// @param[out] num_file is pointer to number of matched file names. 2194 /// @param[out] file is pointer to array of pointers to matched file names. 2195 /// @param flags is a combination of EW_* flags. 2196 /// 2197 /// @returns OK when *file is set to allocated array of matches 2198 /// and *num_file (can be zero) to the number of matches. 2199 /// If FAIL is returned, *num_file and *file are either 2200 /// unchanged or *num_file is set to 0 and *file is set to 2201 /// NULL or points to "". 2202 int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int flags) 2203 { 2204 int retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags); 2205 2206 // When keeping all matches, return here 2207 if ((flags & EW_KEEPALL) || retval == FAIL) { 2208 return retval; 2209 } 2210 2211 // Remove names that match 'wildignore'. 2212 if (*p_wig) { 2213 // check all files in (*files)[] 2214 assert(*num_files == 0 || *files != NULL); 2215 for (int i = 0; i < *num_files; i++) { 2216 char *ffname = FullName_save((*files)[i], false); 2217 assert((*files)[i] != NULL); 2218 assert(ffname != NULL); 2219 if (match_file_list(p_wig, (*files)[i], ffname)) { 2220 // remove this matching file from the list 2221 xfree((*files)[i]); 2222 for (int j = i; j + 1 < *num_files; j++) { 2223 (*files)[j] = (*files)[j + 1]; 2224 } 2225 (*num_files)--; 2226 i--; 2227 } 2228 xfree(ffname); 2229 } 2230 } 2231 2232 // Move the names where 'suffixes' match to the end. 2233 // Skip when interrupted, the result probably won't be used. 2234 assert(*num_files == 0 || *files != NULL); 2235 if (*num_files > 1 && !got_int) { 2236 int non_suf_match = 0; // number without matching suffix 2237 for (int i = 0; i < *num_files; i++) { 2238 if (!match_suffix((*files)[i])) { 2239 // Move the name without matching suffix to the front of the list. 2240 char *p = (*files)[i]; 2241 for (int j = i; j > non_suf_match; j--) { 2242 (*files)[j] = (*files)[j - 1]; 2243 } 2244 (*files)[non_suf_match++] = p; 2245 } 2246 } 2247 } 2248 2249 // Free empty array of matches 2250 if (*num_files == 0) { 2251 XFREE_CLEAR(*files); 2252 return FAIL; 2253 } 2254 2255 return retval; 2256 } 2257 2258 /// @return true if "fname" matches with an entry in 'suffixes'. 2259 bool match_suffix(char *fname) 2260 FUNC_ATTR_NONNULL_ALL 2261 { 2262 #define MAXSUFLEN 30 // maximum length of a file suffix 2263 char suf_buf[MAXSUFLEN]; 2264 2265 size_t fnamelen = strlen(fname); 2266 size_t setsuflen = 0; 2267 for (char *setsuf = p_su; *setsuf;) { 2268 setsuflen = copy_option_part(&setsuf, suf_buf, MAXSUFLEN, ".,"); 2269 if (setsuflen == 0) { 2270 char *tail = path_tail(fname); 2271 2272 // empty entry: match name without a '.' 2273 if (vim_strchr(tail, '.') == NULL) { 2274 setsuflen = 1; 2275 break; 2276 } 2277 } else { 2278 if (fnamelen >= setsuflen 2279 && path_fnamencmp(suf_buf, fname + fnamelen - setsuflen, setsuflen) == 0) { 2280 break; 2281 } 2282 setsuflen = 0; 2283 } 2284 } 2285 return setsuflen != 0; 2286 } 2287 2288 /// Get the absolute name of the given relative directory. 2289 /// 2290 /// @param directory Directory name, relative to current directory. 2291 /// @return `FAIL` for failure, `OK` for success. 2292 int path_full_dir_name(char *directory, char *buffer, size_t len) 2293 FUNC_ATTR_NONNULL_ALL 2294 { 2295 if (strlen(directory) == 0) { 2296 return os_dirname(buffer, len); 2297 } 2298 2299 if (os_realpath(directory, buffer, len) != NULL) { 2300 return OK; 2301 } 2302 2303 // Path does not exist (yet). For a full path fail, will use the path as-is. 2304 if (path_is_absolute(directory)) { 2305 return FAIL; 2306 } 2307 // For a relative path use the current directory and append the file name. 2308 2309 char old_dir[MAXPATHL]; 2310 2311 // Get current directory name. 2312 if (os_dirname(old_dir, MAXPATHL) == FAIL) { 2313 return FAIL; 2314 } 2315 2316 xstrlcpy(buffer, old_dir, len); 2317 if (append_path(buffer, directory, len) == FAIL) { 2318 return FAIL; 2319 } 2320 2321 return OK; 2322 } 2323 2324 // Append to_append to path with a slash in between. 2325 int append_path(char *path, const char *to_append, size_t max_len) 2326 FUNC_ATTR_NONNULL_ALL 2327 { 2328 size_t current_length = strlen(path); 2329 size_t to_append_length = strlen(to_append); 2330 2331 // Do not append empty string or a dot. 2332 if (to_append_length == 0 || strcmp(to_append, ".") == 0) { 2333 return OK; 2334 } 2335 2336 // Combine the path segments, separated by a slash. 2337 if (current_length > 0 && !vim_ispathsep_nocolon(path[current_length - 1])) { 2338 // +1 for the NUL at the end. 2339 if (current_length + STRLEN_LITERAL(PATHSEPSTR) + 1 > max_len) { 2340 return FAIL; // No space for trailing slash. 2341 } 2342 xstrlcpy(path + current_length, PATHSEPSTR, max_len - current_length); 2343 current_length += STRLEN_LITERAL(PATHSEPSTR); 2344 } 2345 2346 // +1 for the NUL at the end. 2347 if (current_length + to_append_length + 1 > max_len) { 2348 return FAIL; 2349 } 2350 2351 xstrlcpy(path + current_length, to_append, max_len - current_length); 2352 return OK; 2353 } 2354 2355 /// Used by `vim_FullName` and `fix_fname` to expand a filename to its full path. 2356 /// 2357 /// @param fname Filename to expand. 2358 /// @param buf Where to store the absolute path of "fname". 2359 /// @param len Length of `buf`. 2360 /// @param force Also expand when `fname` is already absolute. 2361 /// 2362 /// @return FAIL for failure, OK for success. 2363 static int path_to_absolute(const char *fname, char *buf, size_t len, int force) 2364 FUNC_ATTR_NONNULL_ALL 2365 { 2366 const char *p; 2367 *buf = NUL; 2368 2369 char *relative_directory = xmalloc(len); 2370 const char *end_of_path = fname; 2371 2372 // expand it if forced or not an absolute path 2373 if (force || !path_is_absolute(fname) 2374 #ifdef MSWIN // enforce drive letter on Windows paths 2375 || fname[0] == '/' || fname[0] == '\\' 2376 #endif 2377 ) { 2378 p = strrchr(fname, '/'); 2379 #ifdef MSWIN 2380 if (p == NULL) { 2381 p = strrchr(fname, '\\'); 2382 } 2383 #endif 2384 if (p == NULL && strcmp(fname, "..") == 0) { 2385 // Handle ".." without path separators. 2386 p = fname + 2; 2387 } 2388 if (p != NULL) { 2389 if (vim_ispathsep_nocolon(*p) && strcmp(p + 1, "..") == 0) { 2390 // For "/path/dir/.." include the "/..". 2391 p += 3; 2392 } 2393 assert(p >= fname); 2394 memcpy(relative_directory, fname, (size_t)(p - fname + 1)); 2395 relative_directory[p - fname + 1] = NUL; 2396 end_of_path = (vim_ispathsep_nocolon(*p) ? p + 1 : p); 2397 } else { 2398 relative_directory[0] = NUL; 2399 } 2400 2401 if (FAIL == path_full_dir_name(relative_directory, buf, len)) { 2402 xfree(relative_directory); 2403 return FAIL; 2404 } 2405 } 2406 xfree(relative_directory); 2407 return append_path(buf, end_of_path, len); 2408 } 2409 2410 /// Check if file `fname` is a full (absolute) path. 2411 /// 2412 /// @return `true` if "fname" is absolute. 2413 bool path_is_absolute(const char *fname) 2414 FUNC_ATTR_NONNULL_ALL 2415 { 2416 #ifdef MSWIN 2417 if (*fname == NUL) { 2418 return false; 2419 } 2420 // A name like "d:/foo" and "//server/share" is absolute 2421 // /foo and \foo are absolute too because Windows keeps a current drive. 2422 return ((ASCII_ISALPHA(fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2])) 2423 || vim_ispathsep_nocolon(fname[0])); 2424 #else 2425 // UNIX: This just checks if the file name starts with '/' or '~'. 2426 return *fname == '/' || *fname == '~'; 2427 #endif 2428 } 2429 2430 /// Builds a full path from an invocation name `argv0`, based on heuristics. 2431 /// 2432 /// @param[in] argv0 Name by which Nvim was invoked. 2433 /// @param[out] buf Guessed full path to `argv0`. 2434 /// @param[in] bufsize Size of `buf`. 2435 /// 2436 /// @see os_exepath 2437 void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) 2438 FUNC_ATTR_NONNULL_ALL 2439 { 2440 char *path = os_getenv("PATH"); 2441 2442 if (path == NULL || path_is_absolute(argv0)) { 2443 xstrlcpy(buf, argv0, bufsize); 2444 } else if (argv0[0] == '.' || strchr(argv0, PATHSEP)) { 2445 // Relative to CWD. 2446 if (os_dirname(buf, MAXPATHL) != OK) { 2447 buf[0] = NUL; 2448 } 2449 xstrlcat(buf, PATHSEPSTR, bufsize); 2450 xstrlcat(buf, argv0, bufsize); 2451 } else { 2452 // Search $PATH for plausible location. 2453 const void *iter = NULL; 2454 do { 2455 const char *dir; 2456 size_t dir_len; 2457 iter = vim_env_iter(ENV_SEPCHAR, path, iter, &dir, &dir_len); 2458 if (dir == NULL || dir_len == 0) { 2459 break; 2460 } 2461 if (dir_len + 1 > sizeof(NameBuff)) { 2462 continue; 2463 } 2464 xmemcpyz(NameBuff, dir, dir_len); 2465 xstrlcat(NameBuff, PATHSEPSTR, sizeof(NameBuff)); 2466 xstrlcat(NameBuff, argv0, sizeof(NameBuff)); 2467 if (os_can_exe(NameBuff, NULL, false)) { 2468 xstrlcpy(buf, NameBuff, bufsize); 2469 return; 2470 } 2471 } while (iter != NULL); 2472 // Not found in $PATH, fall back to argv0. 2473 xstrlcpy(buf, argv0, bufsize); 2474 } 2475 xfree(path); 2476 }