xref: /vim-8.2.3635/src/findfile.c (revision e015d99a)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * findfile.c: Search for files in directories listed in 'path'
12  */
13 
14 #include "vim.h"
15 
16 /*
17  * File searching functions for 'path', 'tags' and 'cdpath' options.
18  * External visible functions:
19  * vim_findfile_init()		creates/initialises the search context
20  * vim_findfile_free_visited()	free list of visited files/dirs of search
21  *				context
22  * vim_findfile()		find a file in the search context
23  * vim_findfile_cleanup()	cleanup/free search context created by
24  *				vim_findfile_init()
25  *
26  * All static functions and variables start with 'ff_'
27  *
28  * In general it works like this:
29  * First you create yourself a search context by calling vim_findfile_init().
30  * It is possible to give a search context from a previous call to
31  * vim_findfile_init(), so it can be reused. After this you call vim_findfile()
32  * until you are satisfied with the result or it returns NULL. On every call it
33  * returns the next file which matches the conditions given to
34  * vim_findfile_init(). If it doesn't find a next file it returns NULL.
35  *
36  * It is possible to call vim_findfile_init() again to reinitialise your search
37  * with some new parameters. Don't forget to pass your old search context to
38  * it, so it can reuse it and especially reuse the list of already visited
39  * directories. If you want to delete the list of already visited directories
40  * simply call vim_findfile_free_visited().
41  *
42  * When you are done call vim_findfile_cleanup() to free the search context.
43  *
44  * The function vim_findfile_init() has a long comment, which describes the
45  * needed parameters.
46  *
47  *
48  *
49  * ATTENTION:
50  * ==========
51  *	Also we use an allocated search context here, this functions are NOT
52  *	thread-safe!!!!!
53  *
54  *	To minimize parameter passing (or because I'm to lazy), only the
55  *	external visible functions get a search context as a parameter. This is
56  *	then assigned to a static global, which is used throughout the local
57  *	functions.
58  */
59 
60 /*
61  * type for the directory search stack
62  */
63 typedef struct ff_stack
64 {
65     struct ff_stack	*ffs_prev;
66 
67     // the fix part (no wildcards) and the part containing the wildcards
68     // of the search path
69     char_u		*ffs_fix_path;
70 #ifdef FEAT_PATH_EXTRA
71     char_u		*ffs_wc_path;
72 #endif
73 
74     // files/dirs found in the above directory, matched by the first wildcard
75     // of wc_part
76     char_u		**ffs_filearray;
77     int			ffs_filearray_size;
78     char_u		ffs_filearray_cur;   // needed for partly handled dirs
79 
80     // to store status of partly handled directories
81     // 0: we work on this directory for the first time
82     // 1: this directory was partly searched in an earlier step
83     int			ffs_stage;
84 
85     // How deep are we in the directory tree?
86     // Counts backward from value of level parameter to vim_findfile_init
87     int			ffs_level;
88 
89     // Did we already expand '**' to an empty string?
90     int			ffs_star_star_empty;
91 } ff_stack_T;
92 
93 /*
94  * type for already visited directories or files.
95  */
96 typedef struct ff_visited
97 {
98     struct ff_visited	*ffv_next;
99 
100 #ifdef FEAT_PATH_EXTRA
101     // Visited directories are different if the wildcard string are
102     // different. So we have to save it.
103     char_u		*ffv_wc_path;
104 #endif
105     // for unix use inode etc for comparison (needed because of links), else
106     // use filename.
107 #ifdef UNIX
108     int			ffv_dev_valid;	// ffv_dev and ffv_ino were set
109     dev_t		ffv_dev;	// device number
110     ino_t		ffv_ino;	// inode number
111 #endif
112     // The memory for this struct is allocated according to the length of
113     // ffv_fname.
114     char_u		ffv_fname[1];	// actually longer
115 } ff_visited_T;
116 
117 /*
118  * We might have to manage several visited lists during a search.
119  * This is especially needed for the tags option. If tags is set to:
120  *      "./++/tags,./++/TAGS,++/tags"  (replace + with *)
121  * So we have to do 3 searches:
122  *   1) search from the current files directory downward for the file "tags"
123  *   2) search from the current files directory downward for the file "TAGS"
124  *   3) search from Vims current directory downwards for the file "tags"
125  * As you can see, the first and the third search are for the same file, so for
126  * the third search we can use the visited list of the first search. For the
127  * second search we must start from a empty visited list.
128  * The struct ff_visited_list_hdr is used to manage a linked list of already
129  * visited lists.
130  */
131 typedef struct ff_visited_list_hdr
132 {
133     struct ff_visited_list_hdr	*ffvl_next;
134 
135     // the filename the attached visited list is for
136     char_u			*ffvl_filename;
137 
138     ff_visited_T		*ffvl_visited_list;
139 
140 } ff_visited_list_hdr_T;
141 
142 
143 /*
144  * '**' can be expanded to several directory levels.
145  * Set the default maximum depth.
146  */
147 #define FF_MAX_STAR_STAR_EXPAND ((char_u)30)
148 
149 /*
150  * The search context:
151  *   ffsc_stack_ptr:	the stack for the dirs to search
152  *   ffsc_visited_list: the currently active visited list
153  *   ffsc_dir_visited_list: the currently active visited list for search dirs
154  *   ffsc_visited_lists_list: the list of all visited lists
155  *   ffsc_dir_visited_lists_list: the list of all visited lists for search dirs
156  *   ffsc_file_to_search:     the file to search for
157  *   ffsc_start_dir:	the starting directory, if search path was relative
158  *   ffsc_fix_path:	the fix part of the given path (without wildcards)
159  *			Needed for upward search.
160  *   ffsc_wc_path:	the part of the given path containing wildcards
161  *   ffsc_level:	how many levels of dirs to search downwards
162  *   ffsc_stopdirs_v:	array of stop directories for upward search
163  *   ffsc_find_what:	FINDFILE_BOTH, FINDFILE_DIR or FINDFILE_FILE
164  *   ffsc_tagfile:	searching for tags file, don't use 'suffixesadd'
165  */
166 typedef struct ff_search_ctx_T
167 {
168      ff_stack_T			*ffsc_stack_ptr;
169      ff_visited_list_hdr_T	*ffsc_visited_list;
170      ff_visited_list_hdr_T	*ffsc_dir_visited_list;
171      ff_visited_list_hdr_T	*ffsc_visited_lists_list;
172      ff_visited_list_hdr_T	*ffsc_dir_visited_lists_list;
173      char_u			*ffsc_file_to_search;
174      char_u			*ffsc_start_dir;
175      char_u			*ffsc_fix_path;
176 #ifdef FEAT_PATH_EXTRA
177      char_u			*ffsc_wc_path;
178      int			ffsc_level;
179      char_u			**ffsc_stopdirs_v;
180 #endif
181      int			ffsc_find_what;
182      int			ffsc_tagfile;
183 } ff_search_ctx_T;
184 
185 // locally needed functions
186 #ifdef FEAT_PATH_EXTRA
187 static int ff_check_visited(ff_visited_T **, char_u *, char_u *);
188 #else
189 static int ff_check_visited(ff_visited_T **, char_u *);
190 #endif
191 static void vim_findfile_free_visited(void *search_ctx_arg);
192 static void vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp);
193 static void ff_free_visited_list(ff_visited_T *vl);
194 static ff_visited_list_hdr_T* ff_get_visited_list(char_u *, ff_visited_list_hdr_T **list_headp);
195 
196 static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr);
197 static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx);
198 static void ff_clear(ff_search_ctx_T *search_ctx);
199 static void ff_free_stack_element(ff_stack_T *stack_ptr);
200 #ifdef FEAT_PATH_EXTRA
201 static ff_stack_T *ff_create_stack_element(char_u *, char_u *, int, int);
202 #else
203 static ff_stack_T *ff_create_stack_element(char_u *, int, int);
204 #endif
205 #ifdef FEAT_PATH_EXTRA
206 static int ff_path_in_stoplist(char_u *, int, char_u **);
207 #endif
208 
209 static char_u e_pathtoolong[] = N_("E854: path too long for completion");
210 
211 static char_u	*ff_expand_buffer = NULL; // used for expanding filenames
212 
213 #if 0
214 /*
215  * if someone likes findfirst/findnext, here are the functions
216  * NOT TESTED!!
217  */
218 
219 static void *ff_fn_search_context = NULL;
220 
221     char_u *
222 vim_findfirst(char_u *path, char_u *filename, int level)
223 {
224     ff_fn_search_context =
225 	vim_findfile_init(path, filename, NULL, level, TRUE, FALSE,
226 		ff_fn_search_context, rel_fname);
227     if (NULL == ff_fn_search_context)
228 	return NULL;
229     else
230 	return vim_findnext()
231 }
232 
233     char_u *
234 vim_findnext(void)
235 {
236     char_u *ret = vim_findfile(ff_fn_search_context);
237 
238     if (NULL == ret)
239     {
240 	vim_findfile_cleanup(ff_fn_search_context);
241 	ff_fn_search_context = NULL;
242     }
243     return ret;
244 }
245 #endif
246 
247 /*
248  * Initialization routine for vim_findfile().
249  *
250  * Returns the newly allocated search context or NULL if an error occurred.
251  *
252  * Don't forget to clean up by calling vim_findfile_cleanup() if you are done
253  * with the search context.
254  *
255  * Find the file 'filename' in the directory 'path'.
256  * The parameter 'path' may contain wildcards. If so only search 'level'
257  * directories deep. The parameter 'level' is the absolute maximum and is
258  * not related to restricts given to the '**' wildcard. If 'level' is 100
259  * and you use '**200' vim_findfile() will stop after 100 levels.
260  *
261  * 'filename' cannot contain wildcards!  It is used as-is, no backslashes to
262  * escape special characters.
263  *
264  * If 'stopdirs' is not NULL and nothing is found downward, the search is
265  * restarted on the next higher directory level. This is repeated until the
266  * start-directory of a search is contained in 'stopdirs'. 'stopdirs' has the
267  * format ";*<dirname>*\(;<dirname>\)*;\=$".
268  *
269  * If the 'path' is relative, the starting dir for the search is either VIM's
270  * current dir or if the path starts with "./" the current files dir.
271  * If the 'path' is absolute, the starting dir is that part of the path before
272  * the first wildcard.
273  *
274  * Upward search is only done on the starting dir.
275  *
276  * If 'free_visited' is TRUE the list of already visited files/directories is
277  * cleared. Set this to FALSE if you just want to search from another
278  * directory, but want to be sure that no directory from a previous search is
279  * searched again. This is useful if you search for a file at different places.
280  * The list of visited files/dirs can also be cleared with the function
281  * vim_findfile_free_visited().
282  *
283  * Set the parameter 'find_what' to FINDFILE_DIR if you want to search for
284  * directories only, FINDFILE_FILE for files only, FINDFILE_BOTH for both.
285  *
286  * A search context returned by a previous call to vim_findfile_init() can be
287  * passed in the parameter "search_ctx_arg".  This context is reused and
288  * reinitialized with the new parameters.  The list of already visited
289  * directories from this context is only deleted if the parameter
290  * "free_visited" is true.  Be aware that the passed "search_ctx_arg" is freed
291  * if the reinitialization fails.
292  *
293  * If you don't have a search context from a previous call "search_ctx_arg"
294  * must be NULL.
295  *
296  * This function silently ignores a few errors, vim_findfile() will have
297  * limited functionality then.
298  */
299     void *
vim_findfile_init(char_u * path,char_u * filename,char_u * stopdirs UNUSED,int level,int free_visited,int find_what,void * search_ctx_arg,int tagfile,char_u * rel_fname)300 vim_findfile_init(
301     char_u	*path,
302     char_u	*filename,
303     char_u	*stopdirs UNUSED,
304     int		level,
305     int		free_visited,
306     int		find_what,
307     void	*search_ctx_arg,
308     int		tagfile,	// expanding names of tags files
309     char_u	*rel_fname)	// file name to use for "."
310 {
311 #ifdef FEAT_PATH_EXTRA
312     char_u		*wc_part;
313 #endif
314     ff_stack_T		*sptr;
315     ff_search_ctx_T	*search_ctx;
316 
317     // If a search context is given by the caller, reuse it, else allocate a
318     // new one.
319     if (search_ctx_arg != NULL)
320 	search_ctx = search_ctx_arg;
321     else
322     {
323 	search_ctx = ALLOC_CLEAR_ONE(ff_search_ctx_T);
324 	if (search_ctx == NULL)
325 	    goto error_return;
326     }
327     search_ctx->ffsc_find_what = find_what;
328     search_ctx->ffsc_tagfile = tagfile;
329 
330     // clear the search context, but NOT the visited lists
331     ff_clear(search_ctx);
332 
333     // clear visited list if wanted
334     if (free_visited == TRUE)
335 	vim_findfile_free_visited(search_ctx);
336     else
337     {
338 	// Reuse old visited lists. Get the visited list for the given
339 	// filename. If no list for the current filename exists, creates a new
340 	// one.
341 	search_ctx->ffsc_visited_list = ff_get_visited_list(filename,
342 					&search_ctx->ffsc_visited_lists_list);
343 	if (search_ctx->ffsc_visited_list == NULL)
344 	    goto error_return;
345 	search_ctx->ffsc_dir_visited_list = ff_get_visited_list(filename,
346 				    &search_ctx->ffsc_dir_visited_lists_list);
347 	if (search_ctx->ffsc_dir_visited_list == NULL)
348 	    goto error_return;
349     }
350 
351     if (ff_expand_buffer == NULL)
352     {
353 	ff_expand_buffer = alloc(MAXPATHL);
354 	if (ff_expand_buffer == NULL)
355 	    goto error_return;
356     }
357 
358     // Store information on starting dir now if path is relative.
359     // If path is absolute, we do that later.
360     if (path[0] == '.'
361 	    && (vim_ispathsep(path[1]) || path[1] == NUL)
362 	    && (!tagfile || vim_strchr(p_cpo, CPO_DOTTAG) == NULL)
363 	    && rel_fname != NULL)
364     {
365 	int	len = (int)(gettail(rel_fname) - rel_fname);
366 
367 	if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL)
368 	{
369 	    // Make the start dir an absolute path name.
370 	    vim_strncpy(ff_expand_buffer, rel_fname, len);
371 	    search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, FALSE);
372 	}
373 	else
374 	    search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len);
375 	if (search_ctx->ffsc_start_dir == NULL)
376 	    goto error_return;
377 	if (*++path != NUL)
378 	    ++path;
379     }
380     else if (*path == NUL || !vim_isAbsName(path))
381     {
382 #ifdef BACKSLASH_IN_FILENAME
383 	// "c:dir" needs "c:" to be expanded, otherwise use current dir
384 	if (*path != NUL && path[1] == ':')
385 	{
386 	    char_u  drive[3];
387 
388 	    drive[0] = path[0];
389 	    drive[1] = ':';
390 	    drive[2] = NUL;
391 	    if (vim_FullName(drive, ff_expand_buffer, MAXPATHL, TRUE) == FAIL)
392 		goto error_return;
393 	    path += 2;
394 	}
395 	else
396 #endif
397 	if (mch_dirname(ff_expand_buffer, MAXPATHL) == FAIL)
398 	    goto error_return;
399 
400 	search_ctx->ffsc_start_dir = vim_strsave(ff_expand_buffer);
401 	if (search_ctx->ffsc_start_dir == NULL)
402 	    goto error_return;
403 
404 #ifdef BACKSLASH_IN_FILENAME
405 	// A path that starts with "/dir" is relative to the drive, not to the
406 	// directory (but not for "//machine/dir").  Only use the drive name.
407 	if ((*path == '/' || *path == '\\')
408 		&& path[1] != path[0]
409 		&& search_ctx->ffsc_start_dir[1] == ':')
410 	    search_ctx->ffsc_start_dir[2] = NUL;
411 #endif
412     }
413 
414 #ifdef FEAT_PATH_EXTRA
415     /*
416      * If stopdirs are given, split them into an array of pointers.
417      * If this fails (mem allocation), there is no upward search at all or a
418      * stop directory is not recognized -> continue silently.
419      * If stopdirs just contains a ";" or is empty,
420      * search_ctx->ffsc_stopdirs_v will only contain a  NULL pointer. This
421      * is handled as unlimited upward search.  See function
422      * ff_path_in_stoplist() for details.
423      */
424     if (stopdirs != NULL)
425     {
426 	char_u	*walker = stopdirs;
427 	int	dircount;
428 
429 	while (*walker == ';')
430 	    walker++;
431 
432 	dircount = 1;
433 	search_ctx->ffsc_stopdirs_v = ALLOC_ONE(char_u *);
434 
435 	if (search_ctx->ffsc_stopdirs_v != NULL)
436 	{
437 	    do
438 	    {
439 		char_u	*helper;
440 		void	*ptr;
441 
442 		helper = walker;
443 		ptr = vim_realloc(search_ctx->ffsc_stopdirs_v,
444 					   (dircount + 1) * sizeof(char_u *));
445 		if (ptr)
446 		    search_ctx->ffsc_stopdirs_v = ptr;
447 		else
448 		    // ignore, keep what we have and continue
449 		    break;
450 		walker = vim_strchr(walker, ';');
451 		if (walker)
452 		{
453 		    search_ctx->ffsc_stopdirs_v[dircount-1] =
454 					 vim_strnsave(helper, walker - helper);
455 		    walker++;
456 		}
457 		else
458 		    // this might be "", which means ascent till top
459 		    // of directory tree.
460 		    search_ctx->ffsc_stopdirs_v[dircount-1] =
461 							  vim_strsave(helper);
462 
463 		dircount++;
464 
465 	    } while (walker != NULL);
466 	    search_ctx->ffsc_stopdirs_v[dircount-1] = NULL;
467 	}
468     }
469 #endif
470 
471 #ifdef FEAT_PATH_EXTRA
472     search_ctx->ffsc_level = level;
473 
474     /*
475      * split into:
476      *  -fix path
477      *  -wildcard_stuff (might be NULL)
478      */
479     wc_part = vim_strchr(path, '*');
480     if (wc_part != NULL)
481     {
482 	int	llevel;
483 	int	len;
484 	char	*errpt;
485 
486 	// save the fix part of the path
487 	search_ctx->ffsc_fix_path = vim_strnsave(path, wc_part - path);
488 
489 	/*
490 	 * copy wc_path and add restricts to the '**' wildcard.
491 	 * The octet after a '**' is used as a (binary) counter.
492 	 * So '**3' is transposed to '**^C' ('^C' is ASCII value 3)
493 	 * or '**76' is transposed to '**N'( 'N' is ASCII value 76).
494 	 * For EBCDIC you get different character values.
495 	 * If no restrict is given after '**' the default is used.
496 	 * Due to this technique the path looks awful if you print it as a
497 	 * string.
498 	 */
499 	len = 0;
500 	while (*wc_part != NUL)
501 	{
502 	    if (len + 5 >= MAXPATHL)
503 	    {
504 		emsg(_(e_pathtoolong));
505 		break;
506 	    }
507 	    if (STRNCMP(wc_part, "**", 2) == 0)
508 	    {
509 		ff_expand_buffer[len++] = *wc_part++;
510 		ff_expand_buffer[len++] = *wc_part++;
511 
512 		llevel = strtol((char *)wc_part, &errpt, 10);
513 		if ((char_u *)errpt != wc_part && llevel > 0 && llevel < 255)
514 		    ff_expand_buffer[len++] = llevel;
515 		else if ((char_u *)errpt != wc_part && llevel == 0)
516 		    // restrict is 0 -> remove already added '**'
517 		    len -= 2;
518 		else
519 		    ff_expand_buffer[len++] = FF_MAX_STAR_STAR_EXPAND;
520 		wc_part = (char_u *)errpt;
521 		if (*wc_part != NUL && !vim_ispathsep(*wc_part))
522 		{
523 		    semsg(_("E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'."), PATHSEPSTR);
524 		    goto error_return;
525 		}
526 	    }
527 	    else
528 		ff_expand_buffer[len++] = *wc_part++;
529 	}
530 	ff_expand_buffer[len] = NUL;
531 	search_ctx->ffsc_wc_path = vim_strsave(ff_expand_buffer);
532 
533 	if (search_ctx->ffsc_wc_path == NULL)
534 	    goto error_return;
535     }
536     else
537 #endif
538 	search_ctx->ffsc_fix_path = vim_strsave(path);
539 
540     if (search_ctx->ffsc_start_dir == NULL)
541     {
542 	// store the fix part as startdir.
543 	// This is needed if the parameter path is fully qualified.
544 	search_ctx->ffsc_start_dir = vim_strsave(search_ctx->ffsc_fix_path);
545 	if (search_ctx->ffsc_start_dir == NULL)
546 	    goto error_return;
547 	search_ctx->ffsc_fix_path[0] = NUL;
548     }
549 
550     // create an absolute path
551     if (STRLEN(search_ctx->ffsc_start_dir)
552 			  + STRLEN(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL)
553     {
554 	emsg(_(e_pathtoolong));
555 	goto error_return;
556     }
557     STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir);
558     add_pathsep(ff_expand_buffer);
559     {
560 	int    eb_len = (int)STRLEN(ff_expand_buffer);
561 	char_u *buf = alloc(eb_len
562 				+ (int)STRLEN(search_ctx->ffsc_fix_path) + 1);
563 
564 	STRCPY(buf, ff_expand_buffer);
565 	STRCPY(buf + eb_len, search_ctx->ffsc_fix_path);
566 	if (mch_isdir(buf))
567 	{
568 	    STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path);
569 	    add_pathsep(ff_expand_buffer);
570 	}
571 #ifdef FEAT_PATH_EXTRA
572 	else
573 	{
574 	    char_u *p =  gettail(search_ctx->ffsc_fix_path);
575 	    char_u *wc_path = NULL;
576 	    char_u *temp = NULL;
577 	    int    len = 0;
578 
579 	    if (p > search_ctx->ffsc_fix_path)
580 	    {
581 		// do not add '..' to the path and start upwards searching
582 		len = (int)(p - search_ctx->ffsc_fix_path) - 1;
583 		if ((len >= 2
584 			&& STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0)
585 			&& (len == 2
586 				   || search_ctx->ffsc_fix_path[2] == PATHSEP))
587 		{
588 		    vim_free(buf);
589 		    goto error_return;
590 		}
591 		STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len);
592 		add_pathsep(ff_expand_buffer);
593 	    }
594 	    else
595 		len = (int)STRLEN(search_ctx->ffsc_fix_path);
596 
597 	    if (search_ctx->ffsc_wc_path != NULL)
598 	    {
599 		wc_path = vim_strsave(search_ctx->ffsc_wc_path);
600 		temp = alloc(STRLEN(search_ctx->ffsc_wc_path)
601 				 + STRLEN(search_ctx->ffsc_fix_path + len)
602 				 + 1);
603 		if (temp == NULL || wc_path == NULL)
604 		{
605 		    vim_free(buf);
606 		    vim_free(temp);
607 		    vim_free(wc_path);
608 		    goto error_return;
609 		}
610 
611 		STRCPY(temp, search_ctx->ffsc_fix_path + len);
612 		STRCAT(temp, search_ctx->ffsc_wc_path);
613 		vim_free(search_ctx->ffsc_wc_path);
614 		vim_free(wc_path);
615 		search_ctx->ffsc_wc_path = temp;
616 	    }
617 	}
618 #endif
619 	vim_free(buf);
620     }
621 
622     sptr = ff_create_stack_element(ff_expand_buffer,
623 #ifdef FEAT_PATH_EXTRA
624 	    search_ctx->ffsc_wc_path,
625 #endif
626 	    level, 0);
627 
628     if (sptr == NULL)
629 	goto error_return;
630 
631     ff_push(search_ctx, sptr);
632 
633     search_ctx->ffsc_file_to_search = vim_strsave(filename);
634     if (search_ctx->ffsc_file_to_search == NULL)
635 	goto error_return;
636 
637     return search_ctx;
638 
639 error_return:
640     /*
641      * We clear the search context now!
642      * Even when the caller gave us a (perhaps valid) context we free it here,
643      * as we might have already destroyed it.
644      */
645     vim_findfile_cleanup(search_ctx);
646     return NULL;
647 }
648 
649 #if defined(FEAT_PATH_EXTRA) || defined(PROTO)
650 /*
651  * Get the stopdir string.  Check that ';' is not escaped.
652  */
653     char_u *
vim_findfile_stopdir(char_u * buf)654 vim_findfile_stopdir(char_u *buf)
655 {
656     char_u	*r_ptr = buf;
657 
658     while (*r_ptr != NUL && *r_ptr != ';')
659     {
660 	if (r_ptr[0] == '\\' && r_ptr[1] == ';')
661 	{
662 	    // Overwrite the escape char,
663 	    // use STRLEN(r_ptr) to move the trailing '\0'.
664 	    STRMOVE(r_ptr, r_ptr + 1);
665 	    r_ptr++;
666 	}
667 	r_ptr++;
668     }
669     if (*r_ptr == ';')
670     {
671 	*r_ptr = 0;
672 	r_ptr++;
673     }
674     else if (*r_ptr == NUL)
675 	r_ptr = NULL;
676     return r_ptr;
677 }
678 #endif
679 
680 /*
681  * Clean up the given search context. Can handle a NULL pointer.
682  */
683     void
vim_findfile_cleanup(void * ctx)684 vim_findfile_cleanup(void *ctx)
685 {
686     if (ctx == NULL)
687 	return;
688 
689     vim_findfile_free_visited(ctx);
690     ff_clear(ctx);
691     vim_free(ctx);
692 }
693 
694 /*
695  * Find a file in a search context.
696  * The search context was created with vim_findfile_init() above.
697  * Return a pointer to an allocated file name or NULL if nothing found.
698  * To get all matching files call this function until you get NULL.
699  *
700  * If the passed search_context is NULL, NULL is returned.
701  *
702  * The search algorithm is depth first. To change this replace the
703  * stack with a list (don't forget to leave partly searched directories on the
704  * top of the list).
705  */
706     char_u *
vim_findfile(void * search_ctx_arg)707 vim_findfile(void *search_ctx_arg)
708 {
709     char_u	*file_path;
710 #ifdef FEAT_PATH_EXTRA
711     char_u	*rest_of_wildcards;
712     char_u	*path_end = NULL;
713 #endif
714     ff_stack_T	*stackp;
715 #if defined(FEAT_SEARCHPATH) || defined(FEAT_PATH_EXTRA)
716     int		len;
717 #endif
718     int		i;
719     char_u	*p;
720 #ifdef FEAT_SEARCHPATH
721     char_u	*suf;
722 #endif
723     ff_search_ctx_T *search_ctx;
724 
725     if (search_ctx_arg == NULL)
726 	return NULL;
727 
728     search_ctx = (ff_search_ctx_T *)search_ctx_arg;
729 
730     /*
731      * filepath is used as buffer for various actions and as the storage to
732      * return a found filename.
733      */
734     if ((file_path = alloc(MAXPATHL)) == NULL)
735 	return NULL;
736 
737 #ifdef FEAT_PATH_EXTRA
738     // store the end of the start dir -- needed for upward search
739     if (search_ctx->ffsc_start_dir != NULL)
740 	path_end = &search_ctx->ffsc_start_dir[
741 					  STRLEN(search_ctx->ffsc_start_dir)];
742 #endif
743 
744 #ifdef FEAT_PATH_EXTRA
745     // upward search loop
746     for (;;)
747     {
748 #endif
749 	// downward search loop
750 	for (;;)
751 	{
752 	    // check if user user wants to stop the search
753 	    ui_breakcheck();
754 	    if (got_int)
755 		break;
756 
757 	    // get directory to work on from stack
758 	    stackp = ff_pop(search_ctx);
759 	    if (stackp == NULL)
760 		break;
761 
762 	    /*
763 	     * TODO: decide if we leave this test in
764 	     *
765 	     * GOOD: don't search a directory(-tree) twice.
766 	     * BAD:  - check linked list for every new directory entered.
767 	     *       - check for double files also done below
768 	     *
769 	     * Here we check if we already searched this directory.
770 	     * We already searched a directory if:
771 	     * 1) The directory is the same.
772 	     * 2) We would use the same wildcard string.
773 	     *
774 	     * Good if you have links on same directory via several ways
775 	     *  or you have selfreferences in directories (e.g. SuSE Linux 6.3:
776 	     *  /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop)
777 	     *
778 	     * This check is only needed for directories we work on for the
779 	     * first time (hence stackp->ff_filearray == NULL)
780 	     */
781 	    if (stackp->ffs_filearray == NULL
782 		    && ff_check_visited(&search_ctx->ffsc_dir_visited_list
783 							  ->ffvl_visited_list,
784 			stackp->ffs_fix_path
785 #ifdef FEAT_PATH_EXTRA
786 			, stackp->ffs_wc_path
787 #endif
788 			) == FAIL)
789 	    {
790 #ifdef FF_VERBOSE
791 		if (p_verbose >= 5)
792 		{
793 		    verbose_enter_scroll();
794 		    smsg("Already Searched: %s (%s)",
795 				   stackp->ffs_fix_path, stackp->ffs_wc_path);
796 		    // don't overwrite this either
797 		    msg_puts("\n");
798 		    verbose_leave_scroll();
799 		}
800 #endif
801 		ff_free_stack_element(stackp);
802 		continue;
803 	    }
804 #ifdef FF_VERBOSE
805 	    else if (p_verbose >= 5)
806 	    {
807 		verbose_enter_scroll();
808 		smsg("Searching: %s (%s)",
809 				   stackp->ffs_fix_path, stackp->ffs_wc_path);
810 		// don't overwrite this either
811 		msg_puts("\n");
812 		verbose_leave_scroll();
813 	    }
814 #endif
815 
816 	    // check depth
817 	    if (stackp->ffs_level <= 0)
818 	    {
819 		ff_free_stack_element(stackp);
820 		continue;
821 	    }
822 
823 	    file_path[0] = NUL;
824 
825 	    /*
826 	     * If no filearray till now expand wildcards
827 	     * The function expand_wildcards() can handle an array of paths
828 	     * and all possible expands are returned in one array. We use this
829 	     * to handle the expansion of '**' into an empty string.
830 	     */
831 	    if (stackp->ffs_filearray == NULL)
832 	    {
833 		char_u *dirptrs[2];
834 
835 		// we use filepath to build the path expand_wildcards() should
836 		// expand.
837 		dirptrs[0] = file_path;
838 		dirptrs[1] = NULL;
839 
840 		// if we have a start dir copy it in
841 		if (!vim_isAbsName(stackp->ffs_fix_path)
842 						&& search_ctx->ffsc_start_dir)
843 		{
844 		    if (STRLEN(search_ctx->ffsc_start_dir) + 1 < MAXPATHL)
845 		    {
846 			STRCPY(file_path, search_ctx->ffsc_start_dir);
847 			add_pathsep(file_path);
848 		    }
849 		    else
850 		    {
851 			ff_free_stack_element(stackp);
852 			goto fail;
853 		    }
854 		}
855 
856 		// append the fix part of the search path
857 		if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1
858 								    < MAXPATHL)
859 		{
860 		    STRCAT(file_path, stackp->ffs_fix_path);
861 		    add_pathsep(file_path);
862 		}
863 		else
864 		{
865 		    ff_free_stack_element(stackp);
866 		    goto fail;
867 		}
868 
869 #ifdef FEAT_PATH_EXTRA
870 		rest_of_wildcards = stackp->ffs_wc_path;
871 		if (*rest_of_wildcards != NUL)
872 		{
873 		    len = (int)STRLEN(file_path);
874 		    if (STRNCMP(rest_of_wildcards, "**", 2) == 0)
875 		    {
876 			// pointer to the restrict byte
877 			// The restrict byte is not a character!
878 			p = rest_of_wildcards + 2;
879 
880 			if (*p > 0)
881 			{
882 			    (*p)--;
883 			    if (len + 1 < MAXPATHL)
884 				file_path[len++] = '*';
885 			    else
886 			    {
887 				ff_free_stack_element(stackp);
888 				goto fail;
889 			    }
890 			}
891 
892 			if (*p == 0)
893 			{
894 			    // remove '**<numb> from wildcards
895 			    STRMOVE(rest_of_wildcards, rest_of_wildcards + 3);
896 			}
897 			else
898 			    rest_of_wildcards += 3;
899 
900 			if (stackp->ffs_star_star_empty == 0)
901 			{
902 			    // if not done before, expand '**' to empty
903 			    stackp->ffs_star_star_empty = 1;
904 			    dirptrs[1] = stackp->ffs_fix_path;
905 			}
906 		    }
907 
908 		    /*
909 		     * Here we copy until the next path separator or the end of
910 		     * the path. If we stop at a path separator, there is
911 		     * still something else left. This is handled below by
912 		     * pushing every directory returned from expand_wildcards()
913 		     * on the stack again for further search.
914 		     */
915 		    while (*rest_of_wildcards
916 			    && !vim_ispathsep(*rest_of_wildcards))
917 			if (len + 1 < MAXPATHL)
918 			    file_path[len++] = *rest_of_wildcards++;
919 			else
920 			{
921 			    ff_free_stack_element(stackp);
922 			    goto fail;
923 			}
924 
925 		    file_path[len] = NUL;
926 		    if (vim_ispathsep(*rest_of_wildcards))
927 			rest_of_wildcards++;
928 		}
929 #endif
930 
931 		/*
932 		 * Expand wildcards like "*" and "$VAR".
933 		 * If the path is a URL don't try this.
934 		 */
935 		if (path_with_url(dirptrs[0]))
936 		{
937 		    stackp->ffs_filearray = ALLOC_ONE(char_u *);
938 		    if (stackp->ffs_filearray != NULL
939 			    && (stackp->ffs_filearray[0]
940 				= vim_strsave(dirptrs[0])) != NULL)
941 			stackp->ffs_filearray_size = 1;
942 		    else
943 			stackp->ffs_filearray_size = 0;
944 		}
945 		else
946 		    // Add EW_NOTWILD because the expanded path may contain
947 		    // wildcard characters that are to be taken literally.
948 		    // This is a bit of a hack.
949 		    expand_wildcards((dirptrs[1] == NULL) ? 1 : 2, dirptrs,
950 			    &stackp->ffs_filearray_size,
951 			    &stackp->ffs_filearray,
952 			    EW_DIR|EW_ADDSLASH|EW_SILENT|EW_NOTWILD);
953 
954 		stackp->ffs_filearray_cur = 0;
955 		stackp->ffs_stage = 0;
956 	    }
957 #ifdef FEAT_PATH_EXTRA
958 	    else
959 		rest_of_wildcards = &stackp->ffs_wc_path[
960 						 STRLEN(stackp->ffs_wc_path)];
961 #endif
962 
963 	    if (stackp->ffs_stage == 0)
964 	    {
965 		// this is the first time we work on this directory
966 #ifdef FEAT_PATH_EXTRA
967 		if (*rest_of_wildcards == NUL)
968 #endif
969 		{
970 		    /*
971 		     * We don't have further wildcards to expand, so we have to
972 		     * check for the final file now.
973 		     */
974 		    for (i = stackp->ffs_filearray_cur;
975 					  i < stackp->ffs_filearray_size; ++i)
976 		    {
977 			if (!path_with_url(stackp->ffs_filearray[i])
978 				      && !mch_isdir(stackp->ffs_filearray[i]))
979 			    continue;   // not a directory
980 
981 			// prepare the filename to be checked for existence
982 			// below
983 			if (STRLEN(stackp->ffs_filearray[i]) + 1
984 				+ STRLEN(search_ctx->ffsc_file_to_search)
985 								    < MAXPATHL)
986 			{
987 			    STRCPY(file_path, stackp->ffs_filearray[i]);
988 			    add_pathsep(file_path);
989 			    STRCAT(file_path, search_ctx->ffsc_file_to_search);
990 			}
991 			else
992 			{
993 			    ff_free_stack_element(stackp);
994 			    goto fail;
995 			}
996 
997 			/*
998 			 * Try without extra suffix and then with suffixes
999 			 * from 'suffixesadd'.
1000 			 */
1001 #ifdef FEAT_SEARCHPATH
1002 			len = (int)STRLEN(file_path);
1003 			if (search_ctx->ffsc_tagfile)
1004 			    suf = (char_u *)"";
1005 			else
1006 			    suf = curbuf->b_p_sua;
1007 			for (;;)
1008 #endif
1009 			{
1010 			    // if file exists and we didn't already find it
1011 			    if ((path_with_url(file_path)
1012 				  || (mch_getperm(file_path) >= 0
1013 				      && (search_ctx->ffsc_find_what
1014 							      == FINDFILE_BOTH
1015 					  || ((search_ctx->ffsc_find_what
1016 							      == FINDFILE_DIR)
1017 						   == mch_isdir(file_path)))))
1018 #ifndef FF_VERBOSE
1019 				    && (ff_check_visited(
1020 					    &search_ctx->ffsc_visited_list->ffvl_visited_list,
1021 					    file_path
1022 #ifdef FEAT_PATH_EXTRA
1023 					    , (char_u *)""
1024 #endif
1025 					    ) == OK)
1026 #endif
1027 			       )
1028 			    {
1029 #ifdef FF_VERBOSE
1030 				if (ff_check_visited(
1031 					    &search_ctx->ffsc_visited_list->ffvl_visited_list,
1032 					    file_path
1033 #ifdef FEAT_PATH_EXTRA
1034 					    , (char_u *)""
1035 #endif
1036 						    ) == FAIL)
1037 				{
1038 				    if (p_verbose >= 5)
1039 				    {
1040 					verbose_enter_scroll();
1041 					smsg("Already: %s",
1042 								   file_path);
1043 					// don't overwrite this either
1044 					msg_puts("\n");
1045 					verbose_leave_scroll();
1046 				    }
1047 				    continue;
1048 				}
1049 #endif
1050 
1051 				// push dir to examine rest of subdirs later
1052 				stackp->ffs_filearray_cur = i + 1;
1053 				ff_push(search_ctx, stackp);
1054 
1055 				if (!path_with_url(file_path))
1056 				    simplify_filename(file_path);
1057 				if (mch_dirname(ff_expand_buffer, MAXPATHL)
1058 									== OK)
1059 				{
1060 				    p = shorten_fname(file_path,
1061 							    ff_expand_buffer);
1062 				    if (p != NULL)
1063 					STRMOVE(file_path, p);
1064 				}
1065 #ifdef FF_VERBOSE
1066 				if (p_verbose >= 5)
1067 				{
1068 				    verbose_enter_scroll();
1069 				    smsg("HIT: %s", file_path);
1070 				    // don't overwrite this either
1071 				    msg_puts("\n");
1072 				    verbose_leave_scroll();
1073 				}
1074 #endif
1075 				return file_path;
1076 			    }
1077 
1078 #ifdef FEAT_SEARCHPATH
1079 			    // Not found or found already, try next suffix.
1080 			    if (*suf == NUL)
1081 				break;
1082 			    copy_option_part(&suf, file_path + len,
1083 							 MAXPATHL - len, ",");
1084 #endif
1085 			}
1086 		    }
1087 		}
1088 #ifdef FEAT_PATH_EXTRA
1089 		else
1090 		{
1091 		    /*
1092 		     * still wildcards left, push the directories for further
1093 		     * search
1094 		     */
1095 		    for (i = stackp->ffs_filearray_cur;
1096 					  i < stackp->ffs_filearray_size; ++i)
1097 		    {
1098 			if (!mch_isdir(stackp->ffs_filearray[i]))
1099 			    continue;	// not a directory
1100 
1101 			ff_push(search_ctx,
1102 				ff_create_stack_element(
1103 						     stackp->ffs_filearray[i],
1104 						     rest_of_wildcards,
1105 						     stackp->ffs_level - 1, 0));
1106 		    }
1107 		}
1108 #endif
1109 		stackp->ffs_filearray_cur = 0;
1110 		stackp->ffs_stage = 1;
1111 	    }
1112 
1113 #ifdef FEAT_PATH_EXTRA
1114 	    /*
1115 	     * if wildcards contains '**' we have to descent till we reach the
1116 	     * leaves of the directory tree.
1117 	     */
1118 	    if (STRNCMP(stackp->ffs_wc_path, "**", 2) == 0)
1119 	    {
1120 		for (i = stackp->ffs_filearray_cur;
1121 					  i < stackp->ffs_filearray_size; ++i)
1122 		{
1123 		    if (fnamecmp(stackp->ffs_filearray[i],
1124 						   stackp->ffs_fix_path) == 0)
1125 			continue; // don't repush same directory
1126 		    if (!mch_isdir(stackp->ffs_filearray[i]))
1127 			continue;   // not a directory
1128 		    ff_push(search_ctx,
1129 			    ff_create_stack_element(stackp->ffs_filearray[i],
1130 				stackp->ffs_wc_path, stackp->ffs_level - 1, 1));
1131 		}
1132 	    }
1133 #endif
1134 
1135 	    // we are done with the current directory
1136 	    ff_free_stack_element(stackp);
1137 
1138 	}
1139 
1140 #ifdef FEAT_PATH_EXTRA
1141 	// If we reached this, we didn't find anything downwards.
1142 	// Let's check if we should do an upward search.
1143 	if (search_ctx->ffsc_start_dir
1144 		&& search_ctx->ffsc_stopdirs_v != NULL && !got_int)
1145 	{
1146 	    ff_stack_T  *sptr;
1147 
1148 	    // is the last starting directory in the stop list?
1149 	    if (ff_path_in_stoplist(search_ctx->ffsc_start_dir,
1150 		       (int)(path_end - search_ctx->ffsc_start_dir),
1151 		       search_ctx->ffsc_stopdirs_v) == TRUE)
1152 		break;
1153 
1154 	    // cut of last dir
1155 	    while (path_end > search_ctx->ffsc_start_dir
1156 						  && vim_ispathsep(*path_end))
1157 		path_end--;
1158 	    while (path_end > search_ctx->ffsc_start_dir
1159 					      && !vim_ispathsep(path_end[-1]))
1160 		path_end--;
1161 	    *path_end = 0;
1162 	    path_end--;
1163 
1164 	    if (*search_ctx->ffsc_start_dir == 0)
1165 		break;
1166 
1167 	    if (STRLEN(search_ctx->ffsc_start_dir) + 1
1168 		    + STRLEN(search_ctx->ffsc_fix_path) < MAXPATHL)
1169 	    {
1170 		STRCPY(file_path, search_ctx->ffsc_start_dir);
1171 		add_pathsep(file_path);
1172 		STRCAT(file_path, search_ctx->ffsc_fix_path);
1173 	    }
1174 	    else
1175 		goto fail;
1176 
1177 	    // create a new stack entry
1178 	    sptr = ff_create_stack_element(file_path,
1179 		    search_ctx->ffsc_wc_path, search_ctx->ffsc_level, 0);
1180 	    if (sptr == NULL)
1181 		break;
1182 	    ff_push(search_ctx, sptr);
1183 	}
1184 	else
1185 	    break;
1186     }
1187 #endif
1188 
1189 fail:
1190     vim_free(file_path);
1191     return NULL;
1192 }
1193 
1194 /*
1195  * Free the list of lists of visited files and directories
1196  * Can handle it if the passed search_context is NULL;
1197  */
1198     static void
vim_findfile_free_visited(void * search_ctx_arg)1199 vim_findfile_free_visited(void *search_ctx_arg)
1200 {
1201     ff_search_ctx_T *search_ctx;
1202 
1203     if (search_ctx_arg == NULL)
1204 	return;
1205 
1206     search_ctx = (ff_search_ctx_T *)search_ctx_arg;
1207     vim_findfile_free_visited_list(&search_ctx->ffsc_visited_lists_list);
1208     vim_findfile_free_visited_list(&search_ctx->ffsc_dir_visited_lists_list);
1209 }
1210 
1211     static void
vim_findfile_free_visited_list(ff_visited_list_hdr_T ** list_headp)1212 vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp)
1213 {
1214     ff_visited_list_hdr_T *vp;
1215 
1216     while (*list_headp != NULL)
1217     {
1218 	vp = (*list_headp)->ffvl_next;
1219 	ff_free_visited_list((*list_headp)->ffvl_visited_list);
1220 
1221 	vim_free((*list_headp)->ffvl_filename);
1222 	vim_free(*list_headp);
1223 	*list_headp = vp;
1224     }
1225     *list_headp = NULL;
1226 }
1227 
1228     static void
ff_free_visited_list(ff_visited_T * vl)1229 ff_free_visited_list(ff_visited_T *vl)
1230 {
1231     ff_visited_T *vp;
1232 
1233     while (vl != NULL)
1234     {
1235 	vp = vl->ffv_next;
1236 #ifdef FEAT_PATH_EXTRA
1237 	vim_free(vl->ffv_wc_path);
1238 #endif
1239 	vim_free(vl);
1240 	vl = vp;
1241     }
1242     vl = NULL;
1243 }
1244 
1245 /*
1246  * Returns the already visited list for the given filename. If none is found it
1247  * allocates a new one.
1248  */
1249     static ff_visited_list_hdr_T*
ff_get_visited_list(char_u * filename,ff_visited_list_hdr_T ** list_headp)1250 ff_get_visited_list(
1251     char_u			*filename,
1252     ff_visited_list_hdr_T	**list_headp)
1253 {
1254     ff_visited_list_hdr_T  *retptr = NULL;
1255 
1256     // check if a visited list for the given filename exists
1257     if (*list_headp != NULL)
1258     {
1259 	retptr = *list_headp;
1260 	while (retptr != NULL)
1261 	{
1262 	    if (fnamecmp(filename, retptr->ffvl_filename) == 0)
1263 	    {
1264 #ifdef FF_VERBOSE
1265 		if (p_verbose >= 5)
1266 		{
1267 		    verbose_enter_scroll();
1268 		    smsg("ff_get_visited_list: FOUND list for %s",
1269 								    filename);
1270 		    // don't overwrite this either
1271 		    msg_puts("\n");
1272 		    verbose_leave_scroll();
1273 		}
1274 #endif
1275 		return retptr;
1276 	    }
1277 	    retptr = retptr->ffvl_next;
1278 	}
1279     }
1280 
1281 #ifdef FF_VERBOSE
1282     if (p_verbose >= 5)
1283     {
1284 	verbose_enter_scroll();
1285 	smsg("ff_get_visited_list: new list for %s", filename);
1286 	// don't overwrite this either
1287 	msg_puts("\n");
1288 	verbose_leave_scroll();
1289     }
1290 #endif
1291 
1292     /*
1293      * if we reach this we didn't find a list and we have to allocate new list
1294      */
1295     retptr = ALLOC_ONE(ff_visited_list_hdr_T);
1296     if (retptr == NULL)
1297 	return NULL;
1298 
1299     retptr->ffvl_visited_list = NULL;
1300     retptr->ffvl_filename = vim_strsave(filename);
1301     if (retptr->ffvl_filename == NULL)
1302     {
1303 	vim_free(retptr);
1304 	return NULL;
1305     }
1306     retptr->ffvl_next = *list_headp;
1307     *list_headp = retptr;
1308 
1309     return retptr;
1310 }
1311 
1312 #ifdef FEAT_PATH_EXTRA
1313 /*
1314  * check if two wildcard paths are equal. Returns TRUE or FALSE.
1315  * They are equal if:
1316  *  - both paths are NULL
1317  *  - they have the same length
1318  *  - char by char comparison is OK
1319  *  - the only differences are in the counters behind a '**', so
1320  *    '**\20' is equal to '**\24'
1321  */
1322     static int
ff_wc_equal(char_u * s1,char_u * s2)1323 ff_wc_equal(char_u *s1, char_u *s2)
1324 {
1325     int		i, j;
1326     int		c1 = NUL;
1327     int		c2 = NUL;
1328     int		prev1 = NUL;
1329     int		prev2 = NUL;
1330 
1331     if (s1 == s2)
1332 	return TRUE;
1333 
1334     if (s1 == NULL || s2 == NULL)
1335 	return FALSE;
1336 
1337     for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;)
1338     {
1339 	c1 = PTR2CHAR(s1 + i);
1340 	c2 = PTR2CHAR(s2 + j);
1341 
1342 	if ((p_fic ? MB_TOLOWER(c1) != MB_TOLOWER(c2) : c1 != c2)
1343 		&& (prev1 != '*' || prev2 != '*'))
1344 	    return FALSE;
1345 	prev2 = prev1;
1346 	prev1 = c1;
1347 
1348 	i += mb_ptr2len(s1 + i);
1349 	j += mb_ptr2len(s2 + j);
1350     }
1351     return s1[i] == s2[j];
1352 }
1353 #endif
1354 
1355 /*
1356  * maintains the list of already visited files and dirs
1357  * returns FAIL if the given file/dir is already in the list
1358  * returns OK if it is newly added
1359  *
1360  * TODO: What to do on memory allocation problems?
1361  *	 -> return TRUE - Better the file is found several times instead of
1362  *	    never.
1363  */
1364     static int
ff_check_visited(ff_visited_T ** visited_list,char_u * fname,char_u * wc_path)1365 ff_check_visited(
1366     ff_visited_T	**visited_list,
1367     char_u		*fname
1368 #ifdef FEAT_PATH_EXTRA
1369     , char_u		*wc_path
1370 #endif
1371     )
1372 {
1373     ff_visited_T	*vp;
1374 #ifdef UNIX
1375     stat_T		st;
1376     int			url = FALSE;
1377 #endif
1378 
1379     // For an URL we only compare the name, otherwise we compare the
1380     // device/inode (unix) or the full path name (not Unix).
1381     if (path_with_url(fname))
1382     {
1383 	vim_strncpy(ff_expand_buffer, fname, MAXPATHL - 1);
1384 #ifdef UNIX
1385 	url = TRUE;
1386 #endif
1387     }
1388     else
1389     {
1390 	ff_expand_buffer[0] = NUL;
1391 #ifdef UNIX
1392 	if (mch_stat((char *)fname, &st) < 0)
1393 #else
1394 	if (vim_FullName(fname, ff_expand_buffer, MAXPATHL, TRUE) == FAIL)
1395 #endif
1396 	    return FAIL;
1397     }
1398 
1399     // check against list of already visited files
1400     for (vp = *visited_list; vp != NULL; vp = vp->ffv_next)
1401     {
1402 	if (
1403 #ifdef UNIX
1404 		!url ? (vp->ffv_dev_valid && vp->ffv_dev == st.st_dev
1405 						  && vp->ffv_ino == st.st_ino)
1406 		     :
1407 #endif
1408 		fnamecmp(vp->ffv_fname, ff_expand_buffer) == 0
1409 	   )
1410 	{
1411 #ifdef FEAT_PATH_EXTRA
1412 	    // are the wildcard parts equal
1413 	    if (ff_wc_equal(vp->ffv_wc_path, wc_path) == TRUE)
1414 #endif
1415 		// already visited
1416 		return FAIL;
1417 	}
1418     }
1419 
1420     /*
1421      * New file/dir.  Add it to the list of visited files/dirs.
1422      */
1423     vp = alloc(sizeof(ff_visited_T) + STRLEN(ff_expand_buffer));
1424 
1425     if (vp != NULL)
1426     {
1427 #ifdef UNIX
1428 	if (!url)
1429 	{
1430 	    vp->ffv_dev_valid = TRUE;
1431 	    vp->ffv_ino = st.st_ino;
1432 	    vp->ffv_dev = st.st_dev;
1433 	    vp->ffv_fname[0] = NUL;
1434 	}
1435 	else
1436 	{
1437 	    vp->ffv_dev_valid = FALSE;
1438 #endif
1439 	    STRCPY(vp->ffv_fname, ff_expand_buffer);
1440 #ifdef UNIX
1441 	}
1442 #endif
1443 #ifdef FEAT_PATH_EXTRA
1444 	if (wc_path != NULL)
1445 	    vp->ffv_wc_path = vim_strsave(wc_path);
1446 	else
1447 	    vp->ffv_wc_path = NULL;
1448 #endif
1449 
1450 	vp->ffv_next = *visited_list;
1451 	*visited_list = vp;
1452     }
1453 
1454     return OK;
1455 }
1456 
1457 /*
1458  * create stack element from given path pieces
1459  */
1460     static ff_stack_T *
ff_create_stack_element(char_u * fix_part,char_u * wc_part,int level,int star_star_empty)1461 ff_create_stack_element(
1462     char_u	*fix_part,
1463 #ifdef FEAT_PATH_EXTRA
1464     char_u	*wc_part,
1465 #endif
1466     int		level,
1467     int		star_star_empty)
1468 {
1469     ff_stack_T	*new;
1470 
1471     new = ALLOC_ONE(ff_stack_T);
1472     if (new == NULL)
1473 	return NULL;
1474 
1475     new->ffs_prev	   = NULL;
1476     new->ffs_filearray	   = NULL;
1477     new->ffs_filearray_size = 0;
1478     new->ffs_filearray_cur  = 0;
1479     new->ffs_stage	   = 0;
1480     new->ffs_level	   = level;
1481     new->ffs_star_star_empty = star_star_empty;
1482 
1483     // the following saves NULL pointer checks in vim_findfile
1484     if (fix_part == NULL)
1485 	fix_part = (char_u *)"";
1486     new->ffs_fix_path = vim_strsave(fix_part);
1487 
1488 #ifdef FEAT_PATH_EXTRA
1489     if (wc_part == NULL)
1490 	wc_part  = (char_u *)"";
1491     new->ffs_wc_path = vim_strsave(wc_part);
1492 #endif
1493 
1494     if (new->ffs_fix_path == NULL
1495 #ifdef FEAT_PATH_EXTRA
1496 	    || new->ffs_wc_path == NULL
1497 #endif
1498 	    )
1499     {
1500 	ff_free_stack_element(new);
1501 	new = NULL;
1502     }
1503 
1504     return new;
1505 }
1506 
1507 /*
1508  * Push a dir on the directory stack.
1509  */
1510     static void
ff_push(ff_search_ctx_T * search_ctx,ff_stack_T * stack_ptr)1511 ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr)
1512 {
1513     // check for NULL pointer, not to return an error to the user, but
1514     // to prevent a crash
1515     if (stack_ptr != NULL)
1516     {
1517 	stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr;
1518 	search_ctx->ffsc_stack_ptr = stack_ptr;
1519     }
1520 }
1521 
1522 /*
1523  * Pop a dir from the directory stack.
1524  * Returns NULL if stack is empty.
1525  */
1526     static ff_stack_T *
ff_pop(ff_search_ctx_T * search_ctx)1527 ff_pop(ff_search_ctx_T *search_ctx)
1528 {
1529     ff_stack_T  *sptr;
1530 
1531     sptr = search_ctx->ffsc_stack_ptr;
1532     if (search_ctx->ffsc_stack_ptr != NULL)
1533 	search_ctx->ffsc_stack_ptr = search_ctx->ffsc_stack_ptr->ffs_prev;
1534 
1535     return sptr;
1536 }
1537 
1538 /*
1539  * free the given stack element
1540  */
1541     static void
ff_free_stack_element(ff_stack_T * stack_ptr)1542 ff_free_stack_element(ff_stack_T *stack_ptr)
1543 {
1544     // vim_free handles possible NULL pointers
1545     vim_free(stack_ptr->ffs_fix_path);
1546 #ifdef FEAT_PATH_EXTRA
1547     vim_free(stack_ptr->ffs_wc_path);
1548 #endif
1549 
1550     if (stack_ptr->ffs_filearray != NULL)
1551 	FreeWild(stack_ptr->ffs_filearray_size, stack_ptr->ffs_filearray);
1552 
1553     vim_free(stack_ptr);
1554 }
1555 
1556 /*
1557  * Clear the search context, but NOT the visited list.
1558  */
1559     static void
ff_clear(ff_search_ctx_T * search_ctx)1560 ff_clear(ff_search_ctx_T *search_ctx)
1561 {
1562     ff_stack_T   *sptr;
1563 
1564     // clear up stack
1565     while ((sptr = ff_pop(search_ctx)) != NULL)
1566 	ff_free_stack_element(sptr);
1567 
1568     vim_free(search_ctx->ffsc_file_to_search);
1569     vim_free(search_ctx->ffsc_start_dir);
1570     vim_free(search_ctx->ffsc_fix_path);
1571 #ifdef FEAT_PATH_EXTRA
1572     vim_free(search_ctx->ffsc_wc_path);
1573 #endif
1574 
1575 #ifdef FEAT_PATH_EXTRA
1576     if (search_ctx->ffsc_stopdirs_v != NULL)
1577     {
1578 	int  i = 0;
1579 
1580 	while (search_ctx->ffsc_stopdirs_v[i] != NULL)
1581 	{
1582 	    vim_free(search_ctx->ffsc_stopdirs_v[i]);
1583 	    i++;
1584 	}
1585 	vim_free(search_ctx->ffsc_stopdirs_v);
1586     }
1587     search_ctx->ffsc_stopdirs_v = NULL;
1588 #endif
1589 
1590     // reset everything
1591     search_ctx->ffsc_file_to_search = NULL;
1592     search_ctx->ffsc_start_dir = NULL;
1593     search_ctx->ffsc_fix_path = NULL;
1594 #ifdef FEAT_PATH_EXTRA
1595     search_ctx->ffsc_wc_path = NULL;
1596     search_ctx->ffsc_level = 0;
1597 #endif
1598 }
1599 
1600 #ifdef FEAT_PATH_EXTRA
1601 /*
1602  * check if the given path is in the stopdirs
1603  * returns TRUE if yes else FALSE
1604  */
1605     static int
ff_path_in_stoplist(char_u * path,int path_len,char_u ** stopdirs_v)1606 ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v)
1607 {
1608     int		i = 0;
1609 
1610     // eat up trailing path separators, except the first
1611     while (path_len > 1 && vim_ispathsep(path[path_len - 1]))
1612 	path_len--;
1613 
1614     // if no path consider it as match
1615     if (path_len == 0)
1616 	return TRUE;
1617 
1618     for (i = 0; stopdirs_v[i] != NULL; i++)
1619     {
1620 	if ((int)STRLEN(stopdirs_v[i]) > path_len)
1621 	{
1622 	    // match for parent directory. So '/home' also matches
1623 	    // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
1624 	    // '/home/r' would also match '/home/rks'
1625 	    if (fnamencmp(stopdirs_v[i], path, path_len) == 0
1626 		    && vim_ispathsep(stopdirs_v[i][path_len]))
1627 		return TRUE;
1628 	}
1629 	else
1630 	{
1631 	    if (fnamecmp(stopdirs_v[i], path) == 0)
1632 		return TRUE;
1633 	}
1634     }
1635     return FALSE;
1636 }
1637 #endif
1638 
1639 #if defined(FEAT_SEARCHPATH) || defined(PROTO)
1640 /*
1641  * Find the file name "ptr[len]" in the path.  Also finds directory names.
1642  *
1643  * On the first call set the parameter 'first' to TRUE to initialize
1644  * the search.  For repeating calls to FALSE.
1645  *
1646  * Repeating calls will return other files called 'ptr[len]' from the path.
1647  *
1648  * Only on the first call 'ptr' and 'len' are used.  For repeating calls they
1649  * don't need valid values.
1650  *
1651  * If nothing found on the first call the option FNAME_MESS will issue the
1652  * message:
1653  *	    'Can't find file "<file>" in path'
1654  * On repeating calls:
1655  *	    'No more file "<file>" found in path'
1656  *
1657  * options:
1658  * FNAME_MESS	    give error message when not found
1659  *
1660  * Uses NameBuff[]!
1661  *
1662  * Returns an allocated string for the file name.  NULL for error.
1663  *
1664  */
1665     char_u *
find_file_in_path(char_u * ptr,int len,int options,int first,char_u * rel_fname)1666 find_file_in_path(
1667     char_u	*ptr,		// file name
1668     int		len,		// length of file name
1669     int		options,
1670     int		first,		// use count'th matching file name
1671     char_u	*rel_fname)	// file name searching relative to
1672 {
1673     return find_file_in_path_option(ptr, len, options, first,
1674 	    *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path,
1675 	    FINDFILE_BOTH, rel_fname, curbuf->b_p_sua);
1676 }
1677 
1678 static char_u	*ff_file_to_find = NULL;
1679 static void	*fdip_search_ctx = NULL;
1680 
1681 # if defined(EXITFREE) || defined(PROTO)
1682     void
free_findfile(void)1683 free_findfile(void)
1684 {
1685     vim_free(ff_file_to_find);
1686     vim_findfile_cleanup(fdip_search_ctx);
1687     vim_free(ff_expand_buffer);
1688 }
1689 # endif
1690 
1691 /*
1692  * Find the directory name "ptr[len]" in the path.
1693  *
1694  * options:
1695  * FNAME_MESS	    give error message when not found
1696  * FNAME_UNESC	    unescape backslashes.
1697  *
1698  * Uses NameBuff[]!
1699  *
1700  * Returns an allocated string for the file name.  NULL for error.
1701  */
1702     char_u *
find_directory_in_path(char_u * ptr,int len,int options,char_u * rel_fname)1703 find_directory_in_path(
1704     char_u	*ptr,		// file name
1705     int		len,		// length of file name
1706     int		options,
1707     char_u	*rel_fname)	// file name searching relative to
1708 {
1709     return find_file_in_path_option(ptr, len, options, TRUE, p_cdpath,
1710 				       FINDFILE_DIR, rel_fname, (char_u *)"");
1711 }
1712 
1713     char_u *
find_file_in_path_option(char_u * ptr,int len,int options,int first,char_u * path_option,int find_what,char_u * rel_fname,char_u * suffixes)1714 find_file_in_path_option(
1715     char_u	*ptr,		// file name
1716     int		len,		// length of file name
1717     int		options,
1718     int		first,		// use count'th matching file name
1719     char_u	*path_option,	// p_path or p_cdpath
1720     int		find_what,	// FINDFILE_FILE, _DIR or _BOTH
1721     char_u	*rel_fname,	// file name we are looking relative to.
1722     char_u	*suffixes)	// list of suffixes, 'suffixesadd' option
1723 {
1724     static char_u	*dir;
1725     static int		did_findfile_init = FALSE;
1726     char_u		save_char;
1727     char_u		*file_name = NULL;
1728     char_u		*buf = NULL;
1729     int			rel_to_curdir;
1730 # ifdef AMIGA
1731     struct Process	*proc = (struct Process *)FindTask(0L);
1732     APTR		save_winptr = proc->pr_WindowPtr;
1733 
1734     // Avoid a requester here for a volume that doesn't exist.
1735     proc->pr_WindowPtr = (APTR)-1L;
1736 # endif
1737 
1738     if (first == TRUE)
1739     {
1740 	if (len == 0)
1741 	    return NULL;
1742 
1743 	// copy file name into NameBuff, expanding environment variables
1744 	save_char = ptr[len];
1745 	ptr[len] = NUL;
1746 	expand_env_esc(ptr, NameBuff, MAXPATHL, FALSE, TRUE, NULL);
1747 	ptr[len] = save_char;
1748 
1749 	vim_free(ff_file_to_find);
1750 	ff_file_to_find = vim_strsave(NameBuff);
1751 	if (ff_file_to_find == NULL)	// out of memory
1752 	{
1753 	    file_name = NULL;
1754 	    goto theend;
1755 	}
1756 	if (options & FNAME_UNESC)
1757 	{
1758 	    // Change all "\ " to " ".
1759 	    for (ptr = ff_file_to_find; *ptr != NUL; ++ptr)
1760 		if (ptr[0] == '\\' && ptr[1] == ' ')
1761 		    mch_memmove(ptr, ptr + 1, STRLEN(ptr));
1762 	}
1763     }
1764 
1765     rel_to_curdir = (ff_file_to_find[0] == '.'
1766 		    && (ff_file_to_find[1] == NUL
1767 			|| vim_ispathsep(ff_file_to_find[1])
1768 			|| (ff_file_to_find[1] == '.'
1769 			    && (ff_file_to_find[2] == NUL
1770 				|| vim_ispathsep(ff_file_to_find[2])))));
1771     if (vim_isAbsName(ff_file_to_find)
1772 	    // "..", "../path", "." and "./path": don't use the path_option
1773 	    || rel_to_curdir
1774 # if defined(MSWIN)
1775 	    // handle "\tmp" as absolute path
1776 	    || vim_ispathsep(ff_file_to_find[0])
1777 	    // handle "c:name" as absolute path
1778 	    || (ff_file_to_find[0] != NUL && ff_file_to_find[1] == ':')
1779 # endif
1780 # ifdef AMIGA
1781 	    // handle ":tmp" as absolute path
1782 	    || ff_file_to_find[0] == ':'
1783 # endif
1784        )
1785     {
1786 	/*
1787 	 * Absolute path, no need to use "path_option".
1788 	 * If this is not a first call, return NULL.  We already returned a
1789 	 * filename on the first call.
1790 	 */
1791 	if (first == TRUE)
1792 	{
1793 	    int		l;
1794 	    int		run;
1795 
1796 	    if (path_with_url(ff_file_to_find))
1797 	    {
1798 		file_name = vim_strsave(ff_file_to_find);
1799 		goto theend;
1800 	    }
1801 
1802 	    // When FNAME_REL flag given first use the directory of the file.
1803 	    // Otherwise or when this fails use the current directory.
1804 	    for (run = 1; run <= 2; ++run)
1805 	    {
1806 		l = (int)STRLEN(ff_file_to_find);
1807 		if (run == 1
1808 			&& rel_to_curdir
1809 			&& (options & FNAME_REL)
1810 			&& rel_fname != NULL
1811 			&& STRLEN(rel_fname) + l < MAXPATHL)
1812 		{
1813 		    STRCPY(NameBuff, rel_fname);
1814 		    STRCPY(gettail(NameBuff), ff_file_to_find);
1815 		    l = (int)STRLEN(NameBuff);
1816 		}
1817 		else
1818 		{
1819 		    STRCPY(NameBuff, ff_file_to_find);
1820 		    run = 2;
1821 		}
1822 
1823 		// When the file doesn't exist, try adding parts of
1824 		// 'suffixesadd'.
1825 		buf = suffixes;
1826 		for (;;)
1827 		{
1828 		    if (mch_getperm(NameBuff) >= 0
1829 			     && (find_what == FINDFILE_BOTH
1830 				 || ((find_what == FINDFILE_DIR)
1831 						    == mch_isdir(NameBuff))))
1832 		    {
1833 			file_name = vim_strsave(NameBuff);
1834 			goto theend;
1835 		    }
1836 		    if (*buf == NUL)
1837 			break;
1838 		    copy_option_part(&buf, NameBuff + l, MAXPATHL - l, ",");
1839 		}
1840 	    }
1841 	}
1842     }
1843     else
1844     {
1845 	/*
1846 	 * Loop over all paths in the 'path' or 'cdpath' option.
1847 	 * When "first" is set, first setup to the start of the option.
1848 	 * Otherwise continue to find the next match.
1849 	 */
1850 	if (first == TRUE)
1851 	{
1852 	    // vim_findfile_free_visited can handle a possible NULL pointer
1853 	    vim_findfile_free_visited(fdip_search_ctx);
1854 	    dir = path_option;
1855 	    did_findfile_init = FALSE;
1856 	}
1857 
1858 	for (;;)
1859 	{
1860 	    if (did_findfile_init)
1861 	    {
1862 		file_name = vim_findfile(fdip_search_ctx);
1863 		if (file_name != NULL)
1864 		    break;
1865 
1866 		did_findfile_init = FALSE;
1867 	    }
1868 	    else
1869 	    {
1870 		char_u  *r_ptr;
1871 
1872 		if (dir == NULL || *dir == NUL)
1873 		{
1874 		    // We searched all paths of the option, now we can
1875 		    // free the search context.
1876 		    vim_findfile_cleanup(fdip_search_ctx);
1877 		    fdip_search_ctx = NULL;
1878 		    break;
1879 		}
1880 
1881 		if ((buf = alloc(MAXPATHL)) == NULL)
1882 		    break;
1883 
1884 		// copy next path
1885 		buf[0] = 0;
1886 		copy_option_part(&dir, buf, MAXPATHL, " ,");
1887 
1888 # ifdef FEAT_PATH_EXTRA
1889 		// get the stopdir string
1890 		r_ptr = vim_findfile_stopdir(buf);
1891 # else
1892 		r_ptr = NULL;
1893 # endif
1894 		fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find,
1895 					    r_ptr, 100, FALSE, find_what,
1896 					   fdip_search_ctx, FALSE, rel_fname);
1897 		if (fdip_search_ctx != NULL)
1898 		    did_findfile_init = TRUE;
1899 		vim_free(buf);
1900 	    }
1901 	}
1902     }
1903     if (file_name == NULL && (options & FNAME_MESS))
1904     {
1905 	if (first == TRUE)
1906 	{
1907 	    if (find_what == FINDFILE_DIR)
1908 		semsg(_("E344: Can't find directory \"%s\" in cdpath"),
1909 			ff_file_to_find);
1910 	    else
1911 		semsg(_("E345: Can't find file \"%s\" in path"),
1912 			ff_file_to_find);
1913 	}
1914 	else
1915 	{
1916 	    if (find_what == FINDFILE_DIR)
1917 		semsg(_("E346: No more directory \"%s\" found in cdpath"),
1918 			ff_file_to_find);
1919 	    else
1920 		semsg(_("E347: No more file \"%s\" found in path"),
1921 			ff_file_to_find);
1922 	}
1923     }
1924 
1925 theend:
1926 # ifdef AMIGA
1927     proc->pr_WindowPtr = save_winptr;
1928 # endif
1929     return file_name;
1930 }
1931 
1932 /*
1933  * Get the file name at the cursor.
1934  * If Visual mode is active, use the selected text if it's in one line.
1935  * Returns the name in allocated memory, NULL for failure.
1936  */
1937     char_u *
grab_file_name(long count,linenr_T * file_lnum)1938 grab_file_name(long count, linenr_T *file_lnum)
1939 {
1940     int options = FNAME_MESS|FNAME_EXP|FNAME_REL|FNAME_UNESC;
1941 
1942     if (VIsual_active)
1943     {
1944 	int	len;
1945 	char_u	*ptr;
1946 
1947 	if (get_visual_text(NULL, &ptr, &len) == FAIL)
1948 	    return NULL;
1949 	// Only recognize ":123" here
1950 	if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1]))
1951 	{
1952 	    char_u *p = ptr + len + 1;
1953 
1954 	    *file_lnum = getdigits(&p);
1955 	}
1956 	return find_file_name_in_path(ptr, len, options,
1957 						     count, curbuf->b_ffname);
1958     }
1959     return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
1960 }
1961 
1962 /*
1963  * Return the file name under or after the cursor.
1964  *
1965  * The 'path' option is searched if the file name is not absolute.
1966  * The string returned has been alloc'ed and should be freed by the caller.
1967  * NULL is returned if the file name or file is not found.
1968  *
1969  * options:
1970  * FNAME_MESS	    give error messages
1971  * FNAME_EXP	    expand to path
1972  * FNAME_HYP	    check for hypertext link
1973  * FNAME_INCL	    apply "includeexpr"
1974  */
1975     char_u *
file_name_at_cursor(int options,long count,linenr_T * file_lnum)1976 file_name_at_cursor(int options, long count, linenr_T *file_lnum)
1977 {
1978     return file_name_in_line(ml_get_curline(),
1979 		      curwin->w_cursor.col, options, count, curbuf->b_ffname,
1980 		      file_lnum);
1981 }
1982 
1983 /*
1984  * Return the name of the file under or after ptr[col].
1985  * Otherwise like file_name_at_cursor().
1986  */
1987     char_u *
file_name_in_line(char_u * line,int col,int options,long count,char_u * rel_fname,linenr_T * file_lnum)1988 file_name_in_line(
1989     char_u	*line,
1990     int		col,
1991     int		options,
1992     long	count,
1993     char_u	*rel_fname,	// file we are searching relative to
1994     linenr_T	*file_lnum)	// line number after the file name
1995 {
1996     char_u	*ptr;
1997     int		len;
1998     int		in_type = TRUE;
1999     int		is_url = FALSE;
2000 
2001     /*
2002      * search forward for what could be the start of a file name
2003      */
2004     ptr = line + col;
2005     while (*ptr != NUL && !vim_isfilec(*ptr))
2006 	MB_PTR_ADV(ptr);
2007     if (*ptr == NUL)		// nothing found
2008     {
2009 	if (options & FNAME_MESS)
2010 	    emsg(_("E446: No file name under cursor"));
2011 	return NULL;
2012     }
2013 
2014     /*
2015      * Search backward for first char of the file name.
2016      * Go one char back to ":" before "//" even when ':' is not in 'isfname'.
2017      */
2018     while (ptr > line)
2019     {
2020 	if (has_mbyte && (len = (*mb_head_off)(line, ptr - 1)) > 0)
2021 	    ptr -= len + 1;
2022 	else if (vim_isfilec(ptr[-1])
2023 		|| ((options & FNAME_HYP) && path_is_url(ptr - 1)))
2024 	    --ptr;
2025 	else
2026 	    break;
2027     }
2028 
2029     /*
2030      * Search forward for the last char of the file name.
2031      * Also allow "://" when ':' is not in 'isfname'.
2032      */
2033     len = 0;
2034     while (vim_isfilec(ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ')
2035 			 || ((options & FNAME_HYP) && path_is_url(ptr + len))
2036 			 || (is_url && vim_strchr((char_u *)":?&=", ptr[len]) != NULL))
2037     {
2038 	// After type:// we also include :, ?, & and = as valid characters, so that
2039 	// http://google.com:8080?q=this&that=ok works.
2040 	if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z'))
2041 	{
2042 	    if (in_type && path_is_url(ptr + len + 1))
2043 		is_url = TRUE;
2044 	}
2045 	else
2046 	    in_type = FALSE;
2047 
2048 	if (ptr[len] == '\\')
2049 	    // Skip over the "\" in "\ ".
2050 	    ++len;
2051 	if (has_mbyte)
2052 	    len += (*mb_ptr2len)(ptr + len);
2053 	else
2054 	    ++len;
2055     }
2056 
2057     /*
2058      * If there is trailing punctuation, remove it.
2059      * But don't remove "..", could be a directory name.
2060      */
2061     if (len > 2 && vim_strchr((char_u *)".,:;!", ptr[len - 1]) != NULL
2062 						       && ptr[len - 2] != '.')
2063 	--len;
2064 
2065     if (file_lnum != NULL)
2066     {
2067 	char_u *p;
2068 	char	*line_english = " line ";
2069 	char	*line_transl = _(line_msg);
2070 
2071 	// Get the number after the file name and a separator character.
2072 	// Also accept " line 999" with and without the same translation as
2073 	// used in last_set_msg().
2074 	p = ptr + len;
2075 	if (STRNCMP(p, line_english, STRLEN(line_english)) == 0)
2076 	    p += STRLEN(line_english);
2077 	else if (STRNCMP(p, line_transl, STRLEN(line_transl)) == 0)
2078 	    p += STRLEN(line_transl);
2079 	else
2080 	    p = skipwhite(p);
2081 	if (*p != NUL)
2082 	{
2083 	    if (!isdigit(*p))
2084 		++p;		    // skip the separator
2085 	    p = skipwhite(p);
2086 	    if (isdigit(*p))
2087 		*file_lnum = (int)getdigits(&p);
2088 	}
2089     }
2090 
2091     return find_file_name_in_path(ptr, len, options, count, rel_fname);
2092 }
2093 
2094 # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
2095     static char_u *
eval_includeexpr(char_u * ptr,int len)2096 eval_includeexpr(char_u *ptr, int len)
2097 {
2098     char_u	*res;
2099 
2100     set_vim_var_string(VV_FNAME, ptr, len);
2101     res = eval_to_string_safe(curbuf->b_p_inex,
2102 		      was_set_insecurely((char_u *)"includeexpr", OPT_LOCAL));
2103     set_vim_var_string(VV_FNAME, NULL, 0);
2104     return res;
2105 }
2106 # endif
2107 
2108 /*
2109  * Return the name of the file ptr[len] in 'path'.
2110  * Otherwise like file_name_at_cursor().
2111  */
2112     char_u *
find_file_name_in_path(char_u * ptr,int len,int options,long count,char_u * rel_fname)2113 find_file_name_in_path(
2114     char_u	*ptr,
2115     int		len,
2116     int		options,
2117     long	count,
2118     char_u	*rel_fname)	// file we are searching relative to
2119 {
2120     char_u	*file_name;
2121     int		c;
2122 # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
2123     char_u	*tofree = NULL;
2124 # endif
2125 
2126     if (len == 0)
2127 	return NULL;
2128 
2129 # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
2130     if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL)
2131     {
2132 	tofree = eval_includeexpr(ptr, len);
2133 	if (tofree != NULL)
2134 	{
2135 	    ptr = tofree;
2136 	    len = (int)STRLEN(ptr);
2137 	}
2138     }
2139 # endif
2140 
2141     if (options & FNAME_EXP)
2142     {
2143 	file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
2144 							     TRUE, rel_fname);
2145 
2146 # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
2147 	/*
2148 	 * If the file could not be found in a normal way, try applying
2149 	 * 'includeexpr' (unless done already).
2150 	 */
2151 	if (file_name == NULL
2152 		&& !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL)
2153 	{
2154 	    tofree = eval_includeexpr(ptr, len);
2155 	    if (tofree != NULL)
2156 	    {
2157 		ptr = tofree;
2158 		len = (int)STRLEN(ptr);
2159 		file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
2160 							     TRUE, rel_fname);
2161 	    }
2162 	}
2163 # endif
2164 	if (file_name == NULL && (options & FNAME_MESS))
2165 	{
2166 	    c = ptr[len];
2167 	    ptr[len] = NUL;
2168 	    semsg(_("E447: Can't find file \"%s\" in path"), ptr);
2169 	    ptr[len] = c;
2170 	}
2171 
2172 	// Repeat finding the file "count" times.  This matters when it
2173 	// appears several times in the path.
2174 	while (file_name != NULL && --count > 0)
2175 	{
2176 	    vim_free(file_name);
2177 	    file_name = find_file_in_path(ptr, len, options, FALSE, rel_fname);
2178 	}
2179     }
2180     else
2181 	file_name = vim_strnsave(ptr, len);
2182 
2183 # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
2184     vim_free(tofree);
2185 # endif
2186 
2187     return file_name;
2188 }
2189 
2190 /*
2191  * Return the end of the directory name, on the first path
2192  * separator:
2193  * "/path/file", "/path/dir/", "/path//dir", "/file"
2194  *	 ^	       ^	     ^	      ^
2195  */
2196     static char_u *
gettail_dir(char_u * fname)2197 gettail_dir(char_u *fname)
2198 {
2199     char_u	*dir_end = fname;
2200     char_u	*next_dir_end = fname;
2201     int		look_for_sep = TRUE;
2202     char_u	*p;
2203 
2204     for (p = fname; *p != NUL; )
2205     {
2206 	if (vim_ispathsep(*p))
2207 	{
2208 	    if (look_for_sep)
2209 	    {
2210 		next_dir_end = p;
2211 		look_for_sep = FALSE;
2212 	    }
2213 	}
2214 	else
2215 	{
2216 	    if (!look_for_sep)
2217 		dir_end = next_dir_end;
2218 	    look_for_sep = TRUE;
2219 	}
2220 	MB_PTR_ADV(p);
2221     }
2222     return dir_end;
2223 }
2224 
2225 /*
2226  * return TRUE if 'c' is a path list separator.
2227  */
2228     int
vim_ispathlistsep(int c)2229 vim_ispathlistsep(int c)
2230 {
2231 # ifdef UNIX
2232     return (c == ':');
2233 # else
2234     return (c == ';');	// might not be right for every system...
2235 # endif
2236 }
2237 
2238 /*
2239  * Moves "*psep" back to the previous path separator in "path".
2240  * Returns FAIL is "*psep" ends up at the beginning of "path".
2241  */
2242     static int
find_previous_pathsep(char_u * path,char_u ** psep)2243 find_previous_pathsep(char_u *path, char_u **psep)
2244 {
2245     // skip the current separator
2246     if (*psep > path && vim_ispathsep(**psep))
2247 	--*psep;
2248 
2249     // find the previous separator
2250     while (*psep > path)
2251     {
2252 	if (vim_ispathsep(**psep))
2253 	    return OK;
2254 	MB_PTR_BACK(path, *psep);
2255     }
2256 
2257     return FAIL;
2258 }
2259 
2260 /*
2261  * Returns TRUE if "maybe_unique" is unique wrt other_paths in "gap".
2262  * "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]".
2263  */
2264     static int
is_unique(char_u * maybe_unique,garray_T * gap,int i)2265 is_unique(char_u *maybe_unique, garray_T *gap, int i)
2266 {
2267     int	    j;
2268     int	    candidate_len;
2269     int	    other_path_len;
2270     char_u  **other_paths = (char_u **)gap->ga_data;
2271     char_u  *rival;
2272 
2273     for (j = 0; j < gap->ga_len; j++)
2274     {
2275 	if (j == i)
2276 	    continue;  // don't compare it with itself
2277 
2278 	candidate_len = (int)STRLEN(maybe_unique);
2279 	other_path_len = (int)STRLEN(other_paths[j]);
2280 	if (other_path_len < candidate_len)
2281 	    continue;  // it's different when it's shorter
2282 
2283 	rival = other_paths[j] + other_path_len - candidate_len;
2284 	if (fnamecmp(maybe_unique, rival) == 0
2285 		&& (rival == other_paths[j] || vim_ispathsep(*(rival - 1))))
2286 	    return FALSE;  // match
2287     }
2288 
2289     return TRUE;  // no match found
2290 }
2291 
2292 /*
2293  * Split the 'path' option into an array of strings in garray_T.  Relative
2294  * paths are expanded to their equivalent fullpath.  This includes the "."
2295  * (relative to current buffer directory) and empty path (relative to current
2296  * directory) notations.
2297  *
2298  * TODO: handle upward search (;) and path limiter (**N) notations by
2299  * expanding each into their equivalent path(s).
2300  */
2301     static void
expand_path_option(char_u * curdir,garray_T * gap)2302 expand_path_option(char_u *curdir, garray_T *gap)
2303 {
2304     char_u	*path_option = *curbuf->b_p_path == NUL
2305 						  ? p_path : curbuf->b_p_path;
2306     char_u	*buf;
2307     char_u	*p;
2308     int		len;
2309 
2310     if ((buf = alloc(MAXPATHL)) == NULL)
2311 	return;
2312 
2313     while (*path_option != NUL)
2314     {
2315 	copy_option_part(&path_option, buf, MAXPATHL, " ,");
2316 
2317 	if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1])))
2318 	{
2319 	    // Relative to current buffer:
2320 	    // "/path/file" + "." -> "/path/"
2321 	    // "/path/file"  + "./subdir" -> "/path/subdir"
2322 	    if (curbuf->b_ffname == NULL)
2323 		continue;
2324 	    p = gettail(curbuf->b_ffname);
2325 	    len = (int)(p - curbuf->b_ffname);
2326 	    if (len + (int)STRLEN(buf) >= MAXPATHL)
2327 		continue;
2328 	    if (buf[1] == NUL)
2329 		buf[len] = NUL;
2330 	    else
2331 		STRMOVE(buf + len, buf + 2);
2332 	    mch_memmove(buf, curbuf->b_ffname, len);
2333 	    simplify_filename(buf);
2334 	}
2335 	else if (buf[0] == NUL)
2336 	    // relative to current directory
2337 	    STRCPY(buf, curdir);
2338 	else if (path_with_url(buf))
2339 	    // URL can't be used here
2340 	    continue;
2341 	else if (!mch_isFullName(buf))
2342 	{
2343 	    // Expand relative path to their full path equivalent
2344 	    len = (int)STRLEN(curdir);
2345 	    if (len + (int)STRLEN(buf) + 3 > MAXPATHL)
2346 		continue;
2347 	    STRMOVE(buf + len + 1, buf);
2348 	    STRCPY(buf, curdir);
2349 	    buf[len] = PATHSEP;
2350 	    simplify_filename(buf);
2351 	}
2352 
2353 	if (ga_grow(gap, 1) == FAIL)
2354 	    break;
2355 
2356 # if defined(MSWIN)
2357 	// Avoid the path ending in a backslash, it fails when a comma is
2358 	// appended.
2359 	len = (int)STRLEN(buf);
2360 	if (buf[len - 1] == '\\')
2361 	    buf[len - 1] = '/';
2362 # endif
2363 
2364 	p = vim_strsave(buf);
2365 	if (p == NULL)
2366 	    break;
2367 	((char_u **)gap->ga_data)[gap->ga_len++] = p;
2368     }
2369 
2370     vim_free(buf);
2371 }
2372 
2373 /*
2374  * Returns a pointer to the file or directory name in "fname" that matches the
2375  * longest path in "ga"p, or NULL if there is no match. For example:
2376  *
2377  *    path: /foo/bar/baz
2378  *   fname: /foo/bar/baz/quux.txt
2379  * returns:		 ^this
2380  */
2381     static char_u *
get_path_cutoff(char_u * fname,garray_T * gap)2382 get_path_cutoff(char_u *fname, garray_T *gap)
2383 {
2384     int	    i;
2385     int	    maxlen = 0;
2386     char_u  **path_part = (char_u **)gap->ga_data;
2387     char_u  *cutoff = NULL;
2388 
2389     for (i = 0; i < gap->ga_len; i++)
2390     {
2391 	int j = 0;
2392 
2393 	while ((fname[j] == path_part[i][j]
2394 # if defined(MSWIN)
2395 		|| (vim_ispathsep(fname[j]) && vim_ispathsep(path_part[i][j]))
2396 # endif
2397 			     ) && fname[j] != NUL && path_part[i][j] != NUL)
2398 	    j++;
2399 	if (j > maxlen)
2400 	{
2401 	    maxlen = j;
2402 	    cutoff = &fname[j];
2403 	}
2404     }
2405 
2406     // skip to the file or directory name
2407     if (cutoff != NULL)
2408 	while (vim_ispathsep(*cutoff))
2409 	    MB_PTR_ADV(cutoff);
2410 
2411     return cutoff;
2412 }
2413 
2414 /*
2415  * Sorts, removes duplicates and modifies all the fullpath names in "gap" so
2416  * that they are unique with respect to each other while conserving the part
2417  * that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len".
2418  */
2419     void
uniquefy_paths(garray_T * gap,char_u * pattern)2420 uniquefy_paths(garray_T *gap, char_u *pattern)
2421 {
2422     int		i;
2423     int		len;
2424     char_u	**fnames = (char_u **)gap->ga_data;
2425     int		sort_again = FALSE;
2426     char_u	*pat;
2427     char_u      *file_pattern;
2428     char_u	*curdir;
2429     regmatch_T	regmatch;
2430     garray_T	path_ga;
2431     char_u	**in_curdir = NULL;
2432     char_u	*short_name;
2433 
2434     remove_duplicates(gap);
2435     ga_init2(&path_ga, (int)sizeof(char_u *), 1);
2436 
2437     /*
2438      * We need to prepend a '*' at the beginning of file_pattern so that the
2439      * regex matches anywhere in the path. FIXME: is this valid for all
2440      * possible patterns?
2441      */
2442     len = (int)STRLEN(pattern);
2443     file_pattern = alloc(len + 2);
2444     if (file_pattern == NULL)
2445 	return;
2446     file_pattern[0] = '*';
2447     file_pattern[1] = NUL;
2448     STRCAT(file_pattern, pattern);
2449     pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, TRUE);
2450     vim_free(file_pattern);
2451     if (pat == NULL)
2452 	return;
2453 
2454     regmatch.rm_ic = TRUE;		// always ignore case
2455     regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
2456     vim_free(pat);
2457     if (regmatch.regprog == NULL)
2458 	return;
2459 
2460     if ((curdir = alloc(MAXPATHL)) == NULL)
2461 	goto theend;
2462     mch_dirname(curdir, MAXPATHL);
2463     expand_path_option(curdir, &path_ga);
2464 
2465     in_curdir = ALLOC_CLEAR_MULT(char_u *, gap->ga_len);
2466     if (in_curdir == NULL)
2467 	goto theend;
2468 
2469     for (i = 0; i < gap->ga_len && !got_int; i++)
2470     {
2471 	char_u	    *path = fnames[i];
2472 	int	    is_in_curdir;
2473 	char_u	    *dir_end = gettail_dir(path);
2474 	char_u	    *pathsep_p;
2475 	char_u	    *path_cutoff;
2476 
2477 	len = (int)STRLEN(path);
2478 	is_in_curdir = fnamencmp(curdir, path, dir_end - path) == 0
2479 					     && curdir[dir_end - path] == NUL;
2480 	if (is_in_curdir)
2481 	    in_curdir[i] = vim_strsave(path);
2482 
2483 	// Shorten the filename while maintaining its uniqueness
2484 	path_cutoff = get_path_cutoff(path, &path_ga);
2485 
2486 	// Don't assume all files can be reached without path when search
2487 	// pattern starts with star star slash, so only remove path_cutoff
2488 	// when possible.
2489 	if (pattern[0] == '*' && pattern[1] == '*'
2490 		&& vim_ispathsep_nocolon(pattern[2])
2491 		&& path_cutoff != NULL
2492 		&& vim_regexec(&regmatch, path_cutoff, (colnr_T)0)
2493 		&& is_unique(path_cutoff, gap, i))
2494 	{
2495 	    sort_again = TRUE;
2496 	    mch_memmove(path, path_cutoff, STRLEN(path_cutoff) + 1);
2497 	}
2498 	else
2499 	{
2500 	    // Here all files can be reached without path, so get shortest
2501 	    // unique path.  We start at the end of the path.
2502 	    pathsep_p = path + len - 1;
2503 
2504 	    while (find_previous_pathsep(path, &pathsep_p))
2505 		if (vim_regexec(&regmatch, pathsep_p + 1, (colnr_T)0)
2506 			&& is_unique(pathsep_p + 1, gap, i)
2507 			&& path_cutoff != NULL && pathsep_p + 1 >= path_cutoff)
2508 		{
2509 		    sort_again = TRUE;
2510 		    mch_memmove(path, pathsep_p + 1, STRLEN(pathsep_p));
2511 		    break;
2512 		}
2513 	}
2514 
2515 	if (mch_isFullName(path))
2516 	{
2517 	    /*
2518 	     * Last resort: shorten relative to curdir if possible.
2519 	     * 'possible' means:
2520 	     * 1. It is under the current directory.
2521 	     * 2. The result is actually shorter than the original.
2522 	     *
2523 	     *	    Before		  curdir	After
2524 	     *	    /foo/bar/file.txt	  /foo/bar	./file.txt
2525 	     *	    c:\foo\bar\file.txt   c:\foo\bar	.\file.txt
2526 	     *	    /file.txt		  /		/file.txt
2527 	     *	    c:\file.txt		  c:\		.\file.txt
2528 	     */
2529 	    short_name = shorten_fname(path, curdir);
2530 	    if (short_name != NULL && short_name > path + 1
2531 # if defined(MSWIN)
2532 		    // On windows,
2533 		    //	    shorten_fname("c:\a\a.txt", "c:\a\b")
2534 		    // returns "\a\a.txt", which is not really the short
2535 		    // name, hence:
2536 		    && !vim_ispathsep(*short_name)
2537 # endif
2538 		)
2539 	    {
2540 		STRCPY(path, ".");
2541 		add_pathsep(path);
2542 		STRMOVE(path + STRLEN(path), short_name);
2543 	    }
2544 	}
2545 	ui_breakcheck();
2546     }
2547 
2548     // Shorten filenames in /in/current/directory/{filename}
2549     for (i = 0; i < gap->ga_len && !got_int; i++)
2550     {
2551 	char_u *rel_path;
2552 	char_u *path = in_curdir[i];
2553 
2554 	if (path == NULL)
2555 	    continue;
2556 
2557 	// If the {filename} is not unique, change it to ./{filename}.
2558 	// Else reduce it to {filename}
2559 	short_name = shorten_fname(path, curdir);
2560 	if (short_name == NULL)
2561 	    short_name = path;
2562 	if (is_unique(short_name, gap, i))
2563 	{
2564 	    STRCPY(fnames[i], short_name);
2565 	    continue;
2566 	}
2567 
2568 	rel_path = alloc(STRLEN(short_name) + STRLEN(PATHSEPSTR) + 2);
2569 	if (rel_path == NULL)
2570 	    goto theend;
2571 	STRCPY(rel_path, ".");
2572 	add_pathsep(rel_path);
2573 	STRCAT(rel_path, short_name);
2574 
2575 	vim_free(fnames[i]);
2576 	fnames[i] = rel_path;
2577 	sort_again = TRUE;
2578 	ui_breakcheck();
2579     }
2580 
2581 theend:
2582     vim_free(curdir);
2583     if (in_curdir != NULL)
2584     {
2585 	for (i = 0; i < gap->ga_len; i++)
2586 	    vim_free(in_curdir[i]);
2587 	vim_free(in_curdir);
2588     }
2589     ga_clear_strings(&path_ga);
2590     vim_regfree(regmatch.regprog);
2591 
2592     if (sort_again)
2593 	remove_duplicates(gap);
2594 }
2595 
2596 /*
2597  * Calls globpath() with 'path' values for the given pattern and stores the
2598  * result in "gap".
2599  * Returns the total number of matches.
2600  */
2601     int
expand_in_path(garray_T * gap,char_u * pattern,int flags)2602 expand_in_path(
2603     garray_T	*gap,
2604     char_u	*pattern,
2605     int		flags)		// EW_* flags
2606 {
2607     char_u	*curdir;
2608     garray_T	path_ga;
2609     char_u	*paths = NULL;
2610     int		glob_flags = 0;
2611 
2612     if ((curdir = alloc(MAXPATHL)) == NULL)
2613 	return 0;
2614     mch_dirname(curdir, MAXPATHL);
2615 
2616     ga_init2(&path_ga, (int)sizeof(char_u *), 1);
2617     expand_path_option(curdir, &path_ga);
2618     vim_free(curdir);
2619     if (path_ga.ga_len == 0)
2620 	return 0;
2621 
2622     paths = ga_concat_strings(&path_ga, ",");
2623     ga_clear_strings(&path_ga);
2624     if (paths == NULL)
2625 	return 0;
2626 
2627     if (flags & EW_ICASE)
2628 	glob_flags |= WILD_ICASE;
2629     if (flags & EW_ADDSLASH)
2630 	glob_flags |= WILD_ADD_SLASH;
2631     globpath(paths, pattern, gap, glob_flags);
2632     vim_free(paths);
2633 
2634     return gap->ga_len;
2635 }
2636 
2637 #endif // FEAT_SEARCHPATH
2638 
2639 /*
2640  * Converts a file name into a canonical form. It simplifies a file name into
2641  * its simplest form by stripping out unneeded components, if any.  The
2642  * resulting file name is simplified in place and will either be the same
2643  * length as that supplied, or shorter.
2644  */
2645     void
simplify_filename(char_u * filename)2646 simplify_filename(char_u *filename)
2647 {
2648 #ifndef AMIGA	    // Amiga doesn't have "..", it uses "/"
2649     int		components = 0;
2650     char_u	*p, *tail, *start;
2651     int		stripping_disabled = FALSE;
2652     int		relative = TRUE;
2653 
2654     p = filename;
2655 # ifdef BACKSLASH_IN_FILENAME
2656     if (p[1] == ':')	    // skip "x:"
2657 	p += 2;
2658 # endif
2659 
2660     if (vim_ispathsep(*p))
2661     {
2662 	relative = FALSE;
2663 	do
2664 	    ++p;
2665 	while (vim_ispathsep(*p));
2666     }
2667     start = p;	    // remember start after "c:/" or "/" or "///"
2668 #ifdef UNIX
2669     // Posix says that "//path" is unchanged but "///path" is "/path".
2670     if (start > filename + 2)
2671     {
2672 	STRMOVE(filename + 1, p);
2673 	start = p = filename + 1;
2674     }
2675 #endif
2676 
2677     do
2678     {
2679 	// At this point "p" is pointing to the char following a single "/"
2680 	// or "p" is at the "start" of the (absolute or relative) path name.
2681 # ifdef VMS
2682 	// VMS allows device:[path] - don't strip the [ in directory
2683 	if ((*p == '[' || *p == '<') && p > filename && p[-1] == ':')
2684 	{
2685 	    // :[ or :< composition: vms directory component
2686 	    ++components;
2687 	    p = getnextcomp(p + 1);
2688 	}
2689 	// allow remote calls as host"user passwd"::device:[path]
2690 	else if (p[0] == ':' && p[1] == ':' && p > filename && p[-1] == '"' )
2691 	{
2692 	    // ":: composition: vms host/passwd component
2693 	    ++components;
2694 	    p = getnextcomp(p + 2);
2695 	}
2696 	else
2697 # endif
2698 	  if (vim_ispathsep(*p))
2699 	    STRMOVE(p, p + 1);		// remove duplicate "/"
2700 	else if (p[0] == '.' && (vim_ispathsep(p[1]) || p[1] == NUL))
2701 	{
2702 	    if (p == start && relative)
2703 		p += 1 + (p[1] != NUL);	// keep single "." or leading "./"
2704 	    else
2705 	    {
2706 		// Strip "./" or ".///".  If we are at the end of the file name
2707 		// and there is no trailing path separator, either strip "/." if
2708 		// we are after "start", or strip "." if we are at the beginning
2709 		// of an absolute path name .
2710 		tail = p + 1;
2711 		if (p[1] != NUL)
2712 		    while (vim_ispathsep(*tail))
2713 			MB_PTR_ADV(tail);
2714 		else if (p > start)
2715 		    --p;		// strip preceding path separator
2716 		STRMOVE(p, tail);
2717 	    }
2718 	}
2719 	else if (p[0] == '.' && p[1] == '.' &&
2720 	    (vim_ispathsep(p[2]) || p[2] == NUL))
2721 	{
2722 	    // Skip to after ".." or "../" or "..///".
2723 	    tail = p + 2;
2724 	    while (vim_ispathsep(*tail))
2725 		MB_PTR_ADV(tail);
2726 
2727 	    if (components > 0)		// strip one preceding component
2728 	    {
2729 		int		do_strip = FALSE;
2730 		char_u		saved_char;
2731 		stat_T		st;
2732 
2733 		// Don't strip for an erroneous file name.
2734 		if (!stripping_disabled)
2735 		{
2736 		    // If the preceding component does not exist in the file
2737 		    // system, we strip it.  On Unix, we don't accept a symbolic
2738 		    // link that refers to a non-existent file.
2739 		    saved_char = p[-1];
2740 		    p[-1] = NUL;
2741 # ifdef UNIX
2742 		    if (mch_lstat((char *)filename, &st) < 0)
2743 # else
2744 			if (mch_stat((char *)filename, &st) < 0)
2745 # endif
2746 			    do_strip = TRUE;
2747 		    p[-1] = saved_char;
2748 
2749 		    --p;
2750 		    // Skip back to after previous '/'.
2751 		    while (p > start && !after_pathsep(start, p))
2752 			MB_PTR_BACK(start, p);
2753 
2754 		    if (!do_strip)
2755 		    {
2756 			// If the component exists in the file system, check
2757 			// that stripping it won't change the meaning of the
2758 			// file name.  First get information about the
2759 			// unstripped file name.  This may fail if the component
2760 			// to strip is not a searchable directory (but a regular
2761 			// file, for instance), since the trailing "/.." cannot
2762 			// be applied then.  We don't strip it then since we
2763 			// don't want to replace an erroneous file name by
2764 			// a valid one, and we disable stripping of later
2765 			// components.
2766 			saved_char = *tail;
2767 			*tail = NUL;
2768 			if (mch_stat((char *)filename, &st) >= 0)
2769 			    do_strip = TRUE;
2770 			else
2771 			    stripping_disabled = TRUE;
2772 			*tail = saved_char;
2773 # ifdef UNIX
2774 			if (do_strip)
2775 			{
2776 			    stat_T	new_st;
2777 
2778 			    // On Unix, the check for the unstripped file name
2779 			    // above works also for a symbolic link pointing to
2780 			    // a searchable directory.  But then the parent of
2781 			    // the directory pointed to by the link must be the
2782 			    // same as the stripped file name.  (The latter
2783 			    // exists in the file system since it is the
2784 			    // component's parent directory.)
2785 			    if (p == start && relative)
2786 				(void)mch_stat(".", &new_st);
2787 			    else
2788 			    {
2789 				saved_char = *p;
2790 				*p = NUL;
2791 				(void)mch_stat((char *)filename, &new_st);
2792 				*p = saved_char;
2793 			    }
2794 
2795 			    if (new_st.st_ino != st.st_ino ||
2796 				new_st.st_dev != st.st_dev)
2797 			    {
2798 				do_strip = FALSE;
2799 				// We don't disable stripping of later
2800 				// components since the unstripped path name is
2801 				// still valid.
2802 			    }
2803 			}
2804 # endif
2805 		    }
2806 		}
2807 
2808 		if (!do_strip)
2809 		{
2810 		    // Skip the ".." or "../" and reset the counter for the
2811 		    // components that might be stripped later on.
2812 		    p = tail;
2813 		    components = 0;
2814 		}
2815 		else
2816 		{
2817 		    // Strip previous component.  If the result would get empty
2818 		    // and there is no trailing path separator, leave a single
2819 		    // "." instead.  If we are at the end of the file name and
2820 		    // there is no trailing path separator and a preceding
2821 		    // component is left after stripping, strip its trailing
2822 		    // path separator as well.
2823 		    if (p == start && relative && tail[-1] == '.')
2824 		    {
2825 			*p++ = '.';
2826 			*p = NUL;
2827 		    }
2828 		    else
2829 		    {
2830 			if (p > start && tail[-1] == '.')
2831 			    --p;
2832 			STRMOVE(p, tail);	// strip previous component
2833 		    }
2834 
2835 		    --components;
2836 		}
2837 	    }
2838 	    else if (p == start && !relative)	// leading "/.." or "/../"
2839 		STRMOVE(p, tail);		// strip ".." or "../"
2840 	    else
2841 	    {
2842 		if (p == start + 2 && p[-2] == '.')	// leading "./../"
2843 		{
2844 		    STRMOVE(p - 2, p);			// strip leading "./"
2845 		    tail -= 2;
2846 		}
2847 		p = tail;		// skip to char after ".." or "../"
2848 	    }
2849 	}
2850 	else
2851 	{
2852 	    ++components;		// simple path component
2853 	    p = getnextcomp(p);
2854 	}
2855     } while (*p != NUL);
2856 #endif // !AMIGA
2857 }
2858 
2859 #if defined(FEAT_EVAL) || defined(PROTO)
2860 /*
2861  * "simplify()" function
2862  */
2863     void
f_simplify(typval_T * argvars,typval_T * rettv)2864 f_simplify(typval_T *argvars, typval_T *rettv)
2865 {
2866     char_u	*p;
2867 
2868     if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
2869 	return;
2870 
2871     p = tv_get_string_strict(&argvars[0]);
2872     rettv->vval.v_string = vim_strsave(p);
2873     simplify_filename(rettv->vval.v_string);	// simplify in place
2874     rettv->v_type = VAR_STRING;
2875 }
2876 #endif // FEAT_EVAL
2877