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