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// File searching functions for 'path', 'tags' and 'cdpath' options.
5//
6// External visible functions:
7// vim_findfile_init() creates/initialises the search context
8// vim_findfile_free_visited() free list of visited files/dirs of search
9// context
10// vim_findfile() find a file in the search context
11// vim_findfile_cleanup() cleanup/free search context created by
12// vim_findfile_init()
13//
14// All static functions and variables start with 'ff_'
15//
16// In general it works like this:
17// First you create yourself a search context by calling vim_findfile_init().
18// It is possible to give a search context from a previous call to
19// vim_findfile_init(), so it can be reused. After this you call vim_findfile()
20// until you are satisfied with the result or it returns NULL. On every call it
21// returns the next file which matches the conditions given to
22// vim_findfile_init(). If it doesn't find a next file it returns NULL.
23//
24// It is possible to call vim_findfile_init() again to reinitialise your search
25// with some new parameters. Don't forget to pass your old search context to
26// it, so it can reuse it and especially reuse the list of already visited
27// directories. If you want to delete the list of already visited directories
28// simply call vim_findfile_free_visited().
29//
30// When you are done call vim_findfile_cleanup() to free the search context.
31//
32// The function vim_findfile_init() has a long comment, which describes the
33// needed parameters.
34//
35//
36//
37// ATTENTION:
38// ==========
39// We use an allocated search context, these functions are NOT thread-safe!!!!!
40//
41// To minimize parameter passing (or because I'm too lazy), only the
42// external visible functions get a search context as a parameter. This is
43// then assigned to a static global, which is used throughout the local
44// functions.
45
46#include <assert.h>
47#include <string.h>
48#include <stdbool.h>
49#include <inttypes.h>
50#include <limits.h>
51
52#include "nvim/vim.h"
53#include "nvim/eval.h"
54#include "nvim/ascii.h"
55#include "nvim/file_search.h"
56#include "nvim/charset.h"
57#include "nvim/fileio.h"
58#include "nvim/memory.h"
59#include "nvim/message.h"
60#include "nvim/misc1.h"
61#include "nvim/option.h"
62#include "nvim/os_unix.h"
63#include "nvim/path.h"
64#include "nvim/strings.h"
65#include "nvim/tag.h"
66#include "nvim/window.h"
67#include "nvim/os/os.h"
68#include "nvim/os/input.h"
69#include "nvim/os/fs_defs.h"
70
71static char_u *ff_expand_buffer = NULL; /* used for expanding filenames */
72
73/*
74 * type for the directory search stack
75 */
76typedef struct ff_stack {
77 struct ff_stack *ffs_prev;
78
79 /* the fix part (no wildcards) and the part containing the wildcards
80 * of the search path
81 */
82 char_u *ffs_fix_path;
83 char_u *ffs_wc_path;
84
85 /* files/dirs found in the above directory, matched by the first wildcard
86 * of wc_part
87 */
88 char_u **ffs_filearray;
89 int ffs_filearray_size;
90 char_u ffs_filearray_cur; /* needed for partly handled dirs */
91
92 /* to store status of partly handled directories
93 * 0: we work on this directory for the first time
94 * 1: this directory was partly searched in an earlier step
95 */
96 int ffs_stage;
97
98 /* How deep are we in the directory tree?
99 * Counts backward from value of level parameter to vim_findfile_init
100 */
101 int ffs_level;
102
103 /* Did we already expand '**' to an empty string? */
104 int ffs_star_star_empty;
105} ff_stack_T;
106
107/*
108 * type for already visited directories or files.
109 */
110typedef struct ff_visited {
111 struct ff_visited *ffv_next;
112
113 /* Visited directories are different if the wildcard string are
114 * different. So we have to save it.
115 */
116 char_u *ffv_wc_path;
117 // use FileID for comparison (needed because of links), else use filename.
118 bool file_id_valid;
119 FileID file_id;
120 /* The memory for this struct is allocated according to the length of
121 * ffv_fname.
122 */
123 char_u ffv_fname[1]; /* actually longer */
124} ff_visited_T;
125
126/*
127 * We might have to manage several visited lists during a search.
128 * This is especially needed for the tags option. If tags is set to:
129 * "./++/tags,./++/TAGS,++/tags" (replace + with *)
130 * So we have to do 3 searches:
131 * 1) search from the current files directory downward for the file "tags"
132 * 2) search from the current files directory downward for the file "TAGS"
133 * 3) search from Vims current directory downwards for the file "tags"
134 * As you can see, the first and the third search are for the same file, so for
135 * the third search we can use the visited list of the first search. For the
136 * second search we must start from an empty visited list.
137 * The struct ff_visited_list_hdr is used to manage a linked list of already
138 * visited lists.
139 */
140typedef struct ff_visited_list_hdr {
141 struct ff_visited_list_hdr *ffvl_next;
142
143 /* the filename the attached visited list is for */
144 char_u *ffvl_filename;
145
146 ff_visited_T *ffvl_visited_list;
147
148} ff_visited_list_hdr_T;
149
150
151/*
152 * '**' can be expanded to several directory levels.
153 * Set the default maximum depth.
154 */
155#define FF_MAX_STAR_STAR_EXPAND ((char_u)30)
156
157/*
158 * The search context:
159 * ffsc_stack_ptr: the stack for the dirs to search
160 * ffsc_visited_list: the currently active visited list
161 * ffsc_dir_visited_list: the currently active visited list for search dirs
162 * ffsc_visited_lists_list: the list of all visited lists
163 * ffsc_dir_visited_lists_list: the list of all visited lists for search dirs
164 * ffsc_file_to_search: the file to search for
165 * ffsc_start_dir: the starting directory, if search path was relative
166 * ffsc_fix_path: the fix part of the given path (without wildcards)
167 * Needed for upward search.
168 * ffsc_wc_path: the part of the given path containing wildcards
169 * ffsc_level: how many levels of dirs to search downwards
170 * ffsc_stopdirs_v: array of stop directories for upward search
171 * ffsc_find_what: FINDFILE_BOTH, FINDFILE_DIR or FINDFILE_FILE
172 * ffsc_tagfile: searching for tags file, don't use 'suffixesadd'
173 */
174typedef struct ff_search_ctx_T {
175 ff_stack_T *ffsc_stack_ptr;
176 ff_visited_list_hdr_T *ffsc_visited_list;
177 ff_visited_list_hdr_T *ffsc_dir_visited_list;
178 ff_visited_list_hdr_T *ffsc_visited_lists_list;
179 ff_visited_list_hdr_T *ffsc_dir_visited_lists_list;
180 char_u *ffsc_file_to_search;
181 char_u *ffsc_start_dir;
182 char_u *ffsc_fix_path;
183 char_u *ffsc_wc_path;
184 int ffsc_level;
185 char_u **ffsc_stopdirs_v;
186 int ffsc_find_what;
187 int ffsc_tagfile;
188} ff_search_ctx_T;
189
190/* locally needed functions */
191
192#ifdef INCLUDE_GENERATED_DECLARATIONS
193# include "file_search.c.generated.h"
194#endif
195
196static char_u e_pathtoolong[] = N_("E854: path too long for completion");
197
198/*
199 * Initialization routine for vim_findfile().
200 *
201 * Returns the newly allocated search context or NULL if an error occurred.
202 *
203 * Don't forget to clean up by calling vim_findfile_cleanup() if you are done
204 * with the search context.
205 *
206 * Find the file 'filename' in the directory 'path'.
207 * The parameter 'path' may contain wildcards. If so only search 'level'
208 * directories deep. The parameter 'level' is the absolute maximum and is
209 * not related to restricts given to the '**' wildcard. If 'level' is 100
210 * and you use '**200' vim_findfile() will stop after 100 levels.
211 *
212 * 'filename' cannot contain wildcards! It is used as-is, no backslashes to
213 * escape special characters.
214 *
215 * If 'stopdirs' is not NULL and nothing is found downward, the search is
216 * restarted on the next higher directory level. This is repeated until the
217 * start-directory of a search is contained in 'stopdirs'. 'stopdirs' has the
218 * format ";*<dirname>*\(;<dirname>\)*;\=$".
219 *
220 * If the 'path' is relative, the starting dir for the search is either VIM's
221 * current dir or if the path starts with "./" the current files dir.
222 * If the 'path' is absolute, the starting dir is that part of the path before
223 * the first wildcard.
224 *
225 * Upward search is only done on the starting dir.
226 *
227 * If 'free_visited' is TRUE the list of already visited files/directories is
228 * cleared. Set this to FALSE if you just want to search from another
229 * directory, but want to be sure that no directory from a previous search is
230 * searched again. This is useful if you search for a file at different places.
231 * The list of visited files/dirs can also be cleared with the function
232 * vim_findfile_free_visited().
233 *
234 * Set the parameter 'find_what' to FINDFILE_DIR if you want to search for
235 * directories only, FINDFILE_FILE for files only, FINDFILE_BOTH for both.
236 *
237 * A search context returned by a previous call to vim_findfile_init() can be
238 * passed in the parameter "search_ctx_arg". This context is reused and
239 * reinitialized with the new parameters. The list of already visited
240 * directories from this context is only deleted if the parameter
241 * "free_visited" is true. Be aware that the passed "search_ctx_arg" is freed
242 * if the reinitialization fails.
243 *
244 * If you don't have a search context from a previous call "search_ctx_arg"
245 * must be NULL.
246 *
247 * This function silently ignores a few errors, vim_findfile() will have
248 * limited functionality then.
249 */
250void *
251vim_findfile_init (
252 char_u *path,
253 char_u *filename,
254 char_u *stopdirs,
255 int level,
256 int free_visited,
257 int find_what,
258 void *search_ctx_arg,
259 int tagfile, /* expanding names of tags files */
260 char_u *rel_fname /* file name to use for "." */
261)
262{
263 char_u *wc_part;
264 ff_stack_T *sptr;
265 ff_search_ctx_T *search_ctx;
266
267 /* If a search context is given by the caller, reuse it, else allocate a
268 * new one.
269 */
270 if (search_ctx_arg != NULL)
271 search_ctx = search_ctx_arg;
272 else {
273 search_ctx = xcalloc(1, sizeof(ff_search_ctx_T));
274 }
275 search_ctx->ffsc_find_what = find_what;
276 search_ctx->ffsc_tagfile = tagfile;
277
278 /* clear the search context, but NOT the visited lists */
279 ff_clear(search_ctx);
280
281 /* clear visited list if wanted */
282 if (free_visited == TRUE)
283 vim_findfile_free_visited(search_ctx);
284 else {
285 /* Reuse old visited lists. Get the visited list for the given
286 * filename. If no list for the current filename exists, creates a new
287 * one. */
288 search_ctx->ffsc_visited_list = ff_get_visited_list(filename,
289 &search_ctx->ffsc_visited_lists_list);
290 if (search_ctx->ffsc_visited_list == NULL)
291 goto error_return;
292 search_ctx->ffsc_dir_visited_list = ff_get_visited_list(filename,
293 &search_ctx->ffsc_dir_visited_lists_list);
294 if (search_ctx->ffsc_dir_visited_list == NULL)
295 goto error_return;
296 }
297
298 if (ff_expand_buffer == NULL) {
299 ff_expand_buffer = xmalloc(MAXPATHL);
300 }
301
302 /* Store information on starting dir now if path is relative.
303 * If path is absolute, we do that later. */
304 if (path[0] == '.'
305 && (vim_ispathsep(path[1]) || path[1] == NUL)
306 && (!tagfile || vim_strchr(p_cpo, CPO_DOTTAG) == NULL)
307 && rel_fname != NULL) {
308 size_t len = (size_t)(path_tail(rel_fname) - rel_fname);
309
310 if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) {
311 /* Make the start dir an absolute path name. */
312 STRLCPY(ff_expand_buffer, rel_fname, len + 1);
313 search_ctx->ffsc_start_dir = (char_u *)FullName_save((char *)ff_expand_buffer, FALSE);
314 } else
315 search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len);
316 if (*++path != NUL)
317 ++path;
318 } else if (*path == NUL || !vim_isAbsName(path)) {
319#ifdef BACKSLASH_IN_FILENAME
320 /* "c:dir" needs "c:" to be expanded, otherwise use current dir */
321 if (*path != NUL && path[1] == ':') {
322 char_u drive[3];
323
324 drive[0] = path[0];
325 drive[1] = ':';
326 drive[2] = NUL;
327 if (vim_FullName((const char *)drive, (char *)ff_expand_buffer, MAXPATHL,
328 true)
329 == FAIL) {
330 goto error_return;
331 }
332 path += 2;
333 } else
334#endif
335 if (os_dirname(ff_expand_buffer, MAXPATHL) == FAIL)
336 goto error_return;
337
338 search_ctx->ffsc_start_dir = vim_strsave(ff_expand_buffer);
339
340#ifdef BACKSLASH_IN_FILENAME
341 /* A path that starts with "/dir" is relative to the drive, not to the
342 * directory (but not for "//machine/dir"). Only use the drive name. */
343 if ((*path == '/' || *path == '\\')
344 && path[1] != path[0]
345 && search_ctx->ffsc_start_dir[1] == ':')
346 search_ctx->ffsc_start_dir[2] = NUL;
347#endif
348 }
349
350 /*
351 * If stopdirs are given, split them into an array of pointers.
352 * If this fails (mem allocation), there is no upward search at all or a
353 * stop directory is not recognized -> continue silently.
354 * If stopdirs just contains a ";" or is empty,
355 * search_ctx->ffsc_stopdirs_v will only contain a NULL pointer. This
356 * is handled as unlimited upward search. See function
357 * ff_path_in_stoplist() for details.
358 */
359 if (stopdirs != NULL) {
360 char_u *walker = stopdirs;
361
362 while (*walker == ';')
363 walker++;
364
365 size_t dircount = 1;
366 search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(char_u *));
367
368 do {
369 char_u *helper;
370 void *ptr;
371
372 helper = walker;
373 ptr = xrealloc(search_ctx->ffsc_stopdirs_v,
374 (dircount + 1) * sizeof(char_u *));
375 search_ctx->ffsc_stopdirs_v = ptr;
376 walker = vim_strchr(walker, ';');
377 if (walker) {
378 assert(walker - helper >= 0);
379 search_ctx->ffsc_stopdirs_v[dircount-1] =
380 vim_strnsave(helper, (size_t)(walker - helper));
381 walker++;
382 } else
383 /* this might be "", which means ascent till top
384 * of directory tree.
385 */
386 search_ctx->ffsc_stopdirs_v[dircount-1] =
387 vim_strsave(helper);
388
389 dircount++;
390
391 } while (walker != NULL);
392 search_ctx->ffsc_stopdirs_v[dircount-1] = NULL;
393 }
394
395 search_ctx->ffsc_level = level;
396
397 /* split into:
398 * -fix path
399 * -wildcard_stuff (might be NULL)
400 */
401 wc_part = vim_strchr(path, '*');
402 if (wc_part != NULL) {
403 int64_t llevel;
404 int len;
405 char *errpt;
406
407 /* save the fix part of the path */
408 assert(wc_part - path >= 0);
409 search_ctx->ffsc_fix_path = vim_strnsave(path, (size_t)(wc_part - path));
410
411 /*
412 * copy wc_path and add restricts to the '**' wildcard.
413 * The octet after a '**' is used as a (binary) counter.
414 * So '**3' is transposed to '**^C' ('^C' is ASCII value 3)
415 * or '**76' is transposed to '**N'( 'N' is ASCII value 76).
416 * If no restrict is given after '**' the default is used.
417 * Due to this technique the path looks awful if you print it as a
418 * string.
419 */
420 len = 0;
421 while (*wc_part != NUL) {
422 if (len + 5 >= MAXPATHL) {
423 EMSG(_(e_pathtoolong));
424 break;
425 }
426 if (STRNCMP(wc_part, "**", 2) == 0) {
427 ff_expand_buffer[len++] = *wc_part++;
428 ff_expand_buffer[len++] = *wc_part++;
429
430 llevel = strtol((char *)wc_part, &errpt, 10);
431 if ((char_u *)errpt != wc_part && llevel > 0 && llevel < 255)
432 ff_expand_buffer[len++] = (char_u)llevel;
433 else if ((char_u *)errpt != wc_part && llevel == 0)
434 /* restrict is 0 -> remove already added '**' */
435 len -= 2;
436 else
437 ff_expand_buffer[len++] = FF_MAX_STAR_STAR_EXPAND;
438 wc_part = (char_u *)errpt;
439 if (*wc_part != NUL && !vim_ispathsep(*wc_part)) {
440 EMSG2(_(
441 "E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'."),
442 PATHSEPSTR);
443 goto error_return;
444 }
445 } else
446 ff_expand_buffer[len++] = *wc_part++;
447 }
448 ff_expand_buffer[len] = NUL;
449 search_ctx->ffsc_wc_path = vim_strsave(ff_expand_buffer);
450 } else
451 search_ctx->ffsc_fix_path = vim_strsave(path);
452
453 if (search_ctx->ffsc_start_dir == NULL) {
454 /* store the fix part as startdir.
455 * This is needed if the parameter path is fully qualified.
456 */
457 search_ctx->ffsc_start_dir = vim_strsave(search_ctx->ffsc_fix_path);
458 search_ctx->ffsc_fix_path[0] = NUL;
459 }
460
461 /* create an absolute path */
462 if (STRLEN(search_ctx->ffsc_start_dir)
463 + STRLEN(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) {
464 EMSG(_(e_pathtoolong));
465 goto error_return;
466 }
467 STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir);
468 add_pathsep((char *)ff_expand_buffer);
469 {
470 size_t eb_len = STRLEN(ff_expand_buffer);
471 char_u *buf = xmalloc(eb_len + STRLEN(search_ctx->ffsc_fix_path) + 1);
472
473 STRCPY(buf, ff_expand_buffer);
474 STRCPY(buf + eb_len, search_ctx->ffsc_fix_path);
475 if (os_isdir(buf)) {
476 STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path);
477 add_pathsep((char *)ff_expand_buffer);
478 } else {
479 char_u *p = path_tail(search_ctx->ffsc_fix_path);
480 char_u *wc_path = NULL;
481 char_u *temp = NULL;
482 int len = 0;
483
484 if (p > search_ctx->ffsc_fix_path) {
485 len = (int)(p - search_ctx->ffsc_fix_path) - 1;
486 STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len);
487 add_pathsep((char *)ff_expand_buffer);
488 } else {
489 len = (int)STRLEN(search_ctx->ffsc_fix_path);
490 }
491
492 if (search_ctx->ffsc_wc_path != NULL) {
493 wc_path = vim_strsave(search_ctx->ffsc_wc_path);
494 temp = xmalloc(STRLEN(search_ctx->ffsc_wc_path)
495 + STRLEN(search_ctx->ffsc_fix_path + len)
496 + 1);
497 STRCPY(temp, search_ctx->ffsc_fix_path + len);
498 STRCAT(temp, search_ctx->ffsc_wc_path);
499 xfree(search_ctx->ffsc_wc_path);
500 xfree(wc_path);
501 search_ctx->ffsc_wc_path = temp;
502 }
503 }
504 xfree(buf);
505 }
506
507 sptr = ff_create_stack_element(ff_expand_buffer,
508 search_ctx->ffsc_wc_path,
509 level, 0);
510
511 ff_push(search_ctx, sptr);
512 search_ctx->ffsc_file_to_search = vim_strsave(filename);
513 return search_ctx;
514
515error_return:
516 /*
517 * We clear the search context now!
518 * Even when the caller gave us a (perhaps valid) context we free it here,
519 * as we might have already destroyed it.
520 */
521 vim_findfile_cleanup(search_ctx);
522 return NULL;
523}
524
525/*
526 * Get the stopdir string. Check that ';' is not escaped.
527 */
528char_u *vim_findfile_stopdir(char_u *buf)
529{
530 char_u *r_ptr = buf;
531
532 while (*r_ptr != NUL && *r_ptr != ';') {
533 if (r_ptr[0] == '\\' && r_ptr[1] == ';') {
534 /* Overwrite the escape char,
535 * use STRLEN(r_ptr) to move the trailing '\0'. */
536 STRMOVE(r_ptr, r_ptr + 1);
537 r_ptr++;
538 }
539 r_ptr++;
540 }
541 if (*r_ptr == ';') {
542 *r_ptr = 0;
543 r_ptr++;
544 } else if (*r_ptr == NUL)
545 r_ptr = NULL;
546 return r_ptr;
547}
548
549/*
550 * Clean up the given search context. Can handle a NULL pointer.
551 */
552void vim_findfile_cleanup(void *ctx)
553{
554 if (ctx == NULL)
555 return;
556
557 vim_findfile_free_visited(ctx);
558 ff_clear(ctx);
559 xfree(ctx);
560}
561
562/*
563 * Find a file in a search context.
564 * The search context was created with vim_findfile_init() above.
565 * Return a pointer to an allocated file name or NULL if nothing found.
566 * To get all matching files call this function until you get NULL.
567 *
568 * If the passed search_context is NULL, NULL is returned.
569 *
570 * The search algorithm is depth first. To change this replace the
571 * stack with a list (don't forget to leave partly searched directories on the
572 * top of the list).
573 */
574char_u *vim_findfile(void *search_ctx_arg)
575{
576 char_u *file_path;
577 char_u *rest_of_wildcards;
578 char_u *path_end = NULL;
579 ff_stack_T *stackp = NULL;
580 size_t len;
581 char_u *p;
582 char_u *suf;
583 ff_search_ctx_T *search_ctx;
584
585 if (search_ctx_arg == NULL)
586 return NULL;
587
588 search_ctx = (ff_search_ctx_T *)search_ctx_arg;
589
590 /*
591 * filepath is used as buffer for various actions and as the storage to
592 * return a found filename.
593 */
594 file_path = xmalloc(MAXPATHL);
595
596 /* store the end of the start dir -- needed for upward search */
597 if (search_ctx->ffsc_start_dir != NULL)
598 path_end = &search_ctx->ffsc_start_dir[
599 STRLEN(search_ctx->ffsc_start_dir)];
600
601 /* upward search loop */
602 for (;; ) {
603 /* downward search loop */
604 for (;; ) {
605 /* check if user user wants to stop the search*/
606 os_breakcheck();
607 if (got_int)
608 break;
609
610 /* get directory to work on from stack */
611 stackp = ff_pop(search_ctx);
612 if (stackp == NULL)
613 break;
614
615 /*
616 * TODO: decide if we leave this test in
617 *
618 * GOOD: don't search a directory(-tree) twice.
619 * BAD: - check linked list for every new directory entered.
620 * - check for double files also done below
621 *
622 * Here we check if we already searched this directory.
623 * We already searched a directory if:
624 * 1) The directory is the same.
625 * 2) We would use the same wildcard string.
626 *
627 * Good if you have links on same directory via several ways
628 * or you have selfreferences in directories (e.g. SuSE Linux 6.3:
629 * /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop)
630 *
631 * This check is only needed for directories we work on for the
632 * first time (hence stackp->ff_filearray == NULL)
633 */
634 if (stackp->ffs_filearray == NULL
635 && ff_check_visited(&search_ctx->ffsc_dir_visited_list
636 ->ffvl_visited_list,
637 stackp->ffs_fix_path
638 , stackp->ffs_wc_path
639 ) == FAIL) {
640#ifdef FF_VERBOSE
641 if (p_verbose >= 5) {
642 verbose_enter_scroll();
643 smsg("Already Searched: %s (%s)",
644 stackp->ffs_fix_path, stackp->ffs_wc_path);
645 msg_puts("\n"); // don't overwrite this either
646 verbose_leave_scroll();
647 }
648#endif
649 ff_free_stack_element(stackp);
650 continue;
651 }
652#ifdef FF_VERBOSE
653 else if (p_verbose >= 5) {
654 verbose_enter_scroll();
655 smsg("Searching: %s (%s)",
656 stackp->ffs_fix_path, stackp->ffs_wc_path);
657 msg_puts("\n"); // don't overwrite this either
658 verbose_leave_scroll();
659 }
660#endif
661
662 /* check depth */
663 if (stackp->ffs_level <= 0) {
664 ff_free_stack_element(stackp);
665 continue;
666 }
667
668 file_path[0] = NUL;
669
670 /*
671 * If no filearray till now expand wildcards
672 * The function expand_wildcards() can handle an array of paths
673 * and all possible expands are returned in one array. We use this
674 * to handle the expansion of '**' into an empty string.
675 */
676 if (stackp->ffs_filearray == NULL) {
677 char_u *dirptrs[2];
678
679 /* we use filepath to build the path expand_wildcards() should
680 * expand.
681 */
682 dirptrs[0] = file_path;
683 dirptrs[1] = NULL;
684
685 // if we have a start dir copy it in
686 if (!vim_isAbsName(stackp->ffs_fix_path)
687 && search_ctx->ffsc_start_dir) {
688 if (STRLEN(search_ctx->ffsc_start_dir) + 1 >= MAXPATHL) {
689 ff_free_stack_element(stackp);
690 goto fail;
691 }
692 STRCPY(file_path, search_ctx->ffsc_start_dir);
693 if (!add_pathsep((char *)file_path)) {
694 ff_free_stack_element(stackp);
695 goto fail;
696 }
697 }
698
699 // append the fix part of the search path
700 if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1 >= MAXPATHL) {
701 ff_free_stack_element(stackp);
702 goto fail;
703 }
704 STRCAT(file_path, stackp->ffs_fix_path);
705 if (!add_pathsep((char *)file_path)) {
706 ff_free_stack_element(stackp);
707 goto fail;
708 }
709
710 rest_of_wildcards = stackp->ffs_wc_path;
711 if (*rest_of_wildcards != NUL) {
712 len = STRLEN(file_path);
713 if (STRNCMP(rest_of_wildcards, "**", 2) == 0) {
714 // pointer to the restrict byte
715 // The restrict byte is not a character!
716 p = rest_of_wildcards + 2;
717
718 if (*p > 0) {
719 (*p)--;
720 if (len + 1 >= MAXPATHL) {
721 ff_free_stack_element(stackp);
722 goto fail;
723 }
724 file_path[len++] = '*';
725 }
726
727 if (*p == 0) {
728 /* remove '**<numb> from wildcards */
729 STRMOVE(rest_of_wildcards, rest_of_wildcards + 3);
730 } else
731 rest_of_wildcards += 3;
732
733 if (stackp->ffs_star_star_empty == 0) {
734 /* if not done before, expand '**' to empty */
735 stackp->ffs_star_star_empty = 1;
736 dirptrs[1] = stackp->ffs_fix_path;
737 }
738 }
739
740 /*
741 * Here we copy until the next path separator or the end of
742 * the path. If we stop at a path separator, there is
743 * still something else left. This is handled below by
744 * pushing every directory returned from expand_wildcards()
745 * on the stack again for further search.
746 */
747 while (*rest_of_wildcards
748 && !vim_ispathsep(*rest_of_wildcards)) {
749 if (len + 1 >= MAXPATHL) {
750 ff_free_stack_element(stackp);
751 goto fail;
752 }
753 file_path[len++] = *rest_of_wildcards++;
754 }
755
756 file_path[len] = NUL;
757 if (vim_ispathsep(*rest_of_wildcards))
758 rest_of_wildcards++;
759 }
760
761 /*
762 * Expand wildcards like "*" and "$VAR".
763 * If the path is a URL don't try this.
764 */
765 if (path_with_url((char *)dirptrs[0])) {
766 stackp->ffs_filearray = xmalloc(sizeof(char *));
767 stackp->ffs_filearray[0] = vim_strsave(dirptrs[0]);
768 stackp->ffs_filearray_size = 1;
769 } else
770 /* Add EW_NOTWILD because the expanded path may contain
771 * wildcard characters that are to be taken literally.
772 * This is a bit of a hack. */
773 expand_wildcards((dirptrs[1] == NULL) ? 1 : 2, dirptrs,
774 &stackp->ffs_filearray_size,
775 &stackp->ffs_filearray,
776 EW_DIR|EW_ADDSLASH|EW_SILENT|EW_NOTWILD);
777
778 stackp->ffs_filearray_cur = 0;
779 stackp->ffs_stage = 0;
780 } else
781 rest_of_wildcards = &stackp->ffs_wc_path[
782 STRLEN(stackp->ffs_wc_path)];
783
784 if (stackp->ffs_stage == 0) {
785 /* this is the first time we work on this directory */
786 if (*rest_of_wildcards == NUL) {
787 /*
788 * We don't have further wildcards to expand, so we have to
789 * check for the final file now.
790 */
791 for (int i = stackp->ffs_filearray_cur;
792 i < stackp->ffs_filearray_size; ++i) {
793 if (!path_with_url((char *)stackp->ffs_filearray[i])
794 && !os_isdir(stackp->ffs_filearray[i]))
795 continue; /* not a directory */
796
797 // prepare the filename to be checked for existence below
798 if (STRLEN(stackp->ffs_filearray[i]) + 1
799 + STRLEN(search_ctx->ffsc_file_to_search) >= MAXPATHL) {
800 ff_free_stack_element(stackp);
801 goto fail;
802 }
803 STRCPY(file_path, stackp->ffs_filearray[i]);
804 if (!add_pathsep((char *)file_path)) {
805 ff_free_stack_element(stackp);
806 goto fail;
807 }
808 STRCAT(file_path, search_ctx->ffsc_file_to_search);
809
810 /*
811 * Try without extra suffix and then with suffixes
812 * from 'suffixesadd'.
813 */
814 len = STRLEN(file_path);
815 if (search_ctx->ffsc_tagfile)
816 suf = (char_u *)"";
817 else
818 suf = curbuf->b_p_sua;
819 for (;; ) {
820 /* if file exists and we didn't already find it */
821 if ((path_with_url((char *)file_path)
822 || (os_path_exists(file_path)
823 && (search_ctx->ffsc_find_what
824 == FINDFILE_BOTH
825 || ((search_ctx->ffsc_find_what
826 == FINDFILE_DIR)
827 == os_isdir(file_path)))))
828#ifndef FF_VERBOSE
829 && (ff_check_visited(
830 &search_ctx->ffsc_visited_list->ffvl_visited_list,
831 file_path
832 , (char_u *)""
833 ) == OK)
834#endif
835 ) {
836#ifdef FF_VERBOSE
837 if (ff_check_visited(
838 &search_ctx->ffsc_visited_list->ffvl_visited_list,
839 file_path
840 , (char_u *)""
841 ) == FAIL) {
842 if (p_verbose >= 5) {
843 verbose_enter_scroll();
844 smsg("Already: %s", file_path);
845 msg_puts("\n"); // don't overwrite this either
846 verbose_leave_scroll();
847 }
848 continue;
849 }
850#endif
851
852 /* push dir to examine rest of subdirs later */
853 assert(i < UCHAR_MAX - 1);
854 stackp->ffs_filearray_cur = (char_u)(i + 1);
855 ff_push(search_ctx, stackp);
856
857 if (!path_with_url((char *)file_path))
858 simplify_filename(file_path);
859 if (os_dirname(ff_expand_buffer, MAXPATHL)
860 == OK) {
861 p = path_shorten_fname(file_path,
862 ff_expand_buffer);
863 if (p != NULL)
864 STRMOVE(file_path, p);
865 }
866#ifdef FF_VERBOSE
867 if (p_verbose >= 5) {
868 verbose_enter_scroll();
869 smsg("HIT: %s", file_path);
870 msg_puts("\n"); // don't overwrite this either
871 verbose_leave_scroll();
872 }
873#endif
874 return file_path;
875 }
876
877 /* Not found or found already, try next suffix. */
878 if (*suf == NUL)
879 break;
880 assert(MAXPATHL >= len);
881 copy_option_part(&suf, file_path + len,
882 MAXPATHL - len, ",");
883 }
884 }
885 } else {
886 /*
887 * still wildcards left, push the directories for further
888 * search
889 */
890 for (int i = stackp->ffs_filearray_cur;
891 i < stackp->ffs_filearray_size; ++i) {
892 if (!os_isdir(stackp->ffs_filearray[i]))
893 continue; /* not a directory */
894
895 ff_push(search_ctx,
896 ff_create_stack_element(
897 stackp->ffs_filearray[i],
898 rest_of_wildcards,
899 stackp->ffs_level - 1, 0));
900 }
901 }
902 stackp->ffs_filearray_cur = 0;
903 stackp->ffs_stage = 1;
904 }
905
906 /*
907 * if wildcards contains '**' we have to descent till we reach the
908 * leaves of the directory tree.
909 */
910 if (STRNCMP(stackp->ffs_wc_path, "**", 2) == 0) {
911 for (int i = stackp->ffs_filearray_cur;
912 i < stackp->ffs_filearray_size; ++i) {
913 if (fnamecmp(stackp->ffs_filearray[i],
914 stackp->ffs_fix_path) == 0)
915 continue; /* don't repush same directory */
916 if (!os_isdir(stackp->ffs_filearray[i]))
917 continue; /* not a directory */
918 ff_push(search_ctx,
919 ff_create_stack_element(stackp->ffs_filearray[i],
920 stackp->ffs_wc_path, stackp->ffs_level - 1, 1));
921 }
922 }
923
924 /* we are done with the current directory */
925 ff_free_stack_element(stackp);
926
927 }
928
929 /* If we reached this, we didn't find anything downwards.
930 * Let's check if we should do an upward search.
931 */
932 if (search_ctx->ffsc_start_dir
933 && search_ctx->ffsc_stopdirs_v != NULL && !got_int) {
934 ff_stack_T *sptr;
935
936 /* is the last starting directory in the stop list? */
937 if (ff_path_in_stoplist(search_ctx->ffsc_start_dir,
938 (int)(path_end - search_ctx->ffsc_start_dir),
939 search_ctx->ffsc_stopdirs_v) == TRUE)
940 break;
941
942 /* cut of last dir */
943 while (path_end > search_ctx->ffsc_start_dir
944 && vim_ispathsep(*path_end))
945 path_end--;
946 while (path_end > search_ctx->ffsc_start_dir
947 && !vim_ispathsep(path_end[-1]))
948 path_end--;
949 *path_end = 0;
950 path_end--;
951
952 if (*search_ctx->ffsc_start_dir == 0)
953 break;
954
955 if (STRLEN(search_ctx->ffsc_start_dir) + 1
956 + STRLEN(search_ctx->ffsc_fix_path) >= MAXPATHL) {
957 goto fail;
958 }
959 STRCPY(file_path, search_ctx->ffsc_start_dir);
960 if (!add_pathsep((char *)file_path)) {
961 goto fail;
962 }
963 STRCAT(file_path, search_ctx->ffsc_fix_path);
964
965 /* create a new stack entry */
966 sptr = ff_create_stack_element(file_path,
967 search_ctx->ffsc_wc_path, search_ctx->ffsc_level, 0);
968 ff_push(search_ctx, sptr);
969 } else
970 break;
971 }
972
973fail:
974 xfree(file_path);
975 return NULL;
976}
977
978/*
979 * Free the list of lists of visited files and directories
980 * Can handle it if the passed search_context is NULL;
981 */
982void vim_findfile_free_visited(void *search_ctx_arg)
983{
984 ff_search_ctx_T *search_ctx;
985
986 if (search_ctx_arg == NULL)
987 return;
988
989 search_ctx = (ff_search_ctx_T *)search_ctx_arg;
990 vim_findfile_free_visited_list(&search_ctx->ffsc_visited_lists_list);
991 vim_findfile_free_visited_list(&search_ctx->ffsc_dir_visited_lists_list);
992}
993
994static void vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp)
995{
996 ff_visited_list_hdr_T *vp;
997
998 while (*list_headp != NULL) {
999 vp = (*list_headp)->ffvl_next;
1000 ff_free_visited_list((*list_headp)->ffvl_visited_list);
1001
1002 xfree((*list_headp)->ffvl_filename);
1003 xfree(*list_headp);
1004 *list_headp = vp;
1005 }
1006 *list_headp = NULL;
1007}
1008
1009static void ff_free_visited_list(ff_visited_T *vl)
1010{
1011 ff_visited_T *vp;
1012
1013 while (vl != NULL) {
1014 vp = vl->ffv_next;
1015 xfree(vl->ffv_wc_path);
1016 xfree(vl);
1017 vl = vp;
1018 }
1019 vl = NULL;
1020}
1021
1022/*
1023 * Returns the already visited list for the given filename. If none is found it
1024 * allocates a new one.
1025 */
1026static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, ff_visited_list_hdr_T **list_headp)
1027{
1028 ff_visited_list_hdr_T *retptr = NULL;
1029
1030 /* check if a visited list for the given filename exists */
1031 if (*list_headp != NULL) {
1032 retptr = *list_headp;
1033 while (retptr != NULL) {
1034 if (fnamecmp(filename, retptr->ffvl_filename) == 0) {
1035#ifdef FF_VERBOSE
1036 if (p_verbose >= 5) {
1037 verbose_enter_scroll();
1038 smsg("ff_get_visited_list: FOUND list for %s", filename);
1039 msg_puts("\n"); // don't overwrite this either
1040 verbose_leave_scroll();
1041 }
1042#endif
1043 return retptr;
1044 }
1045 retptr = retptr->ffvl_next;
1046 }
1047 }
1048
1049#ifdef FF_VERBOSE
1050 if (p_verbose >= 5) {
1051 verbose_enter_scroll();
1052 smsg("ff_get_visited_list: new list for %s", filename);
1053 msg_puts("\n"); // don't overwrite this either
1054 verbose_leave_scroll();
1055 }
1056#endif
1057
1058 /*
1059 * if we reach this we didn't find a list and we have to allocate new list
1060 */
1061 retptr = xmalloc(sizeof(*retptr));
1062
1063 retptr->ffvl_visited_list = NULL;
1064 retptr->ffvl_filename = vim_strsave(filename);
1065 retptr->ffvl_next = *list_headp;
1066 *list_headp = retptr;
1067
1068 return retptr;
1069}
1070
1071// Check if two wildcard paths are equal.
1072// They are equal if:
1073// - both paths are NULL
1074// - they have the same length
1075// - char by char comparison is OK
1076// - the only differences are in the counters behind a '**', so
1077// '**\20' is equal to '**\24'
1078static bool ff_wc_equal(char_u *s1, char_u *s2)
1079{
1080 int i, j;
1081 int c1 = NUL;
1082 int c2 = NUL;
1083 int prev1 = NUL;
1084 int prev2 = NUL;
1085
1086 if (s1 == s2) {
1087 return true;
1088 }
1089
1090 if (s1 == NULL || s2 == NULL) {
1091 return false;
1092 }
1093
1094 for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;) {
1095 c1 = PTR2CHAR(s1 + i);
1096 c2 = PTR2CHAR(s2 + j);
1097
1098 if ((p_fic ? mb_tolower(c1) != mb_tolower(c2) : c1 != c2)
1099 && (prev1 != '*' || prev2 != '*')) {
1100 return false;
1101 }
1102 prev2 = prev1;
1103 prev1 = c1;
1104
1105 i += MB_PTR2LEN(s1 + i);
1106 j += MB_PTR2LEN(s2 + j);
1107 }
1108 return s1[i] == s2[j];
1109}
1110
1111/*
1112 * maintains the list of already visited files and dirs
1113 * returns FAIL if the given file/dir is already in the list
1114 * returns OK if it is newly added
1115 */
1116static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *wc_path)
1117{
1118 ff_visited_T *vp;
1119 bool url = false;
1120
1121 FileID file_id;
1122 // For an URL we only compare the name, otherwise we compare the
1123 // device/inode.
1124 if (path_with_url((char *)fname)) {
1125 STRLCPY(ff_expand_buffer, fname, MAXPATHL);
1126 url = true;
1127 } else {
1128 ff_expand_buffer[0] = NUL;
1129 if (!os_fileid((char *)fname, &file_id)) {
1130 return FAIL;
1131 }
1132 }
1133
1134 /* check against list of already visited files */
1135 for (vp = *visited_list; vp != NULL; vp = vp->ffv_next) {
1136 if ((url && fnamecmp(vp->ffv_fname, ff_expand_buffer) == 0)
1137 || (!url && vp->file_id_valid
1138 && os_fileid_equal(&(vp->file_id), &file_id))) {
1139 // are the wildcard parts equal
1140 if (ff_wc_equal(vp->ffv_wc_path, wc_path)) {
1141 // already visited
1142 return FAIL;
1143 }
1144 }
1145 }
1146
1147 /*
1148 * New file/dir. Add it to the list of visited files/dirs.
1149 */
1150 vp = xmalloc(sizeof(ff_visited_T) + STRLEN(ff_expand_buffer));
1151
1152 if (!url) {
1153 vp->file_id_valid = true;
1154 vp->file_id = file_id;
1155 vp->ffv_fname[0] = NUL;
1156 } else {
1157 vp->file_id_valid = false;
1158 STRCPY(vp->ffv_fname, ff_expand_buffer);
1159 }
1160
1161 if (wc_path != NULL)
1162 vp->ffv_wc_path = vim_strsave(wc_path);
1163 else
1164 vp->ffv_wc_path = NULL;
1165
1166 vp->ffv_next = *visited_list;
1167 *visited_list = vp;
1168
1169 return OK;
1170}
1171
1172/*
1173 * create stack element from given path pieces
1174 */
1175static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, int level, int star_star_empty)
1176{
1177 ff_stack_T *new = xmalloc(sizeof(ff_stack_T));
1178
1179 new->ffs_prev = NULL;
1180 new->ffs_filearray = NULL;
1181 new->ffs_filearray_size = 0;
1182 new->ffs_filearray_cur = 0;
1183 new->ffs_stage = 0;
1184 new->ffs_level = level;
1185 new->ffs_star_star_empty = star_star_empty;
1186
1187 /* the following saves NULL pointer checks in vim_findfile */
1188 if (fix_part == NULL)
1189 fix_part = (char_u *)"";
1190 new->ffs_fix_path = vim_strsave(fix_part);
1191
1192 if (wc_part == NULL)
1193 wc_part = (char_u *)"";
1194 new->ffs_wc_path = vim_strsave(wc_part);
1195
1196 return new;
1197}
1198
1199/*
1200 * Push a dir on the directory stack.
1201 */
1202static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr)
1203{
1204 /* check for NULL pointer, not to return an error to the user, but
1205 * to prevent a crash */
1206 if (stack_ptr != NULL) {
1207 stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr;
1208 search_ctx->ffsc_stack_ptr = stack_ptr;
1209 }
1210}
1211
1212/*
1213 * Pop a dir from the directory stack.
1214 * Returns NULL if stack is empty.
1215 */
1216static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx)
1217{
1218 ff_stack_T *sptr;
1219
1220 sptr = search_ctx->ffsc_stack_ptr;
1221 if (search_ctx->ffsc_stack_ptr != NULL)
1222 search_ctx->ffsc_stack_ptr = search_ctx->ffsc_stack_ptr->ffs_prev;
1223
1224 return sptr;
1225}
1226
1227/*
1228 * free the given stack element
1229 */
1230static void ff_free_stack_element(ff_stack_T *const stack_ptr)
1231{
1232 if (stack_ptr == NULL) {
1233 return;
1234 }
1235
1236 // free handles possible NULL pointers
1237 xfree(stack_ptr->ffs_fix_path);
1238 xfree(stack_ptr->ffs_wc_path);
1239
1240 if (stack_ptr->ffs_filearray != NULL) {
1241 FreeWild(stack_ptr->ffs_filearray_size, stack_ptr->ffs_filearray);
1242 }
1243
1244 xfree(stack_ptr);
1245}
1246
1247/*
1248 * Clear the search context, but NOT the visited list.
1249 */
1250static void ff_clear(ff_search_ctx_T *search_ctx)
1251{
1252 ff_stack_T *sptr;
1253
1254 /* clear up stack */
1255 while ((sptr = ff_pop(search_ctx)) != NULL)
1256 ff_free_stack_element(sptr);
1257
1258 xfree(search_ctx->ffsc_file_to_search);
1259 xfree(search_ctx->ffsc_start_dir);
1260 xfree(search_ctx->ffsc_fix_path);
1261 xfree(search_ctx->ffsc_wc_path);
1262
1263 if (search_ctx->ffsc_stopdirs_v != NULL) {
1264 int i = 0;
1265
1266 while (search_ctx->ffsc_stopdirs_v[i] != NULL) {
1267 xfree(search_ctx->ffsc_stopdirs_v[i]);
1268 i++;
1269 }
1270 xfree(search_ctx->ffsc_stopdirs_v);
1271 }
1272 search_ctx->ffsc_stopdirs_v = NULL;
1273
1274 /* reset everything */
1275 search_ctx->ffsc_file_to_search = NULL;
1276 search_ctx->ffsc_start_dir = NULL;
1277 search_ctx->ffsc_fix_path = NULL;
1278 search_ctx->ffsc_wc_path = NULL;
1279 search_ctx->ffsc_level = 0;
1280}
1281
1282/*
1283 * check if the given path is in the stopdirs
1284 * returns TRUE if yes else FALSE
1285 */
1286static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v)
1287{
1288 int i = 0;
1289
1290 /* eat up trailing path separators, except the first */
1291 while (path_len > 1 && vim_ispathsep(path[path_len - 1]))
1292 path_len--;
1293
1294 /* if no path consider it as match */
1295 if (path_len == 0)
1296 return TRUE;
1297
1298 for (i = 0; stopdirs_v[i] != NULL; i++) {
1299 if ((int)STRLEN(stopdirs_v[i]) > path_len) {
1300 /* match for parent directory. So '/home' also matches
1301 * '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
1302 * '/home/r' would also match '/home/rks'
1303 */
1304 if (fnamencmp(stopdirs_v[i], path, path_len) == 0
1305 && vim_ispathsep(stopdirs_v[i][path_len]))
1306 return TRUE;
1307 } else {
1308 if (fnamecmp(stopdirs_v[i], path) == 0)
1309 return TRUE;
1310 }
1311 }
1312 return FALSE;
1313}
1314
1315/*
1316 * Find the file name "ptr[len]" in the path. Also finds directory names.
1317 *
1318 * On the first call set the parameter 'first' to TRUE to initialize
1319 * the search. For repeating calls to FALSE.
1320 *
1321 * Repeating calls will return other files called 'ptr[len]' from the path.
1322 *
1323 * Only on the first call 'ptr' and 'len' are used. For repeating calls they
1324 * don't need valid values.
1325 *
1326 * If nothing found on the first call the option FNAME_MESS will issue the
1327 * message:
1328 * 'Can't find file "<file>" in path'
1329 * On repeating calls:
1330 * 'No more file "<file>" found in path'
1331 *
1332 * options:
1333 * FNAME_MESS give error message when not found
1334 *
1335 * Uses NameBuff[]!
1336 *
1337 * Returns an allocated string for the file name. NULL for error.
1338 *
1339 */
1340char_u *
1341find_file_in_path (
1342 char_u *ptr, /* file name */
1343 size_t len, /* length of file name */
1344 int options,
1345 int first, /* use count'th matching file name */
1346 char_u *rel_fname /* file name searching relative to */
1347)
1348{
1349 return find_file_in_path_option(ptr, len, options, first,
1350 (*curbuf->b_p_path == NUL
1351 ? p_path
1352 : curbuf->b_p_path),
1353 FINDFILE_BOTH, rel_fname, curbuf->b_p_sua);
1354}
1355
1356static char_u *ff_file_to_find = NULL;
1357static void *fdip_search_ctx = NULL;
1358
1359#if defined(EXITFREE)
1360void free_findfile(void)
1361{
1362 xfree(ff_file_to_find);
1363 vim_findfile_cleanup(fdip_search_ctx);
1364 xfree(ff_expand_buffer);
1365}
1366
1367#endif
1368
1369/*
1370 * Find the directory name "ptr[len]" in the path.
1371 *
1372 * options:
1373 * FNAME_MESS give error message when not found
1374 * FNAME_UNESC unescape backslashes
1375 *
1376 * Uses NameBuff[]!
1377 *
1378 * Returns an allocated string for the file name. NULL for error.
1379 */
1380char_u *
1381find_directory_in_path (
1382 char_u *ptr, /* file name */
1383 size_t len, /* length of file name */
1384 int options,
1385 char_u *rel_fname /* file name searching relative to */
1386)
1387{
1388 return find_file_in_path_option(ptr, len, options, TRUE, p_cdpath,
1389 FINDFILE_DIR, rel_fname, (char_u *)"");
1390}
1391
1392char_u *
1393find_file_in_path_option (
1394 char_u *ptr, /* file name */
1395 size_t len, /* length of file name */
1396 int options,
1397 int first, /* use count'th matching file name */
1398 char_u *path_option, /* p_path or p_cdpath */
1399 int find_what, /* FINDFILE_FILE, _DIR or _BOTH */
1400 char_u *rel_fname, /* file name we are looking relative to. */
1401 char_u *suffixes /* list of suffixes, 'suffixesadd' option */
1402)
1403{
1404 static char_u *dir;
1405 static int did_findfile_init = FALSE;
1406 char_u save_char;
1407 char_u *file_name = NULL;
1408 char_u *buf = NULL;
1409 int rel_to_curdir;
1410
1411 if (rel_fname != NULL && path_with_url((const char *)rel_fname)) {
1412 // Do not attempt to search "relative" to a URL. #6009
1413 rel_fname = NULL;
1414 }
1415
1416 if (first == TRUE) {
1417 /* copy file name into NameBuff, expanding environment variables */
1418 save_char = ptr[len];
1419 ptr[len] = NUL;
1420 expand_env_esc(ptr, NameBuff, MAXPATHL, false, true, NULL);
1421 ptr[len] = save_char;
1422
1423 xfree(ff_file_to_find);
1424 ff_file_to_find = vim_strsave(NameBuff);
1425 if (options & FNAME_UNESC) {
1426 // Change all "\ " to " ".
1427 for (ptr = ff_file_to_find; *ptr != NUL; ++ptr) {
1428 if (ptr[0] == '\\' && ptr[1] == ' ') {
1429 memmove(ptr, ptr + 1, STRLEN(ptr));
1430 }
1431 }
1432 }
1433 }
1434
1435 rel_to_curdir = (ff_file_to_find[0] == '.'
1436 && (ff_file_to_find[1] == NUL
1437 || vim_ispathsep(ff_file_to_find[1])
1438 || (ff_file_to_find[1] == '.'
1439 && (ff_file_to_find[2] == NUL
1440 || vim_ispathsep(ff_file_to_find[2])))));
1441 if (vim_isAbsName(ff_file_to_find)
1442 // "..", "../path", "." and "./path": don't use the path_option
1443 || rel_to_curdir
1444#if defined(WIN32)
1445 // handle "\tmp" as absolute path
1446 || vim_ispathsep(ff_file_to_find[0])
1447 // handle "c:name" as absolute path
1448 || (ff_file_to_find[0] != NUL && ff_file_to_find[1] == ':')
1449#endif
1450 ) {
1451 /*
1452 * Absolute path, no need to use "path_option".
1453 * If this is not a first call, return NULL. We already returned a
1454 * filename on the first call.
1455 */
1456 if (first == TRUE) {
1457 if (path_with_url((char *)ff_file_to_find)) {
1458 file_name = vim_strsave(ff_file_to_find);
1459 goto theend;
1460 }
1461
1462 /* When FNAME_REL flag given first use the directory of the file.
1463 * Otherwise or when this fails use the current directory. */
1464 for (int run = 1; run <= 2; ++run) {
1465 size_t l = STRLEN(ff_file_to_find);
1466 if (run == 1
1467 && rel_to_curdir
1468 && (options & FNAME_REL)
1469 && rel_fname != NULL
1470 && STRLEN(rel_fname) + l < MAXPATHL) {
1471 STRCPY(NameBuff, rel_fname);
1472 STRCPY(path_tail(NameBuff), ff_file_to_find);
1473 l = STRLEN(NameBuff);
1474 } else {
1475 STRCPY(NameBuff, ff_file_to_find);
1476 run = 2;
1477 }
1478
1479 /* When the file doesn't exist, try adding parts of
1480 * 'suffixesadd'. */
1481 buf = suffixes;
1482 for (;; ) {
1483 if (
1484 (os_path_exists(NameBuff)
1485 && (find_what == FINDFILE_BOTH
1486 || ((find_what == FINDFILE_DIR)
1487 == os_isdir(NameBuff))))) {
1488 file_name = vim_strsave(NameBuff);
1489 goto theend;
1490 }
1491 if (*buf == NUL)
1492 break;
1493 assert(MAXPATHL >= l);
1494 copy_option_part(&buf, NameBuff + l, MAXPATHL - l, ",");
1495 }
1496 }
1497 }
1498 } else {
1499 /*
1500 * Loop over all paths in the 'path' or 'cdpath' option.
1501 * When "first" is set, first setup to the start of the option.
1502 * Otherwise continue to find the next match.
1503 */
1504 if (first == TRUE) {
1505 /* vim_findfile_free_visited can handle a possible NULL pointer */
1506 vim_findfile_free_visited(fdip_search_ctx);
1507 dir = path_option;
1508 did_findfile_init = FALSE;
1509 }
1510
1511 for (;; ) {
1512 if (did_findfile_init) {
1513 file_name = vim_findfile(fdip_search_ctx);
1514 if (file_name != NULL)
1515 break;
1516
1517 did_findfile_init = FALSE;
1518 } else {
1519 char_u *r_ptr;
1520
1521 if (dir == NULL || *dir == NUL) {
1522 /* We searched all paths of the option, now we can
1523 * free the search context. */
1524 vim_findfile_cleanup(fdip_search_ctx);
1525 fdip_search_ctx = NULL;
1526 break;
1527 }
1528
1529 buf = xmalloc(MAXPATHL);
1530
1531 /* copy next path */
1532 buf[0] = 0;
1533 copy_option_part(&dir, buf, MAXPATHL, " ,");
1534
1535 /* get the stopdir string */
1536 r_ptr = vim_findfile_stopdir(buf);
1537 fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find,
1538 r_ptr, 100, FALSE, find_what,
1539 fdip_search_ctx, FALSE, rel_fname);
1540 if (fdip_search_ctx != NULL)
1541 did_findfile_init = TRUE;
1542 xfree(buf);
1543 }
1544 }
1545 }
1546 if (file_name == NULL && (options & FNAME_MESS)) {
1547 if (first == TRUE) {
1548 if (find_what == FINDFILE_DIR)
1549 EMSG2(_("E344: Can't find directory \"%s\" in cdpath"),
1550 ff_file_to_find);
1551 else
1552 EMSG2(_("E345: Can't find file \"%s\" in path"),
1553 ff_file_to_find);
1554 } else {
1555 if (find_what == FINDFILE_DIR)
1556 EMSG2(_("E346: No more directory \"%s\" found in cdpath"),
1557 ff_file_to_find);
1558 else
1559 EMSG2(_("E347: No more file \"%s\" found in path"),
1560 ff_file_to_find);
1561 }
1562 }
1563
1564theend:
1565 return file_name;
1566}
1567
1568void do_autocmd_dirchanged(char *new_dir, CdScope scope)
1569{
1570 static bool recursive = false;
1571
1572 if (recursive || !has_event(EVENT_DIRCHANGED)) {
1573 // No autocommand was defined or we changed
1574 // the directory from this autocommand.
1575 return;
1576 }
1577
1578 recursive = true;
1579
1580 dict_T *dict = get_vim_var_dict(VV_EVENT);
1581 char buf[8];
1582
1583 switch (scope) {
1584 case kCdScopeGlobal: {
1585 snprintf(buf, sizeof(buf), "global");
1586 break;
1587 }
1588 case kCdScopeTab: {
1589 snprintf(buf, sizeof(buf), "tab");
1590 break;
1591 }
1592 case kCdScopeWindow: {
1593 snprintf(buf, sizeof(buf), "window");
1594 break;
1595 }
1596 case kCdScopeInvalid: {
1597 // Should never happen.
1598 assert(false);
1599 }
1600 }
1601
1602 tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614
1603 tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
1604 tv_dict_set_keys_readonly(dict);
1605
1606 apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false,
1607 curbuf);
1608
1609 tv_dict_clear(dict);
1610
1611 recursive = false;
1612}
1613
1614/// Change to a file's directory.
1615/// Caller must call shorten_fnames()!
1616/// @return OK or FAIL
1617int vim_chdirfile(char_u *fname)
1618{
1619 char dir[MAXPATHL];
1620
1621 STRLCPY(dir, fname, MAXPATHL);
1622 *path_tail_with_sep((char_u *)dir) = NUL;
1623
1624 if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) {
1625 NameBuff[0] = NUL;
1626 }
1627
1628 if (os_chdir(dir) != 0) {
1629 return FAIL;
1630 }
1631
1632#ifdef BACKSLASH_IN_FILENAME
1633 slash_adjust((char_u *)dir);
1634#endif
1635 if (!strequal(dir, (char *)NameBuff)) {
1636 do_autocmd_dirchanged(dir, kCdScopeWindow);
1637 }
1638
1639 return OK;
1640}
1641
1642/// Change directory to "new_dir". Search 'cdpath' for relative directory names.
1643int vim_chdir(char_u *new_dir)
1644{
1645 char_u *dir_name = find_directory_in_path(new_dir, STRLEN(new_dir),
1646 FNAME_MESS, curbuf->b_ffname);
1647 if (dir_name == NULL) {
1648 return -1;
1649 }
1650
1651 int r = os_chdir((char *)dir_name);
1652 xfree(dir_name);
1653 return r;
1654}
1655
1656