blob: 4517f412ce6aa3e15876577e406787ed5d592567 [file] [log] [blame]
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001/* 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/*
Bram Moolenaar9810cfb2019-12-11 21:23:00 +010011 * filepath.c: dealing with file names and paths.
Bram Moolenaarb005cd82019-09-04 15:54:55 +020012 */
13
14#include "vim.h"
15
16#ifdef MSWIN
17/*
18 * Functions for ":8" filename modifier: get 8.3 version of a filename.
19 */
20
21/*
22 * Get the short path (8.3) for the filename in "fnamep".
23 * Only works for a valid file name.
24 * When the path gets longer "fnamep" is changed and the allocated buffer
25 * is put in "bufp".
26 * *fnamelen is the length of "fnamep" and set to 0 for a nonexistent path.
27 * Returns OK on success, FAIL on failure.
28 */
29 static int
30get_short_pathname(char_u **fnamep, char_u **bufp, int *fnamelen)
31{
32 int l, len;
Bram Moolenaar3f396972019-10-30 04:10:06 +010033 WCHAR *newbuf;
34 WCHAR *wfname;
Bram Moolenaarb005cd82019-09-04 15:54:55 +020035
Bram Moolenaar3f396972019-10-30 04:10:06 +010036 len = MAXPATHL;
37 newbuf = malloc(len * sizeof(*newbuf));
38 if (newbuf == NULL)
39 return FAIL;
40
41 wfname = enc_to_utf16(*fnamep, NULL);
42 if (wfname == NULL)
43 {
44 vim_free(newbuf);
45 return FAIL;
46 }
47
48 l = GetShortPathNameW(wfname, newbuf, len);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020049 if (l > len - 1)
50 {
Bram Moolenaar26262f82019-09-04 20:59:15 +020051 // If that doesn't work (not enough space), then save the string
52 // and try again with a new buffer big enough.
Bram Moolenaar3f396972019-10-30 04:10:06 +010053 WCHAR *newbuf_t = newbuf;
54 newbuf = vim_realloc(newbuf, (l + 1) * sizeof(*newbuf));
Bram Moolenaarb005cd82019-09-04 15:54:55 +020055 if (newbuf == NULL)
Bram Moolenaar3f396972019-10-30 04:10:06 +010056 {
57 vim_free(wfname);
58 vim_free(newbuf_t);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020059 return FAIL;
Bram Moolenaar3f396972019-10-30 04:10:06 +010060 }
Bram Moolenaar26262f82019-09-04 20:59:15 +020061 // Really should always succeed, as the buffer is big enough.
Bram Moolenaar3f396972019-10-30 04:10:06 +010062 l = GetShortPathNameW(wfname, newbuf, l+1);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020063 }
Bram Moolenaar3f396972019-10-30 04:10:06 +010064 if (l != 0)
65 {
66 char_u *p = utf16_to_enc(newbuf, NULL);
67 if (p != NULL)
68 {
69 vim_free(*bufp);
70 *fnamep = *bufp = p;
71 }
72 else
73 {
74 vim_free(wfname);
75 vim_free(newbuf);
76 return FAIL;
77 }
78 }
79 vim_free(wfname);
80 vim_free(newbuf);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020081
Bram Moolenaar2ade7142019-11-04 20:36:50 +010082 *fnamelen = l == 0 ? l : (int)STRLEN(*bufp);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020083 return OK;
84}
85
86/*
87 * Get the short path (8.3) for the filename in "fname". The converted
88 * path is returned in "bufp".
89 *
90 * Some of the directories specified in "fname" may not exist. This function
91 * will shorten the existing directories at the beginning of the path and then
92 * append the remaining non-existing path.
93 *
94 * fname - Pointer to the filename to shorten. On return, contains the
95 * pointer to the shortened pathname
96 * bufp - Pointer to an allocated buffer for the filename.
97 * fnamelen - Length of the filename pointed to by fname
98 *
99 * Returns OK on success (or nothing done) and FAIL on failure (out of memory).
100 */
101 static int
102shortpath_for_invalid_fname(
103 char_u **fname,
104 char_u **bufp,
105 int *fnamelen)
106{
107 char_u *short_fname, *save_fname, *pbuf_unused;
108 char_u *endp, *save_endp;
109 char_u ch;
110 int old_len, len;
111 int new_len, sfx_len;
112 int retval = OK;
113
Bram Moolenaar26262f82019-09-04 20:59:15 +0200114 // Make a copy
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200115 old_len = *fnamelen;
116 save_fname = vim_strnsave(*fname, old_len);
117 pbuf_unused = NULL;
118 short_fname = NULL;
119
Bram Moolenaar26262f82019-09-04 20:59:15 +0200120 endp = save_fname + old_len - 1; // Find the end of the copy
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200121 save_endp = endp;
122
123 /*
124 * Try shortening the supplied path till it succeeds by removing one
125 * directory at a time from the tail of the path.
126 */
127 len = 0;
128 for (;;)
129 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200130 // go back one path-separator
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200131 while (endp > save_fname && !after_pathsep(save_fname, endp + 1))
132 --endp;
133 if (endp <= save_fname)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200134 break; // processed the complete path
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200135
136 /*
137 * Replace the path separator with a NUL and try to shorten the
138 * resulting path.
139 */
140 ch = *endp;
141 *endp = 0;
142 short_fname = save_fname;
143 len = (int)STRLEN(short_fname) + 1;
144 if (get_short_pathname(&short_fname, &pbuf_unused, &len) == FAIL)
145 {
146 retval = FAIL;
147 goto theend;
148 }
Bram Moolenaar26262f82019-09-04 20:59:15 +0200149 *endp = ch; // preserve the string
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200150
151 if (len > 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200152 break; // successfully shortened the path
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200153
Bram Moolenaar26262f82019-09-04 20:59:15 +0200154 // failed to shorten the path. Skip the path separator
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200155 --endp;
156 }
157
158 if (len > 0)
159 {
160 /*
161 * Succeeded in shortening the path. Now concatenate the shortened
162 * path with the remaining path at the tail.
163 */
164
Bram Moolenaar217e1b82019-12-01 21:41:28 +0100165 // Compute the length of the new path.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200166 sfx_len = (int)(save_endp - endp) + 1;
167 new_len = len + sfx_len;
168
169 *fnamelen = new_len;
170 vim_free(*bufp);
171 if (new_len > old_len)
172 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200173 // There is not enough space in the currently allocated string,
174 // copy it to a buffer big enough.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200175 *fname = *bufp = vim_strnsave(short_fname, new_len);
176 if (*fname == NULL)
177 {
178 retval = FAIL;
179 goto theend;
180 }
181 }
182 else
183 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200184 // Transfer short_fname to the main buffer (it's big enough),
185 // unless get_short_pathname() did its work in-place.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200186 *fname = *bufp = save_fname;
187 if (short_fname != save_fname)
188 vim_strncpy(save_fname, short_fname, len);
189 save_fname = NULL;
190 }
191
Bram Moolenaar26262f82019-09-04 20:59:15 +0200192 // concat the not-shortened part of the path
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200193 vim_strncpy(*fname + len, endp, sfx_len);
194 (*fname)[new_len] = NUL;
195 }
196
197theend:
198 vim_free(pbuf_unused);
199 vim_free(save_fname);
200
201 return retval;
202}
203
204/*
205 * Get a pathname for a partial path.
206 * Returns OK for success, FAIL for failure.
207 */
208 static int
209shortpath_for_partial(
210 char_u **fnamep,
211 char_u **bufp,
212 int *fnamelen)
213{
214 int sepcount, len, tflen;
215 char_u *p;
216 char_u *pbuf, *tfname;
217 int hasTilde;
218
Bram Moolenaar26262f82019-09-04 20:59:15 +0200219 // Count up the path separators from the RHS.. so we know which part
220 // of the path to return.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200221 sepcount = 0;
222 for (p = *fnamep; p < *fnamep + *fnamelen; MB_PTR_ADV(p))
223 if (vim_ispathsep(*p))
224 ++sepcount;
225
Bram Moolenaar26262f82019-09-04 20:59:15 +0200226 // Need full path first (use expand_env() to remove a "~/")
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200227 hasTilde = (**fnamep == '~');
228 if (hasTilde)
229 pbuf = tfname = expand_env_save(*fnamep);
230 else
231 pbuf = tfname = FullName_save(*fnamep, FALSE);
232
233 len = tflen = (int)STRLEN(tfname);
234
235 if (get_short_pathname(&tfname, &pbuf, &len) == FAIL)
236 return FAIL;
237
238 if (len == 0)
239 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200240 // Don't have a valid filename, so shorten the rest of the
241 // path if we can. This CAN give us invalid 8.3 filenames, but
242 // there's not a lot of point in guessing what it might be.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200243 len = tflen;
244 if (shortpath_for_invalid_fname(&tfname, &pbuf, &len) == FAIL)
245 return FAIL;
246 }
247
Bram Moolenaar26262f82019-09-04 20:59:15 +0200248 // Count the paths backward to find the beginning of the desired string.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200249 for (p = tfname + len - 1; p >= tfname; --p)
250 {
251 if (has_mbyte)
252 p -= mb_head_off(tfname, p);
253 if (vim_ispathsep(*p))
254 {
255 if (sepcount == 0 || (hasTilde && sepcount == 1))
256 break;
257 else
258 sepcount --;
259 }
260 }
261 if (hasTilde)
262 {
263 --p;
264 if (p >= tfname)
265 *p = '~';
266 else
267 return FAIL;
268 }
269 else
270 ++p;
271
Bram Moolenaar26262f82019-09-04 20:59:15 +0200272 // Copy in the string - p indexes into tfname - allocated at pbuf
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200273 vim_free(*bufp);
274 *fnamelen = (int)STRLEN(p);
275 *bufp = pbuf;
276 *fnamep = p;
277
278 return OK;
279}
280#endif // MSWIN
281
282/*
283 * Adjust a filename, according to a string of modifiers.
284 * *fnamep must be NUL terminated when called. When returning, the length is
285 * determined by *fnamelen.
286 * Returns VALID_ flags or -1 for failure.
287 * When there is an error, *fnamep is set to NULL.
288 */
289 int
290modify_fname(
291 char_u *src, // string with modifiers
292 int tilde_file, // "~" is a file name, not $HOME
293 int *usedlen, // characters after src that are used
294 char_u **fnamep, // file name so far
295 char_u **bufp, // buffer for allocated file name or NULL
296 int *fnamelen) // length of fnamep
297{
298 int valid = 0;
299 char_u *tail;
300 char_u *s, *p, *pbuf;
301 char_u dirname[MAXPATHL];
302 int c;
303 int has_fullname = 0;
Bram Moolenaard816cd92020-02-04 22:23:09 +0100304 int has_homerelative = 0;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200305#ifdef MSWIN
306 char_u *fname_start = *fnamep;
307 int has_shortname = 0;
308#endif
309
310repeat:
Bram Moolenaar26262f82019-09-04 20:59:15 +0200311 // ":p" - full path/file_name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200312 if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p')
313 {
314 has_fullname = 1;
315
316 valid |= VALID_PATH;
317 *usedlen += 2;
318
Bram Moolenaar26262f82019-09-04 20:59:15 +0200319 // Expand "~/path" for all systems and "~user/path" for Unix and VMS
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200320 if ((*fnamep)[0] == '~'
321#if !defined(UNIX) && !(defined(VMS) && defined(USER_HOME))
322 && ((*fnamep)[1] == '/'
323# ifdef BACKSLASH_IN_FILENAME
324 || (*fnamep)[1] == '\\'
325# endif
326 || (*fnamep)[1] == NUL)
327#endif
328 && !(tilde_file && (*fnamep)[1] == NUL)
329 )
330 {
331 *fnamep = expand_env_save(*fnamep);
Bram Moolenaar26262f82019-09-04 20:59:15 +0200332 vim_free(*bufp); // free any allocated file name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200333 *bufp = *fnamep;
334 if (*fnamep == NULL)
335 return -1;
336 }
337
Bram Moolenaar26262f82019-09-04 20:59:15 +0200338 // When "/." or "/.." is used: force expansion to get rid of it.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200339 for (p = *fnamep; *p != NUL; MB_PTR_ADV(p))
340 {
341 if (vim_ispathsep(*p)
342 && p[1] == '.'
343 && (p[2] == NUL
344 || vim_ispathsep(p[2])
345 || (p[2] == '.'
346 && (p[3] == NUL || vim_ispathsep(p[3])))))
347 break;
348 }
349
Bram Moolenaar26262f82019-09-04 20:59:15 +0200350 // FullName_save() is slow, don't use it when not needed.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200351 if (*p != NUL || !vim_isAbsName(*fnamep))
352 {
353 *fnamep = FullName_save(*fnamep, *p != NUL);
Bram Moolenaar26262f82019-09-04 20:59:15 +0200354 vim_free(*bufp); // free any allocated file name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200355 *bufp = *fnamep;
356 if (*fnamep == NULL)
357 return -1;
358 }
359
360#ifdef MSWIN
361# if _WIN32_WINNT >= 0x0500
362 if (vim_strchr(*fnamep, '~') != NULL)
363 {
364 // Expand 8.3 filename to full path. Needed to make sure the same
365 // file does not have two different names.
366 // Note: problem does not occur if _WIN32_WINNT < 0x0500.
367 WCHAR *wfname = enc_to_utf16(*fnamep, NULL);
368 WCHAR buf[_MAX_PATH];
369
370 if (wfname != NULL)
371 {
372 if (GetLongPathNameW(wfname, buf, _MAX_PATH))
373 {
374 char_u *p = utf16_to_enc(buf, NULL);
375
376 if (p != NULL)
377 {
378 vim_free(*bufp); // free any allocated file name
379 *bufp = *fnamep = p;
380 }
381 }
382 vim_free(wfname);
383 }
384 }
385# endif
386#endif
Bram Moolenaar26262f82019-09-04 20:59:15 +0200387 // Append a path separator to a directory.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200388 if (mch_isdir(*fnamep))
389 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200390 // Make room for one or two extra characters.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200391 *fnamep = vim_strnsave(*fnamep, (int)STRLEN(*fnamep) + 2);
Bram Moolenaar26262f82019-09-04 20:59:15 +0200392 vim_free(*bufp); // free any allocated file name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200393 *bufp = *fnamep;
394 if (*fnamep == NULL)
395 return -1;
396 add_pathsep(*fnamep);
397 }
398 }
399
Bram Moolenaar26262f82019-09-04 20:59:15 +0200400 // ":." - path relative to the current directory
401 // ":~" - path relative to the home directory
402 // ":8" - shortname path - postponed till after
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200403 while (src[*usedlen] == ':'
404 && ((c = src[*usedlen + 1]) == '.' || c == '~' || c == '8'))
405 {
406 *usedlen += 2;
407 if (c == '8')
408 {
409#ifdef MSWIN
Bram Moolenaar26262f82019-09-04 20:59:15 +0200410 has_shortname = 1; // Postpone this.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200411#endif
412 continue;
413 }
414 pbuf = NULL;
Bram Moolenaar26262f82019-09-04 20:59:15 +0200415 // Need full path first (use expand_env() to remove a "~/")
Bram Moolenaard816cd92020-02-04 22:23:09 +0100416 if (!has_fullname && !has_homerelative)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200417 {
418 if (c == '.' && **fnamep == '~')
419 p = pbuf = expand_env_save(*fnamep);
420 else
421 p = pbuf = FullName_save(*fnamep, FALSE);
422 }
423 else
424 p = *fnamep;
425
426 has_fullname = 0;
427
428 if (p != NULL)
429 {
430 if (c == '.')
431 {
Bram Moolenaard816cd92020-02-04 22:23:09 +0100432 size_t namelen;
433
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200434 mch_dirname(dirname, MAXPATHL);
Bram Moolenaard816cd92020-02-04 22:23:09 +0100435 if (has_homerelative)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200436 {
Bram Moolenaard816cd92020-02-04 22:23:09 +0100437 s = vim_strsave(dirname);
438 if (s != NULL)
439 {
440 home_replace(NULL, s, dirname, MAXPATHL, TRUE);
441 vim_free(s);
442 }
443 }
444 namelen = STRLEN(dirname);
445
446 // Do not call shorten_fname() here since it removes the prefix
447 // even though the path does not have a prefix.
448 if (fnamencmp(p, dirname, namelen) == 0)
449 {
450 p += namelen;
451 while (*p && vim_ispathsep(*p))
452 ++p;
453 *fnamep = p;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200454 if (pbuf != NULL)
455 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200456 vim_free(*bufp); // free any allocated file name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200457 *bufp = pbuf;
458 pbuf = NULL;
459 }
460 }
461 }
462 else
463 {
464 home_replace(NULL, p, dirname, MAXPATHL, TRUE);
Bram Moolenaar26262f82019-09-04 20:59:15 +0200465 // Only replace it when it starts with '~'
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200466 if (*dirname == '~')
467 {
468 s = vim_strsave(dirname);
469 if (s != NULL)
470 {
471 *fnamep = s;
472 vim_free(*bufp);
473 *bufp = s;
Bram Moolenaard816cd92020-02-04 22:23:09 +0100474 has_homerelative = TRUE;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200475 }
476 }
477 }
478 vim_free(pbuf);
479 }
480 }
481
482 tail = gettail(*fnamep);
483 *fnamelen = (int)STRLEN(*fnamep);
484
Bram Moolenaar26262f82019-09-04 20:59:15 +0200485 // ":h" - head, remove "/file_name", can be repeated
486 // Don't remove the first "/" or "c:\"
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200487 while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h')
488 {
489 valid |= VALID_HEAD;
490 *usedlen += 2;
491 s = get_past_head(*fnamep);
492 while (tail > s && after_pathsep(s, tail))
493 MB_PTR_BACK(*fnamep, tail);
494 *fnamelen = (int)(tail - *fnamep);
495#ifdef VMS
496 if (*fnamelen > 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200497 *fnamelen += 1; // the path separator is part of the path
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200498#endif
499 if (*fnamelen == 0)
500 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200501 // Result is empty. Turn it into "." to make ":cd %:h" work.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200502 p = vim_strsave((char_u *)".");
503 if (p == NULL)
504 return -1;
505 vim_free(*bufp);
506 *bufp = *fnamep = tail = p;
507 *fnamelen = 1;
508 }
509 else
510 {
511 while (tail > s && !after_pathsep(s, tail))
512 MB_PTR_BACK(*fnamep, tail);
513 }
514 }
515
Bram Moolenaar26262f82019-09-04 20:59:15 +0200516 // ":8" - shortname
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200517 if (src[*usedlen] == ':' && src[*usedlen + 1] == '8')
518 {
519 *usedlen += 2;
520#ifdef MSWIN
521 has_shortname = 1;
522#endif
523 }
524
525#ifdef MSWIN
526 /*
527 * Handle ":8" after we have done 'heads' and before we do 'tails'.
528 */
529 if (has_shortname)
530 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200531 // Copy the string if it is shortened by :h and when it wasn't copied
532 // yet, because we are going to change it in place. Avoids changing
533 // the buffer name for "%:8".
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200534 if (*fnamelen < (int)STRLEN(*fnamep) || *fnamep == fname_start)
535 {
536 p = vim_strnsave(*fnamep, *fnamelen);
537 if (p == NULL)
538 return -1;
539 vim_free(*bufp);
540 *bufp = *fnamep = p;
541 }
542
Bram Moolenaar26262f82019-09-04 20:59:15 +0200543 // Split into two implementations - makes it easier. First is where
544 // there isn't a full name already, second is where there is.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200545 if (!has_fullname && !vim_isAbsName(*fnamep))
546 {
547 if (shortpath_for_partial(fnamep, bufp, fnamelen) == FAIL)
548 return -1;
549 }
550 else
551 {
552 int l = *fnamelen;
553
Bram Moolenaar26262f82019-09-04 20:59:15 +0200554 // Simple case, already have the full-name.
555 // Nearly always shorter, so try first time.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200556 if (get_short_pathname(fnamep, bufp, &l) == FAIL)
557 return -1;
558
559 if (l == 0)
560 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200561 // Couldn't find the filename, search the paths.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200562 l = *fnamelen;
563 if (shortpath_for_invalid_fname(fnamep, bufp, &l) == FAIL)
564 return -1;
565 }
566 *fnamelen = l;
567 }
568 }
569#endif // MSWIN
570
Bram Moolenaar26262f82019-09-04 20:59:15 +0200571 // ":t" - tail, just the basename
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200572 if (src[*usedlen] == ':' && src[*usedlen + 1] == 't')
573 {
574 *usedlen += 2;
575 *fnamelen -= (int)(tail - *fnamep);
576 *fnamep = tail;
577 }
578
Bram Moolenaar26262f82019-09-04 20:59:15 +0200579 // ":e" - extension, can be repeated
580 // ":r" - root, without extension, can be repeated
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200581 while (src[*usedlen] == ':'
582 && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r'))
583 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200584 // find a '.' in the tail:
585 // - for second :e: before the current fname
586 // - otherwise: The last '.'
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200587 if (src[*usedlen + 1] == 'e' && *fnamep > tail)
588 s = *fnamep - 2;
589 else
590 s = *fnamep + *fnamelen - 1;
591 for ( ; s > tail; --s)
592 if (s[0] == '.')
593 break;
Bram Moolenaar26262f82019-09-04 20:59:15 +0200594 if (src[*usedlen + 1] == 'e') // :e
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200595 {
596 if (s > tail)
597 {
598 *fnamelen += (int)(*fnamep - (s + 1));
599 *fnamep = s + 1;
600#ifdef VMS
Bram Moolenaar26262f82019-09-04 20:59:15 +0200601 // cut version from the extension
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200602 s = *fnamep + *fnamelen - 1;
603 for ( ; s > *fnamep; --s)
604 if (s[0] == ';')
605 break;
606 if (s > *fnamep)
607 *fnamelen = s - *fnamep;
608#endif
609 }
610 else if (*fnamep <= tail)
611 *fnamelen = 0;
612 }
Bram Moolenaar26262f82019-09-04 20:59:15 +0200613 else // :r
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200614 {
Bram Moolenaarb1892952019-10-08 23:26:50 +0200615 char_u *limit = *fnamep;
616
617 if (limit < tail)
618 limit = tail;
619 if (s > limit) // remove one extension
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200620 *fnamelen = (int)(s - *fnamep);
621 }
622 *usedlen += 2;
623 }
624
Bram Moolenaar26262f82019-09-04 20:59:15 +0200625 // ":s?pat?foo?" - substitute
626 // ":gs?pat?foo?" - global substitute
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200627 if (src[*usedlen] == ':'
628 && (src[*usedlen + 1] == 's'
629 || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's')))
630 {
631 char_u *str;
632 char_u *pat;
633 char_u *sub;
634 int sep;
635 char_u *flags;
636 int didit = FALSE;
637
638 flags = (char_u *)"";
639 s = src + *usedlen + 2;
640 if (src[*usedlen + 1] == 'g')
641 {
642 flags = (char_u *)"g";
643 ++s;
644 }
645
646 sep = *s++;
647 if (sep)
648 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200649 // find end of pattern
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200650 p = vim_strchr(s, sep);
651 if (p != NULL)
652 {
653 pat = vim_strnsave(s, (int)(p - s));
654 if (pat != NULL)
655 {
656 s = p + 1;
Bram Moolenaar26262f82019-09-04 20:59:15 +0200657 // find end of substitution
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200658 p = vim_strchr(s, sep);
659 if (p != NULL)
660 {
661 sub = vim_strnsave(s, (int)(p - s));
662 str = vim_strnsave(*fnamep, *fnamelen);
663 if (sub != NULL && str != NULL)
664 {
665 *usedlen = (int)(p + 1 - src);
666 s = do_string_sub(str, pat, sub, NULL, flags);
667 if (s != NULL)
668 {
669 *fnamep = s;
670 *fnamelen = (int)STRLEN(s);
671 vim_free(*bufp);
672 *bufp = s;
673 didit = TRUE;
674 }
675 }
676 vim_free(sub);
677 vim_free(str);
678 }
679 vim_free(pat);
680 }
681 }
Bram Moolenaar26262f82019-09-04 20:59:15 +0200682 // after using ":s", repeat all the modifiers
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200683 if (didit)
684 goto repeat;
685 }
686 }
687
688 if (src[*usedlen] == ':' && src[*usedlen + 1] == 'S')
689 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200690 // vim_strsave_shellescape() needs a NUL terminated string.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200691 c = (*fnamep)[*fnamelen];
692 if (c != NUL)
693 (*fnamep)[*fnamelen] = NUL;
694 p = vim_strsave_shellescape(*fnamep, FALSE, FALSE);
695 if (c != NUL)
696 (*fnamep)[*fnamelen] = c;
697 if (p == NULL)
698 return -1;
699 vim_free(*bufp);
700 *bufp = *fnamep = p;
701 *fnamelen = (int)STRLEN(p);
702 *usedlen += 2;
703 }
704
705 return valid;
706}
707
708#if defined(FEAT_EVAL) || defined(PROTO)
709
710/*
711 * "chdir(dir)" function
712 */
713 void
714f_chdir(typval_T *argvars, typval_T *rettv)
715{
716 char_u *cwd;
717 cdscope_T scope = CDSCOPE_GLOBAL;
718
719 rettv->v_type = VAR_STRING;
720 rettv->vval.v_string = NULL;
721
722 if (argvars[0].v_type != VAR_STRING)
Bram Moolenaard816cd92020-02-04 22:23:09 +0100723 // Returning an empty string means it failed.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200724 return;
725
726 // Return the current directory
727 cwd = alloc(MAXPATHL);
728 if (cwd != NULL)
729 {
730 if (mch_dirname(cwd, MAXPATHL) != FAIL)
731 {
732#ifdef BACKSLASH_IN_FILENAME
733 slash_adjust(cwd);
734#endif
735 rettv->vval.v_string = vim_strsave(cwd);
736 }
737 vim_free(cwd);
738 }
739
740 if (curwin->w_localdir != NULL)
741 scope = CDSCOPE_WINDOW;
742 else if (curtab->tp_localdir != NULL)
743 scope = CDSCOPE_TABPAGE;
744
745 if (!changedir_func(argvars[0].vval.v_string, TRUE, scope))
746 // Directory change failed
747 VIM_CLEAR(rettv->vval.v_string);
748}
749
750/*
751 * "delete()" function
752 */
753 void
754f_delete(typval_T *argvars, typval_T *rettv)
755{
756 char_u nbuf[NUMBUFLEN];
757 char_u *name;
758 char_u *flags;
759
760 rettv->vval.v_number = -1;
761 if (check_restricted() || check_secure())
762 return;
763
764 name = tv_get_string(&argvars[0]);
765 if (name == NULL || *name == NUL)
766 {
767 emsg(_(e_invarg));
768 return;
769 }
770
771 if (argvars[1].v_type != VAR_UNKNOWN)
772 flags = tv_get_string_buf(&argvars[1], nbuf);
773 else
774 flags = (char_u *)"";
775
776 if (*flags == NUL)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200777 // delete a file
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200778 rettv->vval.v_number = mch_remove(name) == 0 ? 0 : -1;
779 else if (STRCMP(flags, "d") == 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200780 // delete an empty directory
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200781 rettv->vval.v_number = mch_rmdir(name) == 0 ? 0 : -1;
782 else if (STRCMP(flags, "rf") == 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200783 // delete a directory recursively
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200784 rettv->vval.v_number = delete_recursive(name);
785 else
786 semsg(_(e_invexpr2), flags);
787}
788
789/*
790 * "executable()" function
791 */
792 void
793f_executable(typval_T *argvars, typval_T *rettv)
794{
795 char_u *name = tv_get_string(&argvars[0]);
796
Bram Moolenaar26262f82019-09-04 20:59:15 +0200797 // Check in $PATH and also check directly if there is a directory name.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200798 rettv->vval.v_number = mch_can_exe(name, NULL, TRUE);
799}
800
801/*
802 * "exepath()" function
803 */
804 void
805f_exepath(typval_T *argvars, typval_T *rettv)
806{
807 char_u *p = NULL;
808
809 (void)mch_can_exe(tv_get_string(&argvars[0]), &p, TRUE);
810 rettv->v_type = VAR_STRING;
811 rettv->vval.v_string = p;
812}
813
814/*
815 * "filereadable()" function
816 */
817 void
818f_filereadable(typval_T *argvars, typval_T *rettv)
819{
820 int fd;
821 char_u *p;
822 int n;
823
824#ifndef O_NONBLOCK
825# define O_NONBLOCK 0
826#endif
827 p = tv_get_string(&argvars[0]);
828 if (*p && !mch_isdir(p) && (fd = mch_open((char *)p,
829 O_RDONLY | O_NONBLOCK, 0)) >= 0)
830 {
831 n = TRUE;
832 close(fd);
833 }
834 else
835 n = FALSE;
836
837 rettv->vval.v_number = n;
838}
839
840/*
841 * Return 0 for not writable, 1 for writable file, 2 for a dir which we have
842 * rights to write into.
843 */
844 void
845f_filewritable(typval_T *argvars, typval_T *rettv)
846{
847 rettv->vval.v_number = filewritable(tv_get_string(&argvars[0]));
848}
849
Bram Moolenaar840d16f2019-09-10 21:27:18 +0200850 static void
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200851findfilendir(
852 typval_T *argvars UNUSED,
853 typval_T *rettv,
854 int find_what UNUSED)
855{
856#ifdef FEAT_SEARCHPATH
857 char_u *fname;
858 char_u *fresult = NULL;
859 char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
860 char_u *p;
861 char_u pathbuf[NUMBUFLEN];
862 int count = 1;
863 int first = TRUE;
864 int error = FALSE;
865#endif
866
867 rettv->vval.v_string = NULL;
868 rettv->v_type = VAR_STRING;
869
870#ifdef FEAT_SEARCHPATH
871 fname = tv_get_string(&argvars[0]);
872
873 if (argvars[1].v_type != VAR_UNKNOWN)
874 {
875 p = tv_get_string_buf_chk(&argvars[1], pathbuf);
876 if (p == NULL)
877 error = TRUE;
878 else
879 {
880 if (*p != NUL)
881 path = p;
882
883 if (argvars[2].v_type != VAR_UNKNOWN)
884 count = (int)tv_get_number_chk(&argvars[2], &error);
885 }
886 }
887
888 if (count < 0 && rettv_list_alloc(rettv) == FAIL)
889 error = TRUE;
890
891 if (*fname != NUL && !error)
892 {
893 do
894 {
895 if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST)
896 vim_free(fresult);
897 fresult = find_file_in_path_option(first ? fname : NULL,
898 first ? (int)STRLEN(fname) : 0,
899 0, first, path,
900 find_what,
901 curbuf->b_ffname,
902 find_what == FINDFILE_DIR
903 ? (char_u *)"" : curbuf->b_p_sua);
904 first = FALSE;
905
906 if (fresult != NULL && rettv->v_type == VAR_LIST)
907 list_append_string(rettv->vval.v_list, fresult, -1);
908
909 } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
910 }
911
912 if (rettv->v_type == VAR_STRING)
913 rettv->vval.v_string = fresult;
914#endif
915}
916
917/*
918 * "finddir({fname}[, {path}[, {count}]])" function
919 */
920 void
921f_finddir(typval_T *argvars, typval_T *rettv)
922{
923 findfilendir(argvars, rettv, FINDFILE_DIR);
924}
925
926/*
927 * "findfile({fname}[, {path}[, {count}]])" function
928 */
929 void
930f_findfile(typval_T *argvars, typval_T *rettv)
931{
932 findfilendir(argvars, rettv, FINDFILE_FILE);
933}
934
935/*
936 * "fnamemodify({fname}, {mods})" function
937 */
938 void
939f_fnamemodify(typval_T *argvars, typval_T *rettv)
940{
941 char_u *fname;
942 char_u *mods;
943 int usedlen = 0;
944 int len;
945 char_u *fbuf = NULL;
946 char_u buf[NUMBUFLEN];
947
948 fname = tv_get_string_chk(&argvars[0]);
949 mods = tv_get_string_buf_chk(&argvars[1], buf);
950 if (fname == NULL || mods == NULL)
951 fname = NULL;
952 else
953 {
954 len = (int)STRLEN(fname);
955 (void)modify_fname(mods, FALSE, &usedlen, &fname, &fbuf, &len);
956 }
957
958 rettv->v_type = VAR_STRING;
959 if (fname == NULL)
960 rettv->vval.v_string = NULL;
961 else
962 rettv->vval.v_string = vim_strnsave(fname, len);
963 vim_free(fbuf);
964}
965
966/*
967 * "getcwd()" function
968 *
969 * Return the current working directory of a window in a tab page.
970 * First optional argument 'winnr' is the window number or -1 and the second
971 * optional argument 'tabnr' is the tab page number.
972 *
973 * If no arguments are supplied, then return the directory of the current
974 * window.
975 * If only 'winnr' is specified and is not -1 or 0 then return the directory of
976 * the specified window.
977 * If 'winnr' is 0 then return the directory of the current window.
978 * If both 'winnr and 'tabnr' are specified and 'winnr' is -1 then return the
979 * directory of the specified tab page. Otherwise return the directory of the
980 * specified window in the specified tab page.
981 * If the window or the tab page doesn't exist then return NULL.
982 */
983 void
984f_getcwd(typval_T *argvars, typval_T *rettv)
985{
986 win_T *wp = NULL;
987 tabpage_T *tp = NULL;
988 char_u *cwd;
989 int global = FALSE;
990
991 rettv->v_type = VAR_STRING;
992 rettv->vval.v_string = NULL;
993
994 if (argvars[0].v_type == VAR_NUMBER
995 && argvars[0].vval.v_number == -1
996 && argvars[1].v_type == VAR_UNKNOWN)
997 global = TRUE;
998 else
999 wp = find_tabwin(&argvars[0], &argvars[1], &tp);
1000
1001 if (wp != NULL && wp->w_localdir != NULL)
1002 rettv->vval.v_string = vim_strsave(wp->w_localdir);
1003 else if (tp != NULL && tp->tp_localdir != NULL)
1004 rettv->vval.v_string = vim_strsave(tp->tp_localdir);
1005 else if (wp != NULL || tp != NULL || global)
1006 {
1007 if (globaldir != NULL)
1008 rettv->vval.v_string = vim_strsave(globaldir);
1009 else
1010 {
1011 cwd = alloc(MAXPATHL);
1012 if (cwd != NULL)
1013 {
1014 if (mch_dirname(cwd, MAXPATHL) != FAIL)
1015 rettv->vval.v_string = vim_strsave(cwd);
1016 vim_free(cwd);
1017 }
1018 }
1019 }
1020#ifdef BACKSLASH_IN_FILENAME
1021 if (rettv->vval.v_string != NULL)
1022 slash_adjust(rettv->vval.v_string);
1023#endif
1024}
1025
1026/*
1027 * "getfperm({fname})" function
1028 */
1029 void
1030f_getfperm(typval_T *argvars, typval_T *rettv)
1031{
1032 char_u *fname;
1033 stat_T st;
1034 char_u *perm = NULL;
1035 char_u flags[] = "rwx";
1036 int i;
1037
1038 fname = tv_get_string(&argvars[0]);
1039
1040 rettv->v_type = VAR_STRING;
1041 if (mch_stat((char *)fname, &st) >= 0)
1042 {
1043 perm = vim_strsave((char_u *)"---------");
1044 if (perm != NULL)
1045 {
1046 for (i = 0; i < 9; i++)
1047 {
1048 if (st.st_mode & (1 << (8 - i)))
1049 perm[i] = flags[i % 3];
1050 }
1051 }
1052 }
1053 rettv->vval.v_string = perm;
1054}
1055
1056/*
1057 * "getfsize({fname})" function
1058 */
1059 void
1060f_getfsize(typval_T *argvars, typval_T *rettv)
1061{
1062 char_u *fname;
1063 stat_T st;
1064
1065 fname = tv_get_string(&argvars[0]);
1066
1067 rettv->v_type = VAR_NUMBER;
1068
1069 if (mch_stat((char *)fname, &st) >= 0)
1070 {
1071 if (mch_isdir(fname))
1072 rettv->vval.v_number = 0;
1073 else
1074 {
1075 rettv->vval.v_number = (varnumber_T)st.st_size;
1076
Bram Moolenaar26262f82019-09-04 20:59:15 +02001077 // non-perfect check for overflow
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001078 if ((off_T)rettv->vval.v_number != (off_T)st.st_size)
1079 rettv->vval.v_number = -2;
1080 }
1081 }
1082 else
1083 rettv->vval.v_number = -1;
1084}
1085
1086/*
1087 * "getftime({fname})" function
1088 */
1089 void
1090f_getftime(typval_T *argvars, typval_T *rettv)
1091{
1092 char_u *fname;
1093 stat_T st;
1094
1095 fname = tv_get_string(&argvars[0]);
1096
1097 if (mch_stat((char *)fname, &st) >= 0)
1098 rettv->vval.v_number = (varnumber_T)st.st_mtime;
1099 else
1100 rettv->vval.v_number = -1;
1101}
1102
1103/*
1104 * "getftype({fname})" function
1105 */
1106 void
1107f_getftype(typval_T *argvars, typval_T *rettv)
1108{
1109 char_u *fname;
1110 stat_T st;
1111 char_u *type = NULL;
1112 char *t;
1113
1114 fname = tv_get_string(&argvars[0]);
1115
1116 rettv->v_type = VAR_STRING;
1117 if (mch_lstat((char *)fname, &st) >= 0)
1118 {
1119 if (S_ISREG(st.st_mode))
1120 t = "file";
1121 else if (S_ISDIR(st.st_mode))
1122 t = "dir";
1123 else if (S_ISLNK(st.st_mode))
1124 t = "link";
1125 else if (S_ISBLK(st.st_mode))
1126 t = "bdev";
1127 else if (S_ISCHR(st.st_mode))
1128 t = "cdev";
1129 else if (S_ISFIFO(st.st_mode))
1130 t = "fifo";
1131 else if (S_ISSOCK(st.st_mode))
1132 t = "socket";
1133 else
1134 t = "other";
1135 type = vim_strsave((char_u *)t);
1136 }
1137 rettv->vval.v_string = type;
1138}
1139
1140/*
1141 * "glob()" function
1142 */
1143 void
1144f_glob(typval_T *argvars, typval_T *rettv)
1145{
1146 int options = WILD_SILENT|WILD_USE_NL;
1147 expand_T xpc;
1148 int error = FALSE;
1149
Bram Moolenaar26262f82019-09-04 20:59:15 +02001150 // When the optional second argument is non-zero, don't remove matches
1151 // for 'wildignore' and don't put matches for 'suffixes' at the end.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001152 rettv->v_type = VAR_STRING;
1153 if (argvars[1].v_type != VAR_UNKNOWN)
1154 {
1155 if (tv_get_number_chk(&argvars[1], &error))
1156 options |= WILD_KEEP_ALL;
1157 if (argvars[2].v_type != VAR_UNKNOWN)
1158 {
1159 if (tv_get_number_chk(&argvars[2], &error))
1160 rettv_list_set(rettv, NULL);
1161 if (argvars[3].v_type != VAR_UNKNOWN
1162 && tv_get_number_chk(&argvars[3], &error))
1163 options |= WILD_ALLLINKS;
1164 }
1165 }
1166 if (!error)
1167 {
1168 ExpandInit(&xpc);
1169 xpc.xp_context = EXPAND_FILES;
1170 if (p_wic)
1171 options += WILD_ICASE;
1172 if (rettv->v_type == VAR_STRING)
1173 rettv->vval.v_string = ExpandOne(&xpc, tv_get_string(&argvars[0]),
1174 NULL, options, WILD_ALL);
1175 else if (rettv_list_alloc(rettv) != FAIL)
1176 {
1177 int i;
1178
1179 ExpandOne(&xpc, tv_get_string(&argvars[0]),
1180 NULL, options, WILD_ALL_KEEP);
1181 for (i = 0; i < xpc.xp_numfiles; i++)
1182 list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
1183
1184 ExpandCleanup(&xpc);
1185 }
1186 }
1187 else
1188 rettv->vval.v_string = NULL;
1189}
1190
1191/*
1192 * "glob2regpat()" function
1193 */
1194 void
1195f_glob2regpat(typval_T *argvars, typval_T *rettv)
1196{
1197 char_u *pat = tv_get_string_chk(&argvars[0]);
1198
1199 rettv->v_type = VAR_STRING;
1200 rettv->vval.v_string = (pat == NULL)
1201 ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, FALSE);
1202}
1203
1204/*
1205 * "globpath()" function
1206 */
1207 void
1208f_globpath(typval_T *argvars, typval_T *rettv)
1209{
1210 int flags = WILD_IGNORE_COMPLETESLASH;
1211 char_u buf1[NUMBUFLEN];
1212 char_u *file = tv_get_string_buf_chk(&argvars[1], buf1);
1213 int error = FALSE;
1214 garray_T ga;
1215 int i;
1216
1217 // When the optional second argument is non-zero, don't remove matches
1218 // for 'wildignore' and don't put matches for 'suffixes' at the end.
1219 rettv->v_type = VAR_STRING;
1220 if (argvars[2].v_type != VAR_UNKNOWN)
1221 {
1222 if (tv_get_number_chk(&argvars[2], &error))
1223 flags |= WILD_KEEP_ALL;
1224 if (argvars[3].v_type != VAR_UNKNOWN)
1225 {
1226 if (tv_get_number_chk(&argvars[3], &error))
1227 rettv_list_set(rettv, NULL);
1228 if (argvars[4].v_type != VAR_UNKNOWN
1229 && tv_get_number_chk(&argvars[4], &error))
1230 flags |= WILD_ALLLINKS;
1231 }
1232 }
1233 if (file != NULL && !error)
1234 {
1235 ga_init2(&ga, (int)sizeof(char_u *), 10);
1236 globpath(tv_get_string(&argvars[0]), file, &ga, flags);
1237 if (rettv->v_type == VAR_STRING)
1238 rettv->vval.v_string = ga_concat_strings(&ga, "\n");
1239 else if (rettv_list_alloc(rettv) != FAIL)
1240 for (i = 0; i < ga.ga_len; ++i)
1241 list_append_string(rettv->vval.v_list,
1242 ((char_u **)(ga.ga_data))[i], -1);
1243 ga_clear_strings(&ga);
1244 }
1245 else
1246 rettv->vval.v_string = NULL;
1247}
1248
1249/*
1250 * "isdirectory()" function
1251 */
1252 void
1253f_isdirectory(typval_T *argvars, typval_T *rettv)
1254{
1255 rettv->vval.v_number = mch_isdir(tv_get_string(&argvars[0]));
1256}
1257
1258/*
1259 * Evaluate "expr" (= "context") for readdir().
1260 */
1261 static int
1262readdir_checkitem(void *context, char_u *name)
1263{
1264 typval_T *expr = (typval_T *)context;
1265 typval_T save_val;
1266 typval_T rettv;
1267 typval_T argv[2];
1268 int retval = 0;
1269 int error = FALSE;
1270
1271 if (expr->v_type == VAR_UNKNOWN)
1272 return 1;
1273
1274 prepare_vimvar(VV_VAL, &save_val);
1275 set_vim_var_string(VV_VAL, name, -1);
1276 argv[0].v_type = VAR_STRING;
1277 argv[0].vval.v_string = name;
1278
1279 if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL)
1280 goto theend;
1281
1282 retval = tv_get_number_chk(&rettv, &error);
1283 if (error)
1284 retval = -1;
1285 clear_tv(&rettv);
1286
1287theend:
1288 set_vim_var_string(VV_VAL, NULL, 0);
1289 restore_vimvar(VV_VAL, &save_val);
1290 return retval;
1291}
1292
1293/*
1294 * Create the directory in which "dir" is located, and higher levels when
1295 * needed.
1296 * Return OK or FAIL.
1297 */
1298 static int
1299mkdir_recurse(char_u *dir, int prot)
1300{
1301 char_u *p;
1302 char_u *updir;
1303 int r = FAIL;
1304
Bram Moolenaar26262f82019-09-04 20:59:15 +02001305 // Get end of directory name in "dir".
1306 // We're done when it's "/" or "c:/".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001307 p = gettail_sep(dir);
1308 if (p <= get_past_head(dir))
1309 return OK;
1310
Bram Moolenaar26262f82019-09-04 20:59:15 +02001311 // If the directory exists we're done. Otherwise: create it.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001312 updir = vim_strnsave(dir, (int)(p - dir));
1313 if (updir == NULL)
1314 return FAIL;
1315 if (mch_isdir(updir))
1316 r = OK;
1317 else if (mkdir_recurse(updir, prot) == OK)
1318 r = vim_mkdir_emsg(updir, prot);
1319 vim_free(updir);
1320 return r;
1321}
1322
1323/*
1324 * "mkdir()" function
1325 */
1326 void
1327f_mkdir(typval_T *argvars, typval_T *rettv)
1328{
1329 char_u *dir;
1330 char_u buf[NUMBUFLEN];
1331 int prot = 0755;
1332
1333 rettv->vval.v_number = FAIL;
1334 if (check_restricted() || check_secure())
1335 return;
1336
1337 dir = tv_get_string_buf(&argvars[0], buf);
1338 if (*dir == NUL)
1339 return;
1340
1341 if (*gettail(dir) == NUL)
Bram Moolenaar26262f82019-09-04 20:59:15 +02001342 // remove trailing slashes
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001343 *gettail_sep(dir) = NUL;
1344
1345 if (argvars[1].v_type != VAR_UNKNOWN)
1346 {
1347 if (argvars[2].v_type != VAR_UNKNOWN)
1348 {
1349 prot = (int)tv_get_number_chk(&argvars[2], NULL);
1350 if (prot == -1)
1351 return;
1352 }
1353 if (STRCMP(tv_get_string(&argvars[1]), "p") == 0)
1354 {
1355 if (mch_isdir(dir))
1356 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001357 // With the "p" flag it's OK if the dir already exists.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001358 rettv->vval.v_number = OK;
1359 return;
1360 }
1361 mkdir_recurse(dir, prot);
1362 }
1363 }
1364 rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
1365}
1366
1367/*
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02001368 * "pathshorten()" function
1369 */
1370 void
1371f_pathshorten(typval_T *argvars, typval_T *rettv)
1372{
1373 char_u *p;
1374
1375 rettv->v_type = VAR_STRING;
1376 p = tv_get_string_chk(&argvars[0]);
1377 if (p == NULL)
1378 rettv->vval.v_string = NULL;
1379 else
1380 {
1381 p = vim_strsave(p);
1382 rettv->vval.v_string = p;
1383 if (p != NULL)
1384 shorten_dir(p);
1385 }
1386}
1387
1388/*
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001389 * "readdir()" function
1390 */
1391 void
1392f_readdir(typval_T *argvars, typval_T *rettv)
1393{
1394 typval_T *expr;
1395 int ret;
1396 char_u *path;
1397 char_u *p;
1398 garray_T ga;
1399 int i;
1400
1401 if (rettv_list_alloc(rettv) == FAIL)
1402 return;
1403 path = tv_get_string(&argvars[0]);
1404 expr = &argvars[1];
1405
1406 ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem);
1407 if (ret == OK && rettv->vval.v_list != NULL && ga.ga_len > 0)
1408 {
1409 for (i = 0; i < ga.ga_len; i++)
1410 {
1411 p = ((char_u **)ga.ga_data)[i];
1412 list_append_string(rettv->vval.v_list, p, -1);
1413 }
1414 }
1415 ga_clear_strings(&ga);
1416}
1417
1418/*
1419 * "readfile()" function
1420 */
1421 void
1422f_readfile(typval_T *argvars, typval_T *rettv)
1423{
1424 int binary = FALSE;
1425 int blob = FALSE;
1426 int failed = FALSE;
1427 char_u *fname;
1428 FILE *fd;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001429 char_u buf[(IOSIZE/256)*256]; // rounded to avoid odd + 1
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001430 int io_size = sizeof(buf);
Bram Moolenaar26262f82019-09-04 20:59:15 +02001431 int readlen; // size of last fread()
1432 char_u *prev = NULL; // previously read bytes, if any
1433 long prevlen = 0; // length of data in prev
1434 long prevsize = 0; // size of prev buffer
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001435 long maxline = MAXLNUM;
1436 long cnt = 0;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001437 char_u *p; // position in buf
1438 char_u *start; // start of current line
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001439
1440 if (argvars[1].v_type != VAR_UNKNOWN)
1441 {
1442 if (STRCMP(tv_get_string(&argvars[1]), "b") == 0)
1443 binary = TRUE;
1444 if (STRCMP(tv_get_string(&argvars[1]), "B") == 0)
1445 blob = TRUE;
1446
1447 if (argvars[2].v_type != VAR_UNKNOWN)
1448 maxline = (long)tv_get_number(&argvars[2]);
1449 }
1450
1451 if (blob)
1452 {
1453 if (rettv_blob_alloc(rettv) == FAIL)
1454 return;
1455 }
1456 else
1457 {
1458 if (rettv_list_alloc(rettv) == FAIL)
1459 return;
1460 }
1461
Bram Moolenaar26262f82019-09-04 20:59:15 +02001462 // Always open the file in binary mode, library functions have a mind of
1463 // their own about CR-LF conversion.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001464 fname = tv_get_string(&argvars[0]);
1465 if (*fname == NUL || (fd = mch_fopen((char *)fname, READBIN)) == NULL)
1466 {
1467 semsg(_(e_notopen), *fname == NUL ? (char_u *)_("<empty>") : fname);
1468 return;
1469 }
1470
1471 if (blob)
1472 {
1473 if (read_blob(fd, rettv->vval.v_blob) == FAIL)
1474 {
1475 emsg("cannot read file");
1476 blob_free(rettv->vval.v_blob);
1477 }
1478 fclose(fd);
1479 return;
1480 }
1481
1482 while (cnt < maxline || maxline < 0)
1483 {
1484 readlen = (int)fread(buf, 1, io_size, fd);
1485
Bram Moolenaar26262f82019-09-04 20:59:15 +02001486 // This for loop processes what was read, but is also entered at end
1487 // of file so that either:
1488 // - an incomplete line gets written
1489 // - a "binary" file gets an empty line at the end if it ends in a
1490 // newline.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001491 for (p = buf, start = buf;
1492 p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
1493 ++p)
1494 {
1495 if (*p == '\n' || readlen <= 0)
1496 {
1497 listitem_T *li;
1498 char_u *s = NULL;
1499 long_u len = p - start;
1500
Bram Moolenaar26262f82019-09-04 20:59:15 +02001501 // Finished a line. Remove CRs before NL.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001502 if (readlen > 0 && !binary)
1503 {
1504 while (len > 0 && start[len - 1] == '\r')
1505 --len;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001506 // removal may cross back to the "prev" string
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001507 if (len == 0)
1508 while (prevlen > 0 && prev[prevlen - 1] == '\r')
1509 --prevlen;
1510 }
1511 if (prevlen == 0)
1512 s = vim_strnsave(start, (int)len);
1513 else
1514 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001515 // Change "prev" buffer to be the right size. This way
1516 // the bytes are only copied once, and very long lines are
1517 // allocated only once.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001518 if ((s = vim_realloc(prev, prevlen + len + 1)) != NULL)
1519 {
1520 mch_memmove(s + prevlen, start, len);
1521 s[prevlen + len] = NUL;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001522 prev = NULL; // the list will own the string
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001523 prevlen = prevsize = 0;
1524 }
1525 }
1526 if (s == NULL)
1527 {
1528 do_outofmem_msg((long_u) prevlen + len + 1);
1529 failed = TRUE;
1530 break;
1531 }
1532
1533 if ((li = listitem_alloc()) == NULL)
1534 {
1535 vim_free(s);
1536 failed = TRUE;
1537 break;
1538 }
1539 li->li_tv.v_type = VAR_STRING;
1540 li->li_tv.v_lock = 0;
1541 li->li_tv.vval.v_string = s;
1542 list_append(rettv->vval.v_list, li);
1543
Bram Moolenaar26262f82019-09-04 20:59:15 +02001544 start = p + 1; // step over newline
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001545 if ((++cnt >= maxline && maxline >= 0) || readlen <= 0)
1546 break;
1547 }
1548 else if (*p == NUL)
1549 *p = '\n';
Bram Moolenaar26262f82019-09-04 20:59:15 +02001550 // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
1551 // when finding the BF and check the previous two bytes.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001552 else if (*p == 0xbf && enc_utf8 && !binary)
1553 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001554 // Find the two bytes before the 0xbf. If p is at buf, or buf
1555 // + 1, these may be in the "prev" string.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001556 char_u back1 = p >= buf + 1 ? p[-1]
1557 : prevlen >= 1 ? prev[prevlen - 1] : NUL;
1558 char_u back2 = p >= buf + 2 ? p[-2]
1559 : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
1560 : prevlen >= 2 ? prev[prevlen - 2] : NUL;
1561
1562 if (back2 == 0xef && back1 == 0xbb)
1563 {
1564 char_u *dest = p - 2;
1565
Bram Moolenaar26262f82019-09-04 20:59:15 +02001566 // Usually a BOM is at the beginning of a file, and so at
1567 // the beginning of a line; then we can just step over it.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001568 if (start == dest)
1569 start = p + 1;
1570 else
1571 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001572 // have to shuffle buf to close gap
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001573 int adjust_prevlen = 0;
1574
1575 if (dest < buf)
1576 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001577 adjust_prevlen = (int)(buf - dest); // must be 1 or 2
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001578 dest = buf;
1579 }
1580 if (readlen > p - buf + 1)
1581 mch_memmove(dest, p + 1, readlen - (p - buf) - 1);
1582 readlen -= 3 - adjust_prevlen;
1583 prevlen -= adjust_prevlen;
1584 p = dest - 1;
1585 }
1586 }
1587 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02001588 } // for
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001589
1590 if (failed || (cnt >= maxline && maxline >= 0) || readlen <= 0)
1591 break;
1592 if (start < p)
1593 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001594 // There's part of a line in buf, store it in "prev".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001595 if (p - start + prevlen >= prevsize)
1596 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001597 // need bigger "prev" buffer
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001598 char_u *newprev;
1599
Bram Moolenaar26262f82019-09-04 20:59:15 +02001600 // A common use case is ordinary text files and "prev" gets a
1601 // fragment of a line, so the first allocation is made
1602 // small, to avoid repeatedly 'allocing' large and
1603 // 'reallocing' small.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001604 if (prevsize == 0)
1605 prevsize = (long)(p - start);
1606 else
1607 {
1608 long grow50pc = (prevsize * 3) / 2;
1609 long growmin = (long)((p - start) * 2 + prevlen);
1610 prevsize = grow50pc > growmin ? grow50pc : growmin;
1611 }
1612 newprev = vim_realloc(prev, prevsize);
1613 if (newprev == NULL)
1614 {
1615 do_outofmem_msg((long_u)prevsize);
1616 failed = TRUE;
1617 break;
1618 }
1619 prev = newprev;
1620 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02001621 // Add the line part to end of "prev".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001622 mch_memmove(prev + prevlen, start, p - start);
1623 prevlen += (long)(p - start);
1624 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02001625 } // while
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001626
Bram Moolenaar26262f82019-09-04 20:59:15 +02001627 // For a negative line count use only the lines at the end of the file,
1628 // free the rest.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001629 if (!failed && maxline < 0)
1630 while (cnt > -maxline)
1631 {
1632 listitem_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first);
1633 --cnt;
1634 }
1635
1636 if (failed)
1637 {
1638 // an empty list is returned on error
1639 list_free(rettv->vval.v_list);
1640 rettv_list_alloc(rettv);
1641 }
1642
1643 vim_free(prev);
1644 fclose(fd);
1645}
1646
1647/*
1648 * "resolve()" function
1649 */
1650 void
1651f_resolve(typval_T *argvars, typval_T *rettv)
1652{
1653 char_u *p;
1654#ifdef HAVE_READLINK
1655 char_u *buf = NULL;
1656#endif
1657
1658 p = tv_get_string(&argvars[0]);
1659#ifdef FEAT_SHORTCUT
1660 {
1661 char_u *v = NULL;
1662
1663 v = mch_resolve_path(p, TRUE);
1664 if (v != NULL)
1665 rettv->vval.v_string = v;
1666 else
1667 rettv->vval.v_string = vim_strsave(p);
1668 }
1669#else
1670# ifdef HAVE_READLINK
1671 {
1672 char_u *cpy;
1673 int len;
1674 char_u *remain = NULL;
1675 char_u *q;
1676 int is_relative_to_current = FALSE;
1677 int has_trailing_pathsep = FALSE;
1678 int limit = 100;
1679
1680 p = vim_strsave(p);
Bram Moolenaar70188f52019-12-23 18:18:52 +01001681 if (p == NULL)
1682 goto fail;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001683 if (p[0] == '.' && (vim_ispathsep(p[1])
1684 || (p[1] == '.' && (vim_ispathsep(p[2])))))
1685 is_relative_to_current = TRUE;
1686
1687 len = STRLEN(p);
1688 if (len > 0 && after_pathsep(p, p + len))
1689 {
1690 has_trailing_pathsep = TRUE;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001691 p[len - 1] = NUL; // the trailing slash breaks readlink()
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001692 }
1693
1694 q = getnextcomp(p);
1695 if (*q != NUL)
1696 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001697 // Separate the first path component in "p", and keep the
1698 // remainder (beginning with the path separator).
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001699 remain = vim_strsave(q - 1);
1700 q[-1] = NUL;
1701 }
1702
1703 buf = alloc(MAXPATHL + 1);
1704 if (buf == NULL)
Bram Moolenaar70188f52019-12-23 18:18:52 +01001705 {
1706 vim_free(p);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001707 goto fail;
Bram Moolenaar70188f52019-12-23 18:18:52 +01001708 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001709
1710 for (;;)
1711 {
1712 for (;;)
1713 {
1714 len = readlink((char *)p, (char *)buf, MAXPATHL);
1715 if (len <= 0)
1716 break;
1717 buf[len] = NUL;
1718
1719 if (limit-- == 0)
1720 {
1721 vim_free(p);
1722 vim_free(remain);
1723 emsg(_("E655: Too many symbolic links (cycle?)"));
1724 rettv->vval.v_string = NULL;
1725 goto fail;
1726 }
1727
Bram Moolenaar26262f82019-09-04 20:59:15 +02001728 // Ensure that the result will have a trailing path separator
1729 // if the argument has one.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001730 if (remain == NULL && has_trailing_pathsep)
1731 add_pathsep(buf);
1732
Bram Moolenaar26262f82019-09-04 20:59:15 +02001733 // Separate the first path component in the link value and
1734 // concatenate the remainders.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001735 q = getnextcomp(vim_ispathsep(*buf) ? buf + 1 : buf);
1736 if (*q != NUL)
1737 {
1738 if (remain == NULL)
1739 remain = vim_strsave(q - 1);
1740 else
1741 {
1742 cpy = concat_str(q - 1, remain);
1743 if (cpy != NULL)
1744 {
1745 vim_free(remain);
1746 remain = cpy;
1747 }
1748 }
1749 q[-1] = NUL;
1750 }
1751
1752 q = gettail(p);
1753 if (q > p && *q == NUL)
1754 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001755 // Ignore trailing path separator.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001756 q[-1] = NUL;
1757 q = gettail(p);
1758 }
1759 if (q > p && !mch_isFullName(buf))
1760 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001761 // symlink is relative to directory of argument
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001762 cpy = alloc(STRLEN(p) + STRLEN(buf) + 1);
1763 if (cpy != NULL)
1764 {
1765 STRCPY(cpy, p);
1766 STRCPY(gettail(cpy), buf);
1767 vim_free(p);
1768 p = cpy;
1769 }
1770 }
1771 else
1772 {
1773 vim_free(p);
1774 p = vim_strsave(buf);
1775 }
1776 }
1777
1778 if (remain == NULL)
1779 break;
1780
Bram Moolenaar26262f82019-09-04 20:59:15 +02001781 // Append the first path component of "remain" to "p".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001782 q = getnextcomp(remain + 1);
1783 len = q - remain - (*q != NUL);
1784 cpy = vim_strnsave(p, STRLEN(p) + len);
1785 if (cpy != NULL)
1786 {
1787 STRNCAT(cpy, remain, len);
1788 vim_free(p);
1789 p = cpy;
1790 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02001791 // Shorten "remain".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001792 if (*q != NUL)
1793 STRMOVE(remain, q - 1);
1794 else
1795 VIM_CLEAR(remain);
1796 }
1797
Bram Moolenaar26262f82019-09-04 20:59:15 +02001798 // If the result is a relative path name, make it explicitly relative to
1799 // the current directory if and only if the argument had this form.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001800 if (!vim_ispathsep(*p))
1801 {
1802 if (is_relative_to_current
1803 && *p != NUL
1804 && !(p[0] == '.'
1805 && (p[1] == NUL
1806 || vim_ispathsep(p[1])
1807 || (p[1] == '.'
1808 && (p[2] == NUL
1809 || vim_ispathsep(p[2]))))))
1810 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001811 // Prepend "./".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001812 cpy = concat_str((char_u *)"./", p);
1813 if (cpy != NULL)
1814 {
1815 vim_free(p);
1816 p = cpy;
1817 }
1818 }
1819 else if (!is_relative_to_current)
1820 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001821 // Strip leading "./".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001822 q = p;
1823 while (q[0] == '.' && vim_ispathsep(q[1]))
1824 q += 2;
1825 if (q > p)
1826 STRMOVE(p, p + 2);
1827 }
1828 }
1829
Bram Moolenaar26262f82019-09-04 20:59:15 +02001830 // Ensure that the result will have no trailing path separator
1831 // if the argument had none. But keep "/" or "//".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001832 if (!has_trailing_pathsep)
1833 {
1834 q = p + STRLEN(p);
1835 if (after_pathsep(p, q))
1836 *gettail_sep(p) = NUL;
1837 }
1838
1839 rettv->vval.v_string = p;
1840 }
1841# else
1842 rettv->vval.v_string = vim_strsave(p);
1843# endif
1844#endif
1845
1846 simplify_filename(rettv->vval.v_string);
1847
1848#ifdef HAVE_READLINK
1849fail:
1850 vim_free(buf);
1851#endif
1852 rettv->v_type = VAR_STRING;
1853}
1854
1855/*
1856 * "tempname()" function
1857 */
1858 void
1859f_tempname(typval_T *argvars UNUSED, typval_T *rettv)
1860{
1861 static int x = 'A';
1862
1863 rettv->v_type = VAR_STRING;
1864 rettv->vval.v_string = vim_tempname(x, FALSE);
1865
Bram Moolenaar26262f82019-09-04 20:59:15 +02001866 // Advance 'x' to use A-Z and 0-9, so that there are at least 34 different
1867 // names. Skip 'I' and 'O', they are used for shell redirection.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001868 do
1869 {
1870 if (x == 'Z')
1871 x = '0';
1872 else if (x == '9')
1873 x = 'A';
1874 else
1875 {
1876#ifdef EBCDIC
1877 if (x == 'I')
1878 x = 'J';
1879 else if (x == 'R')
1880 x = 'S';
1881 else
1882#endif
1883 ++x;
1884 }
1885 } while (x == 'I' || x == 'O');
1886}
1887
1888/*
1889 * "writefile()" function
1890 */
1891 void
1892f_writefile(typval_T *argvars, typval_T *rettv)
1893{
1894 int binary = FALSE;
1895 int append = FALSE;
1896#ifdef HAVE_FSYNC
1897 int do_fsync = p_fs;
1898#endif
1899 char_u *fname;
1900 FILE *fd;
1901 int ret = 0;
1902 listitem_T *li;
1903 list_T *list = NULL;
1904 blob_T *blob = NULL;
1905
1906 rettv->vval.v_number = -1;
1907 if (check_secure())
1908 return;
1909
1910 if (argvars[0].v_type == VAR_LIST)
1911 {
1912 list = argvars[0].vval.v_list;
1913 if (list == NULL)
1914 return;
Bram Moolenaar8a7d6542020-01-26 15:56:19 +01001915 range_list_materialize(list);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001916 for (li = list->lv_first; li != NULL; li = li->li_next)
1917 if (tv_get_string_chk(&li->li_tv) == NULL)
1918 return;
1919 }
1920 else if (argvars[0].v_type == VAR_BLOB)
1921 {
1922 blob = argvars[0].vval.v_blob;
1923 if (blob == NULL)
1924 return;
1925 }
1926 else
1927 {
1928 semsg(_(e_invarg2), "writefile()");
1929 return;
1930 }
1931
1932 if (argvars[2].v_type != VAR_UNKNOWN)
1933 {
1934 char_u *arg2 = tv_get_string_chk(&argvars[2]);
1935
1936 if (arg2 == NULL)
1937 return;
1938 if (vim_strchr(arg2, 'b') != NULL)
1939 binary = TRUE;
1940 if (vim_strchr(arg2, 'a') != NULL)
1941 append = TRUE;
1942#ifdef HAVE_FSYNC
1943 if (vim_strchr(arg2, 's') != NULL)
1944 do_fsync = TRUE;
1945 else if (vim_strchr(arg2, 'S') != NULL)
1946 do_fsync = FALSE;
1947#endif
1948 }
1949
1950 fname = tv_get_string_chk(&argvars[1]);
1951 if (fname == NULL)
1952 return;
1953
Bram Moolenaar26262f82019-09-04 20:59:15 +02001954 // Always open the file in binary mode, library functions have a mind of
1955 // their own about CR-LF conversion.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001956 if (*fname == NUL || (fd = mch_fopen((char *)fname,
1957 append ? APPENDBIN : WRITEBIN)) == NULL)
1958 {
1959 semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
1960 ret = -1;
1961 }
1962 else if (blob)
1963 {
1964 if (write_blob(fd, blob) == FAIL)
1965 ret = -1;
1966#ifdef HAVE_FSYNC
1967 else if (do_fsync)
1968 // Ignore the error, the user wouldn't know what to do about it.
1969 // May happen for a device.
1970 vim_ignored = vim_fsync(fileno(fd));
1971#endif
1972 fclose(fd);
1973 }
1974 else
1975 {
1976 if (write_list(fd, list, binary) == FAIL)
1977 ret = -1;
1978#ifdef HAVE_FSYNC
1979 else if (do_fsync)
Bram Moolenaar26262f82019-09-04 20:59:15 +02001980 // Ignore the error, the user wouldn't know what to do about it.
1981 // May happen for a device.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001982 vim_ignored = vim_fsync(fileno(fd));
1983#endif
1984 fclose(fd);
1985 }
1986
1987 rettv->vval.v_number = ret;
1988}
1989
1990#endif // FEAT_EVAL
1991
1992#if defined(FEAT_BROWSE) || defined(PROTO)
1993/*
1994 * Generic browse function. Calls gui_mch_browse() when possible.
1995 * Later this may pop-up a non-GUI file selector (external command?).
1996 */
1997 char_u *
1998do_browse(
Bram Moolenaar26262f82019-09-04 20:59:15 +02001999 int flags, // BROWSE_SAVE and BROWSE_DIR
2000 char_u *title, // title for the window
2001 char_u *dflt, // default file name (may include directory)
2002 char_u *ext, // extension added
2003 char_u *initdir, // initial directory, NULL for current dir or
2004 // when using path from "dflt"
2005 char_u *filter, // file name filter
2006 buf_T *buf) // buffer to read/write for
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002007{
2008 char_u *fname;
Bram Moolenaar26262f82019-09-04 20:59:15 +02002009 static char_u *last_dir = NULL; // last used directory
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002010 char_u *tofree = NULL;
2011 int save_browse = cmdmod.browse;
2012
Bram Moolenaar26262f82019-09-04 20:59:15 +02002013 // Must turn off browse to avoid that autocommands will get the
2014 // flag too!
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002015 cmdmod.browse = FALSE;
2016
2017 if (title == NULL || *title == NUL)
2018 {
2019 if (flags & BROWSE_DIR)
2020 title = (char_u *)_("Select Directory dialog");
2021 else if (flags & BROWSE_SAVE)
2022 title = (char_u *)_("Save File dialog");
2023 else
2024 title = (char_u *)_("Open File dialog");
2025 }
2026
Bram Moolenaar26262f82019-09-04 20:59:15 +02002027 // When no directory specified, use default file name, default dir, buffer
2028 // dir, last dir or current dir
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002029 if ((initdir == NULL || *initdir == NUL) && dflt != NULL && *dflt != NUL)
2030 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002031 if (mch_isdir(dflt)) // default file name is a directory
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002032 {
2033 initdir = dflt;
2034 dflt = NULL;
2035 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02002036 else if (gettail(dflt) != dflt) // default file name includes a path
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002037 {
2038 tofree = vim_strsave(dflt);
2039 if (tofree != NULL)
2040 {
2041 initdir = tofree;
2042 *gettail(initdir) = NUL;
2043 dflt = gettail(dflt);
2044 }
2045 }
2046 }
2047
2048 if (initdir == NULL || *initdir == NUL)
2049 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002050 // When 'browsedir' is a directory, use it
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002051 if (STRCMP(p_bsdir, "last") != 0
2052 && STRCMP(p_bsdir, "buffer") != 0
2053 && STRCMP(p_bsdir, "current") != 0
2054 && mch_isdir(p_bsdir))
2055 initdir = p_bsdir;
Bram Moolenaar26262f82019-09-04 20:59:15 +02002056 // When saving or 'browsedir' is "buffer", use buffer fname
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002057 else if (((flags & BROWSE_SAVE) || *p_bsdir == 'b')
2058 && buf != NULL && buf->b_ffname != NULL)
2059 {
2060 if (dflt == NULL || *dflt == NUL)
2061 dflt = gettail(curbuf->b_ffname);
2062 tofree = vim_strsave(curbuf->b_ffname);
2063 if (tofree != NULL)
2064 {
2065 initdir = tofree;
2066 *gettail(initdir) = NUL;
2067 }
2068 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02002069 // When 'browsedir' is "last", use dir from last browse
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002070 else if (*p_bsdir == 'l')
2071 initdir = last_dir;
Bram Moolenaar26262f82019-09-04 20:59:15 +02002072 // When 'browsedir is "current", use current directory. This is the
2073 // default already, leave initdir empty.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002074 }
2075
2076# ifdef FEAT_GUI
Bram Moolenaar26262f82019-09-04 20:59:15 +02002077 if (gui.in_use) // when this changes, also adjust f_has()!
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002078 {
2079 if (filter == NULL
2080# ifdef FEAT_EVAL
2081 && (filter = get_var_value((char_u *)"b:browsefilter")) == NULL
2082 && (filter = get_var_value((char_u *)"g:browsefilter")) == NULL
2083# endif
2084 )
2085 filter = BROWSE_FILTER_DEFAULT;
2086 if (flags & BROWSE_DIR)
2087 {
2088# if defined(FEAT_GUI_GTK) || defined(MSWIN)
Bram Moolenaar26262f82019-09-04 20:59:15 +02002089 // For systems that have a directory dialog.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002090 fname = gui_mch_browsedir(title, initdir);
2091# else
Bram Moolenaar26262f82019-09-04 20:59:15 +02002092 // Generic solution for selecting a directory: select a file and
2093 // remove the file name.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002094 fname = gui_mch_browse(0, title, dflt, ext, initdir, (char_u *)"");
2095# endif
2096# if !defined(FEAT_GUI_GTK)
Bram Moolenaar26262f82019-09-04 20:59:15 +02002097 // Win32 adds a dummy file name, others return an arbitrary file
2098 // name. GTK+ 2 returns only the directory,
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002099 if (fname != NULL && *fname != NUL && !mch_isdir(fname))
2100 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002101 // Remove the file name.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002102 char_u *tail = gettail_sep(fname);
2103
2104 if (tail == fname)
Bram Moolenaar26262f82019-09-04 20:59:15 +02002105 *tail++ = '.'; // use current dir
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002106 *tail = NUL;
2107 }
2108# endif
2109 }
2110 else
2111 fname = gui_mch_browse(flags & BROWSE_SAVE,
2112 title, dflt, ext, initdir, (char_u *)_(filter));
2113
Bram Moolenaar26262f82019-09-04 20:59:15 +02002114 // We hang around in the dialog for a while, the user might do some
2115 // things to our files. The Win32 dialog allows deleting or renaming
2116 // a file, check timestamps.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002117 need_check_timestamps = TRUE;
2118 did_check_timestamps = FALSE;
2119 }
2120 else
2121# endif
2122 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002123 // TODO: non-GUI file selector here
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002124 emsg(_("E338: Sorry, no file browser in console mode"));
2125 fname = NULL;
2126 }
2127
Bram Moolenaar26262f82019-09-04 20:59:15 +02002128 // keep the directory for next time
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002129 if (fname != NULL)
2130 {
2131 vim_free(last_dir);
2132 last_dir = vim_strsave(fname);
2133 if (last_dir != NULL && !(flags & BROWSE_DIR))
2134 {
2135 *gettail(last_dir) = NUL;
2136 if (*last_dir == NUL)
2137 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002138 // filename only returned, must be in current dir
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002139 vim_free(last_dir);
2140 last_dir = alloc(MAXPATHL);
2141 if (last_dir != NULL)
2142 mch_dirname(last_dir, MAXPATHL);
2143 }
2144 }
2145 }
2146
2147 vim_free(tofree);
2148 cmdmod.browse = save_browse;
2149
2150 return fname;
2151}
2152#endif
2153
2154#if defined(FEAT_EVAL) || defined(PROTO)
2155
2156/*
2157 * "browse(save, title, initdir, default)" function
2158 */
2159 void
2160f_browse(typval_T *argvars UNUSED, typval_T *rettv)
2161{
2162# ifdef FEAT_BROWSE
2163 int save;
2164 char_u *title;
2165 char_u *initdir;
2166 char_u *defname;
2167 char_u buf[NUMBUFLEN];
2168 char_u buf2[NUMBUFLEN];
2169 int error = FALSE;
2170
2171 save = (int)tv_get_number_chk(&argvars[0], &error);
2172 title = tv_get_string_chk(&argvars[1]);
2173 initdir = tv_get_string_buf_chk(&argvars[2], buf);
2174 defname = tv_get_string_buf_chk(&argvars[3], buf2);
2175
2176 if (error || title == NULL || initdir == NULL || defname == NULL)
2177 rettv->vval.v_string = NULL;
2178 else
2179 rettv->vval.v_string =
2180 do_browse(save ? BROWSE_SAVE : 0,
2181 title, defname, NULL, initdir, NULL, curbuf);
2182# else
2183 rettv->vval.v_string = NULL;
2184# endif
2185 rettv->v_type = VAR_STRING;
2186}
2187
2188/*
2189 * "browsedir(title, initdir)" function
2190 */
2191 void
2192f_browsedir(typval_T *argvars UNUSED, typval_T *rettv)
2193{
2194# ifdef FEAT_BROWSE
2195 char_u *title;
2196 char_u *initdir;
2197 char_u buf[NUMBUFLEN];
2198
2199 title = tv_get_string_chk(&argvars[0]);
2200 initdir = tv_get_string_buf_chk(&argvars[1], buf);
2201
2202 if (title == NULL || initdir == NULL)
2203 rettv->vval.v_string = NULL;
2204 else
2205 rettv->vval.v_string = do_browse(BROWSE_DIR,
2206 title, NULL, NULL, initdir, NULL, curbuf);
2207# else
2208 rettv->vval.v_string = NULL;
2209# endif
2210 rettv->v_type = VAR_STRING;
2211}
2212
2213#endif // FEAT_EVAL
Bram Moolenaar26262f82019-09-04 20:59:15 +02002214
2215/*
2216 * Replace home directory by "~" in each space or comma separated file name in
2217 * 'src'.
2218 * If anything fails (except when out of space) dst equals src.
2219 */
2220 void
2221home_replace(
2222 buf_T *buf, // when not NULL, check for help files
2223 char_u *src, // input file name
2224 char_u *dst, // where to put the result
2225 int dstlen, // maximum length of the result
2226 int one) // if TRUE, only replace one file name, include
2227 // spaces and commas in the file name.
2228{
2229 size_t dirlen = 0, envlen = 0;
2230 size_t len;
2231 char_u *homedir_env, *homedir_env_orig;
2232 char_u *p;
2233
2234 if (src == NULL)
2235 {
2236 *dst = NUL;
2237 return;
2238 }
2239
2240 /*
2241 * If the file is a help file, remove the path completely.
2242 */
2243 if (buf != NULL && buf->b_help)
2244 {
2245 vim_snprintf((char *)dst, dstlen, "%s", gettail(src));
2246 return;
2247 }
2248
2249 /*
2250 * We check both the value of the $HOME environment variable and the
2251 * "real" home directory.
2252 */
2253 if (homedir != NULL)
2254 dirlen = STRLEN(homedir);
2255
2256#ifdef VMS
2257 homedir_env_orig = homedir_env = mch_getenv((char_u *)"SYS$LOGIN");
2258#else
2259 homedir_env_orig = homedir_env = mch_getenv((char_u *)"HOME");
2260#endif
2261#ifdef MSWIN
2262 if (homedir_env == NULL)
2263 homedir_env_orig = homedir_env = mch_getenv((char_u *)"USERPROFILE");
2264#endif
2265 // Empty is the same as not set.
2266 if (homedir_env != NULL && *homedir_env == NUL)
2267 homedir_env = NULL;
2268
2269 if (homedir_env != NULL && *homedir_env == '~')
2270 {
2271 int usedlen = 0;
2272 int flen;
2273 char_u *fbuf = NULL;
2274
2275 flen = (int)STRLEN(homedir_env);
2276 (void)modify_fname((char_u *)":p", FALSE, &usedlen,
2277 &homedir_env, &fbuf, &flen);
2278 flen = (int)STRLEN(homedir_env);
2279 if (flen > 0 && vim_ispathsep(homedir_env[flen - 1]))
2280 // Remove the trailing / that is added to a directory.
2281 homedir_env[flen - 1] = NUL;
2282 }
2283
2284 if (homedir_env != NULL)
2285 envlen = STRLEN(homedir_env);
2286
2287 if (!one)
2288 src = skipwhite(src);
2289 while (*src && dstlen > 0)
2290 {
2291 /*
2292 * Here we are at the beginning of a file name.
2293 * First, check to see if the beginning of the file name matches
2294 * $HOME or the "real" home directory. Check that there is a '/'
2295 * after the match (so that if e.g. the file is "/home/pieter/bla",
2296 * and the home directory is "/home/piet", the file does not end up
2297 * as "~er/bla" (which would seem to indicate the file "bla" in user
2298 * er's home directory)).
2299 */
2300 p = homedir;
2301 len = dirlen;
2302 for (;;)
2303 {
2304 if ( len
2305 && fnamencmp(src, p, len) == 0
2306 && (vim_ispathsep(src[len])
2307 || (!one && (src[len] == ',' || src[len] == ' '))
2308 || src[len] == NUL))
2309 {
2310 src += len;
2311 if (--dstlen > 0)
2312 *dst++ = '~';
2313
2314 /*
2315 * If it's just the home directory, add "/".
2316 */
2317 if (!vim_ispathsep(src[0]) && --dstlen > 0)
2318 *dst++ = '/';
2319 break;
2320 }
2321 if (p == homedir_env)
2322 break;
2323 p = homedir_env;
2324 len = envlen;
2325 }
2326
2327 // if (!one) skip to separator: space or comma
2328 while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0)
2329 *dst++ = *src++;
2330 // skip separator
2331 while ((*src == ' ' || *src == ',') && --dstlen > 0)
2332 *dst++ = *src++;
2333 }
2334 // if (dstlen == 0) out of space, what to do???
2335
2336 *dst = NUL;
2337
2338 if (homedir_env != homedir_env_orig)
2339 vim_free(homedir_env);
2340}
2341
2342/*
2343 * Like home_replace, store the replaced string in allocated memory.
2344 * When something fails, NULL is returned.
2345 */
2346 char_u *
2347home_replace_save(
2348 buf_T *buf, // when not NULL, check for help files
2349 char_u *src) // input file name
2350{
2351 char_u *dst;
2352 unsigned len;
2353
2354 len = 3; // space for "~/" and trailing NUL
2355 if (src != NULL) // just in case
2356 len += (unsigned)STRLEN(src);
2357 dst = alloc(len);
2358 if (dst != NULL)
2359 home_replace(buf, src, dst, len, TRUE);
2360 return dst;
2361}
2362
2363/*
2364 * Compare two file names and return:
2365 * FPC_SAME if they both exist and are the same file.
2366 * FPC_SAMEX if they both don't exist and have the same file name.
2367 * FPC_DIFF if they both exist and are different files.
2368 * FPC_NOTX if they both don't exist.
2369 * FPC_DIFFX if one of them doesn't exist.
2370 * For the first name environment variables are expanded if "expandenv" is
2371 * TRUE.
2372 */
2373 int
2374fullpathcmp(
2375 char_u *s1,
2376 char_u *s2,
2377 int checkname, // when both don't exist, check file names
2378 int expandenv)
2379{
2380#ifdef UNIX
2381 char_u exp1[MAXPATHL];
2382 char_u full1[MAXPATHL];
2383 char_u full2[MAXPATHL];
2384 stat_T st1, st2;
2385 int r1, r2;
2386
2387 if (expandenv)
2388 expand_env(s1, exp1, MAXPATHL);
2389 else
2390 vim_strncpy(exp1, s1, MAXPATHL - 1);
2391 r1 = mch_stat((char *)exp1, &st1);
2392 r2 = mch_stat((char *)s2, &st2);
2393 if (r1 != 0 && r2 != 0)
2394 {
Bram Moolenaar217e1b82019-12-01 21:41:28 +01002395 // if mch_stat() doesn't work, may compare the names
Bram Moolenaar26262f82019-09-04 20:59:15 +02002396 if (checkname)
2397 {
2398 if (fnamecmp(exp1, s2) == 0)
2399 return FPC_SAMEX;
2400 r1 = vim_FullName(exp1, full1, MAXPATHL, FALSE);
2401 r2 = vim_FullName(s2, full2, MAXPATHL, FALSE);
2402 if (r1 == OK && r2 == OK && fnamecmp(full1, full2) == 0)
2403 return FPC_SAMEX;
2404 }
2405 return FPC_NOTX;
2406 }
2407 if (r1 != 0 || r2 != 0)
2408 return FPC_DIFFX;
2409 if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
2410 return FPC_SAME;
2411 return FPC_DIFF;
2412#else
2413 char_u *exp1; // expanded s1
2414 char_u *full1; // full path of s1
2415 char_u *full2; // full path of s2
2416 int retval = FPC_DIFF;
2417 int r1, r2;
2418
2419 // allocate one buffer to store three paths (alloc()/free() is slow!)
2420 if ((exp1 = alloc(MAXPATHL * 3)) != NULL)
2421 {
2422 full1 = exp1 + MAXPATHL;
2423 full2 = full1 + MAXPATHL;
2424
2425 if (expandenv)
2426 expand_env(s1, exp1, MAXPATHL);
2427 else
2428 vim_strncpy(exp1, s1, MAXPATHL - 1);
2429 r1 = vim_FullName(exp1, full1, MAXPATHL, FALSE);
2430 r2 = vim_FullName(s2, full2, MAXPATHL, FALSE);
2431
2432 // If vim_FullName() fails, the file probably doesn't exist.
2433 if (r1 != OK && r2 != OK)
2434 {
2435 if (checkname && fnamecmp(exp1, s2) == 0)
2436 retval = FPC_SAMEX;
2437 else
2438 retval = FPC_NOTX;
2439 }
2440 else if (r1 != OK || r2 != OK)
2441 retval = FPC_DIFFX;
2442 else if (fnamecmp(full1, full2))
2443 retval = FPC_DIFF;
2444 else
2445 retval = FPC_SAME;
2446 vim_free(exp1);
2447 }
2448 return retval;
2449#endif
2450}
2451
2452/*
2453 * Get the tail of a path: the file name.
2454 * When the path ends in a path separator the tail is the NUL after it.
2455 * Fail safe: never returns NULL.
2456 */
2457 char_u *
2458gettail(char_u *fname)
2459{
2460 char_u *p1, *p2;
2461
2462 if (fname == NULL)
2463 return (char_u *)"";
2464 for (p1 = p2 = get_past_head(fname); *p2; ) // find last part of path
2465 {
2466 if (vim_ispathsep_nocolon(*p2))
2467 p1 = p2 + 1;
2468 MB_PTR_ADV(p2);
2469 }
2470 return p1;
2471}
2472
2473/*
2474 * Get pointer to tail of "fname", including path separators. Putting a NUL
2475 * here leaves the directory name. Takes care of "c:/" and "//".
2476 * Always returns a valid pointer.
2477 */
2478 char_u *
2479gettail_sep(char_u *fname)
2480{
2481 char_u *p;
2482 char_u *t;
2483
2484 p = get_past_head(fname); // don't remove the '/' from "c:/file"
2485 t = gettail(fname);
2486 while (t > p && after_pathsep(fname, t))
2487 --t;
2488#ifdef VMS
2489 // path separator is part of the path
2490 ++t;
2491#endif
2492 return t;
2493}
2494
2495/*
2496 * get the next path component (just after the next path separator).
2497 */
2498 char_u *
2499getnextcomp(char_u *fname)
2500{
2501 while (*fname && !vim_ispathsep(*fname))
2502 MB_PTR_ADV(fname);
2503 if (*fname)
2504 ++fname;
2505 return fname;
2506}
2507
2508/*
2509 * Get a pointer to one character past the head of a path name.
2510 * Unix: after "/"; DOS: after "c:\"; Amiga: after "disk:/"; Mac: no head.
2511 * If there is no head, path is returned.
2512 */
2513 char_u *
2514get_past_head(char_u *path)
2515{
2516 char_u *retval;
2517
2518#if defined(MSWIN)
2519 // may skip "c:"
2520 if (isalpha(path[0]) && path[1] == ':')
2521 retval = path + 2;
2522 else
2523 retval = path;
2524#else
2525# if defined(AMIGA)
2526 // may skip "label:"
2527 retval = vim_strchr(path, ':');
2528 if (retval == NULL)
2529 retval = path;
2530# else // Unix
2531 retval = path;
2532# endif
2533#endif
2534
2535 while (vim_ispathsep(*retval))
2536 ++retval;
2537
2538 return retval;
2539}
2540
2541/*
2542 * Return TRUE if 'c' is a path separator.
2543 * Note that for MS-Windows this includes the colon.
2544 */
2545 int
2546vim_ispathsep(int c)
2547{
2548#ifdef UNIX
2549 return (c == '/'); // UNIX has ':' inside file names
2550#else
2551# ifdef BACKSLASH_IN_FILENAME
2552 return (c == ':' || c == '/' || c == '\\');
2553# else
2554# ifdef VMS
2555 // server"user passwd"::device:[full.path.name]fname.extension;version"
2556 return (c == ':' || c == '[' || c == ']' || c == '/'
2557 || c == '<' || c == '>' || c == '"' );
2558# else
2559 return (c == ':' || c == '/');
2560# endif // VMS
2561# endif
2562#endif
2563}
2564
2565/*
2566 * Like vim_ispathsep(c), but exclude the colon for MS-Windows.
2567 */
2568 int
2569vim_ispathsep_nocolon(int c)
2570{
2571 return vim_ispathsep(c)
2572#ifdef BACKSLASH_IN_FILENAME
2573 && c != ':'
2574#endif
2575 ;
2576}
2577
2578/*
2579 * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
2580 * It's done in-place.
2581 */
2582 void
2583shorten_dir(char_u *str)
2584{
2585 char_u *tail, *s, *d;
2586 int skip = FALSE;
2587
2588 tail = gettail(str);
2589 d = str;
2590 for (s = str; ; ++s)
2591 {
2592 if (s >= tail) // copy the whole tail
2593 {
2594 *d++ = *s;
2595 if (*s == NUL)
2596 break;
2597 }
2598 else if (vim_ispathsep(*s)) // copy '/' and next char
2599 {
2600 *d++ = *s;
2601 skip = FALSE;
2602 }
2603 else if (!skip)
2604 {
2605 *d++ = *s; // copy next char
2606 if (*s != '~' && *s != '.') // and leading "~" and "."
2607 skip = TRUE;
2608 if (has_mbyte)
2609 {
2610 int l = mb_ptr2len(s);
2611
2612 while (--l > 0)
2613 *d++ = *++s;
2614 }
2615 }
2616 }
2617}
2618
2619/*
2620 * Return TRUE if the directory of "fname" exists, FALSE otherwise.
2621 * Also returns TRUE if there is no directory name.
2622 * "fname" must be writable!.
2623 */
2624 int
2625dir_of_file_exists(char_u *fname)
2626{
2627 char_u *p;
2628 int c;
2629 int retval;
2630
2631 p = gettail_sep(fname);
2632 if (p == fname)
2633 return TRUE;
2634 c = *p;
2635 *p = NUL;
2636 retval = mch_isdir(fname);
2637 *p = c;
2638 return retval;
2639}
2640
2641/*
2642 * Versions of fnamecmp() and fnamencmp() that handle '/' and '\' equally
2643 * and deal with 'fileignorecase'.
2644 */
2645 int
2646vim_fnamecmp(char_u *x, char_u *y)
2647{
2648#ifdef BACKSLASH_IN_FILENAME
2649 return vim_fnamencmp(x, y, MAXPATHL);
2650#else
2651 if (p_fic)
2652 return MB_STRICMP(x, y);
2653 return STRCMP(x, y);
2654#endif
2655}
2656
2657 int
2658vim_fnamencmp(char_u *x, char_u *y, size_t len)
2659{
2660#ifdef BACKSLASH_IN_FILENAME
2661 char_u *px = x;
2662 char_u *py = y;
2663 int cx = NUL;
2664 int cy = NUL;
2665
2666 while (len > 0)
2667 {
2668 cx = PTR2CHAR(px);
2669 cy = PTR2CHAR(py);
2670 if (cx == NUL || cy == NUL
2671 || ((p_fic ? MB_TOLOWER(cx) != MB_TOLOWER(cy) : cx != cy)
2672 && !(cx == '/' && cy == '\\')
2673 && !(cx == '\\' && cy == '/')))
2674 break;
Bram Moolenaar1614a142019-10-06 22:00:13 +02002675 len -= mb_ptr2len(px);
2676 px += mb_ptr2len(px);
2677 py += mb_ptr2len(py);
Bram Moolenaar26262f82019-09-04 20:59:15 +02002678 }
2679 if (len == 0)
2680 return 0;
2681 return (cx - cy);
2682#else
2683 if (p_fic)
2684 return MB_STRNICMP(x, y, len);
2685 return STRNCMP(x, y, len);
2686#endif
2687}
2688
2689/*
2690 * Concatenate file names fname1 and fname2 into allocated memory.
2691 * Only add a '/' or '\\' when 'sep' is TRUE and it is necessary.
2692 */
2693 char_u *
2694concat_fnames(char_u *fname1, char_u *fname2, int sep)
2695{
2696 char_u *dest;
2697
2698 dest = alloc(STRLEN(fname1) + STRLEN(fname2) + 3);
2699 if (dest != NULL)
2700 {
2701 STRCPY(dest, fname1);
2702 if (sep)
2703 add_pathsep(dest);
2704 STRCAT(dest, fname2);
2705 }
2706 return dest;
2707}
2708
2709/*
2710 * Add a path separator to a file name, unless it already ends in a path
2711 * separator.
2712 */
2713 void
2714add_pathsep(char_u *p)
2715{
2716 if (*p != NUL && !after_pathsep(p, p + STRLEN(p)))
2717 STRCAT(p, PATHSEPSTR);
2718}
2719
2720/*
2721 * FullName_save - Make an allocated copy of a full file name.
2722 * Returns NULL when out of memory.
2723 */
2724 char_u *
2725FullName_save(
2726 char_u *fname,
2727 int force) // force expansion, even when it already looks
2728 // like a full path name
2729{
2730 char_u *buf;
2731 char_u *new_fname = NULL;
2732
2733 if (fname == NULL)
2734 return NULL;
2735
2736 buf = alloc(MAXPATHL);
2737 if (buf != NULL)
2738 {
2739 if (vim_FullName(fname, buf, MAXPATHL, force) != FAIL)
2740 new_fname = vim_strsave(buf);
2741 else
2742 new_fname = vim_strsave(fname);
2743 vim_free(buf);
2744 }
2745 return new_fname;
2746}
2747
2748/*
2749 * return TRUE if "fname" exists.
2750 */
2751 int
2752vim_fexists(char_u *fname)
2753{
2754 stat_T st;
2755
2756 if (mch_stat((char *)fname, &st))
2757 return FALSE;
2758 return TRUE;
2759}
2760
2761/*
2762 * Invoke expand_wildcards() for one pattern.
2763 * Expand items like "%:h" before the expansion.
2764 * Returns OK or FAIL.
2765 */
2766 int
2767expand_wildcards_eval(
2768 char_u **pat, // pointer to input pattern
2769 int *num_file, // resulting number of files
2770 char_u ***file, // array of resulting files
2771 int flags) // EW_DIR, etc.
2772{
2773 int ret = FAIL;
2774 char_u *eval_pat = NULL;
2775 char_u *exp_pat = *pat;
2776 char *ignored_msg;
2777 int usedlen;
2778
2779 if (*exp_pat == '%' || *exp_pat == '#' || *exp_pat == '<')
2780 {
2781 ++emsg_off;
2782 eval_pat = eval_vars(exp_pat, exp_pat, &usedlen,
2783 NULL, &ignored_msg, NULL);
2784 --emsg_off;
2785 if (eval_pat != NULL)
2786 exp_pat = concat_str(eval_pat, exp_pat + usedlen);
2787 }
2788
2789 if (exp_pat != NULL)
2790 ret = expand_wildcards(1, &exp_pat, num_file, file, flags);
2791
2792 if (eval_pat != NULL)
2793 {
2794 vim_free(exp_pat);
2795 vim_free(eval_pat);
2796 }
2797
2798 return ret;
2799}
2800
2801/*
2802 * Expand wildcards. Calls gen_expand_wildcards() and removes files matching
2803 * 'wildignore'.
2804 * Returns OK or FAIL. When FAIL then "num_files" won't be set.
2805 */
2806 int
2807expand_wildcards(
2808 int num_pat, // number of input patterns
2809 char_u **pat, // array of input patterns
2810 int *num_files, // resulting number of files
2811 char_u ***files, // array of resulting files
2812 int flags) // EW_DIR, etc.
2813{
2814 int retval;
2815 int i, j;
2816 char_u *p;
2817 int non_suf_match; // number without matching suffix
2818
2819 retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags);
2820
2821 // When keeping all matches, return here
2822 if ((flags & EW_KEEPALL) || retval == FAIL)
2823 return retval;
2824
2825#ifdef FEAT_WILDIGN
2826 /*
2827 * Remove names that match 'wildignore'.
2828 */
2829 if (*p_wig)
2830 {
2831 char_u *ffname;
2832
2833 // check all files in (*files)[]
2834 for (i = 0; i < *num_files; ++i)
2835 {
2836 ffname = FullName_save((*files)[i], FALSE);
2837 if (ffname == NULL) // out of memory
2838 break;
2839# ifdef VMS
2840 vms_remove_version(ffname);
2841# endif
2842 if (match_file_list(p_wig, (*files)[i], ffname))
2843 {
2844 // remove this matching file from the list
2845 vim_free((*files)[i]);
2846 for (j = i; j + 1 < *num_files; ++j)
2847 (*files)[j] = (*files)[j + 1];
2848 --*num_files;
2849 --i;
2850 }
2851 vim_free(ffname);
2852 }
2853
2854 // If the number of matches is now zero, we fail.
2855 if (*num_files == 0)
2856 {
2857 VIM_CLEAR(*files);
2858 return FAIL;
2859 }
2860 }
2861#endif
2862
2863 /*
2864 * Move the names where 'suffixes' match to the end.
2865 */
2866 if (*num_files > 1)
2867 {
2868 non_suf_match = 0;
2869 for (i = 0; i < *num_files; ++i)
2870 {
2871 if (!match_suffix((*files)[i]))
2872 {
2873 /*
2874 * Move the name without matching suffix to the front
2875 * of the list.
2876 */
2877 p = (*files)[i];
2878 for (j = i; j > non_suf_match; --j)
2879 (*files)[j] = (*files)[j - 1];
2880 (*files)[non_suf_match++] = p;
2881 }
2882 }
2883 }
2884
2885 return retval;
2886}
2887
2888/*
2889 * Return TRUE if "fname" matches with an entry in 'suffixes'.
2890 */
2891 int
2892match_suffix(char_u *fname)
2893{
2894 int fnamelen, setsuflen;
2895 char_u *setsuf;
2896#define MAXSUFLEN 30 // maximum length of a file suffix
2897 char_u suf_buf[MAXSUFLEN];
2898
2899 fnamelen = (int)STRLEN(fname);
2900 setsuflen = 0;
2901 for (setsuf = p_su; *setsuf; )
2902 {
2903 setsuflen = copy_option_part(&setsuf, suf_buf, MAXSUFLEN, ".,");
2904 if (setsuflen == 0)
2905 {
2906 char_u *tail = gettail(fname);
2907
2908 // empty entry: match name without a '.'
2909 if (vim_strchr(tail, '.') == NULL)
2910 {
2911 setsuflen = 1;
2912 break;
2913 }
2914 }
2915 else
2916 {
2917 if (fnamelen >= setsuflen
2918 && fnamencmp(suf_buf, fname + fnamelen - setsuflen,
2919 (size_t)setsuflen) == 0)
2920 break;
2921 setsuflen = 0;
2922 }
2923 }
2924 return (setsuflen != 0);
2925}
2926
2927#ifdef VIM_BACKTICK
2928
2929/*
2930 * Return TRUE if we can expand this backtick thing here.
2931 */
2932 static int
2933vim_backtick(char_u *p)
2934{
2935 return (*p == '`' && *(p + 1) != NUL && *(p + STRLEN(p) - 1) == '`');
2936}
2937
2938/*
2939 * Expand an item in `backticks` by executing it as a command.
2940 * Currently only works when pat[] starts and ends with a `.
2941 * Returns number of file names found, -1 if an error is encountered.
2942 */
2943 static int
2944expand_backtick(
2945 garray_T *gap,
2946 char_u *pat,
2947 int flags) // EW_* flags
2948{
2949 char_u *p;
2950 char_u *cmd;
2951 char_u *buffer;
2952 int cnt = 0;
2953 int i;
2954
2955 // Create the command: lop off the backticks.
2956 cmd = vim_strnsave(pat + 1, (int)STRLEN(pat) - 2);
2957 if (cmd == NULL)
2958 return -1;
2959
2960#ifdef FEAT_EVAL
2961 if (*cmd == '=') // `={expr}`: Expand expression
2962 buffer = eval_to_string(cmd + 1, &p, TRUE);
2963 else
2964#endif
2965 buffer = get_cmd_output(cmd, NULL,
2966 (flags & EW_SILENT) ? SHELL_SILENT : 0, NULL);
2967 vim_free(cmd);
2968 if (buffer == NULL)
2969 return -1;
2970
2971 cmd = buffer;
2972 while (*cmd != NUL)
2973 {
2974 cmd = skipwhite(cmd); // skip over white space
2975 p = cmd;
2976 while (*p != NUL && *p != '\r' && *p != '\n') // skip over entry
2977 ++p;
2978 // add an entry if it is not empty
2979 if (p > cmd)
2980 {
2981 i = *p;
2982 *p = NUL;
2983 addfile(gap, cmd, flags);
2984 *p = i;
2985 ++cnt;
2986 }
2987 cmd = p;
2988 while (*cmd != NUL && (*cmd == '\r' || *cmd == '\n'))
2989 ++cmd;
2990 }
2991
2992 vim_free(buffer);
2993 return cnt;
2994}
2995#endif // VIM_BACKTICK
2996
2997#if defined(MSWIN)
2998/*
2999 * File name expansion code for MS-DOS, Win16 and Win32. It's here because
3000 * it's shared between these systems.
3001 */
3002
3003/*
3004 * comparison function for qsort in dos_expandpath()
3005 */
3006 static int
3007pstrcmp(const void *a, const void *b)
3008{
3009 return (pathcmp(*(char **)a, *(char **)b, -1));
3010}
3011
3012/*
3013 * Recursively expand one path component into all matching files and/or
3014 * directories. Adds matches to "gap". Handles "*", "?", "[a-z]", "**", etc.
3015 * Return the number of matches found.
3016 * "path" has backslashes before chars that are not to be expanded, starting
3017 * at "path[wildoff]".
3018 * Return the number of matches found.
3019 * NOTE: much of this is identical to unix_expandpath(), keep in sync!
3020 */
3021 static int
3022dos_expandpath(
3023 garray_T *gap,
3024 char_u *path,
3025 int wildoff,
3026 int flags, // EW_* flags
3027 int didstar) // expanded "**" once already
3028{
3029 char_u *buf;
3030 char_u *path_end;
3031 char_u *p, *s, *e;
3032 int start_len = gap->ga_len;
3033 char_u *pat;
3034 regmatch_T regmatch;
3035 int starts_with_dot;
3036 int matches;
3037 int len;
3038 int starstar = FALSE;
3039 static int stardepth = 0; // depth for "**" expansion
3040 HANDLE hFind = INVALID_HANDLE_VALUE;
3041 WIN32_FIND_DATAW wfb;
3042 WCHAR *wn = NULL; // UCS-2 name, NULL when not used.
3043 char_u *matchname;
3044 int ok;
3045
3046 // Expanding "**" may take a long time, check for CTRL-C.
3047 if (stardepth > 0)
3048 {
3049 ui_breakcheck();
3050 if (got_int)
3051 return 0;
3052 }
3053
3054 // Make room for file name. When doing encoding conversion the actual
3055 // length may be quite a bit longer, thus use the maximum possible length.
3056 buf = alloc(MAXPATHL);
3057 if (buf == NULL)
3058 return 0;
3059
3060 /*
3061 * Find the first part in the path name that contains a wildcard or a ~1.
3062 * Copy it into buf, including the preceding characters.
3063 */
3064 p = buf;
3065 s = buf;
3066 e = NULL;
3067 path_end = path;
3068 while (*path_end != NUL)
3069 {
3070 // May ignore a wildcard that has a backslash before it; it will
3071 // be removed by rem_backslash() or file_pat_to_reg_pat() below.
3072 if (path_end >= path + wildoff && rem_backslash(path_end))
3073 *p++ = *path_end++;
3074 else if (*path_end == '\\' || *path_end == ':' || *path_end == '/')
3075 {
3076 if (e != NULL)
3077 break;
3078 s = p + 1;
3079 }
3080 else if (path_end >= path + wildoff
3081 && vim_strchr((char_u *)"*?[~", *path_end) != NULL)
3082 e = p;
3083 if (has_mbyte)
3084 {
3085 len = (*mb_ptr2len)(path_end);
3086 STRNCPY(p, path_end, len);
3087 p += len;
3088 path_end += len;
3089 }
3090 else
3091 *p++ = *path_end++;
3092 }
3093 e = p;
3094 *e = NUL;
3095
3096 // now we have one wildcard component between s and e
3097 // Remove backslashes between "wildoff" and the start of the wildcard
3098 // component.
3099 for (p = buf + wildoff; p < s; ++p)
3100 if (rem_backslash(p))
3101 {
3102 STRMOVE(p, p + 1);
3103 --e;
3104 --s;
3105 }
3106
3107 // Check for "**" between "s" and "e".
3108 for (p = s; p < e; ++p)
3109 if (p[0] == '*' && p[1] == '*')
3110 starstar = TRUE;
3111
3112 starts_with_dot = *s == '.';
3113 pat = file_pat_to_reg_pat(s, e, NULL, FALSE);
3114 if (pat == NULL)
3115 {
3116 vim_free(buf);
3117 return 0;
3118 }
3119
3120 // compile the regexp into a program
3121 if (flags & (EW_NOERROR | EW_NOTWILD))
3122 ++emsg_silent;
3123 regmatch.rm_ic = TRUE; // Always ignore case
3124 regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
3125 if (flags & (EW_NOERROR | EW_NOTWILD))
3126 --emsg_silent;
3127 vim_free(pat);
3128
3129 if (regmatch.regprog == NULL && (flags & EW_NOTWILD) == 0)
3130 {
3131 vim_free(buf);
3132 return 0;
3133 }
3134
3135 // remember the pattern or file name being looked for
3136 matchname = vim_strsave(s);
3137
3138 // If "**" is by itself, this is the first time we encounter it and more
3139 // is following then find matches without any directory.
3140 if (!didstar && stardepth < 100 && starstar && e - s == 2
3141 && *path_end == '/')
3142 {
3143 STRCPY(s, path_end + 1);
3144 ++stardepth;
3145 (void)dos_expandpath(gap, buf, (int)(s - buf), flags, TRUE);
3146 --stardepth;
3147 }
3148
3149 // Scan all files in the directory with "dir/ *.*"
3150 STRCPY(s, "*.*");
3151 wn = enc_to_utf16(buf, NULL);
3152 if (wn != NULL)
3153 hFind = FindFirstFileW(wn, &wfb);
3154 ok = (hFind != INVALID_HANDLE_VALUE);
3155
3156 while (ok)
3157 {
3158 p = utf16_to_enc(wfb.cFileName, NULL); // p is allocated here
3159 if (p == NULL)
3160 break; // out of memory
3161
3162 // Ignore entries starting with a dot, unless when asked for. Accept
3163 // all entries found with "matchname".
3164 if ((p[0] != '.' || starts_with_dot
3165 || ((flags & EW_DODOT)
3166 && p[1] != NUL && (p[1] != '.' || p[2] != NUL)))
3167 && (matchname == NULL
3168 || (regmatch.regprog != NULL
3169 && vim_regexec(&regmatch, p, (colnr_T)0))
3170 || ((flags & EW_NOTWILD)
3171 && fnamencmp(path + (s - buf), p, e - s) == 0)))
3172 {
3173 STRCPY(s, p);
3174 len = (int)STRLEN(buf);
3175
3176 if (starstar && stardepth < 100)
3177 {
3178 // For "**" in the pattern first go deeper in the tree to
3179 // find matches.
3180 STRCPY(buf + len, "/**");
3181 STRCPY(buf + len + 3, path_end);
3182 ++stardepth;
3183 (void)dos_expandpath(gap, buf, len + 1, flags, TRUE);
3184 --stardepth;
3185 }
3186
3187 STRCPY(buf + len, path_end);
3188 if (mch_has_exp_wildcard(path_end))
3189 {
3190 // need to expand another component of the path
3191 // remove backslashes for the remaining components only
3192 (void)dos_expandpath(gap, buf, len + 1, flags, FALSE);
3193 }
3194 else
3195 {
3196 // no more wildcards, check if there is a match
3197 // remove backslashes for the remaining components only
3198 if (*path_end != 0)
3199 backslash_halve(buf + len + 1);
3200 if (mch_getperm(buf) >= 0) // add existing file
3201 addfile(gap, buf, flags);
3202 }
3203 }
3204
3205 vim_free(p);
3206 ok = FindNextFileW(hFind, &wfb);
3207
3208 // If no more matches and no match was used, try expanding the name
3209 // itself. Finds the long name of a short filename.
3210 if (!ok && matchname != NULL && gap->ga_len == start_len)
3211 {
3212 STRCPY(s, matchname);
3213 FindClose(hFind);
3214 vim_free(wn);
3215 wn = enc_to_utf16(buf, NULL);
3216 if (wn != NULL)
3217 hFind = FindFirstFileW(wn, &wfb);
3218 else
3219 hFind = INVALID_HANDLE_VALUE;
3220 ok = (hFind != INVALID_HANDLE_VALUE);
3221 VIM_CLEAR(matchname);
3222 }
3223 }
3224
3225 FindClose(hFind);
3226 vim_free(wn);
3227 vim_free(buf);
3228 vim_regfree(regmatch.regprog);
3229 vim_free(matchname);
3230
3231 matches = gap->ga_len - start_len;
3232 if (matches > 0)
3233 qsort(((char_u **)gap->ga_data) + start_len, (size_t)matches,
3234 sizeof(char_u *), pstrcmp);
3235 return matches;
3236}
3237
3238 int
3239mch_expandpath(
3240 garray_T *gap,
3241 char_u *path,
3242 int flags) // EW_* flags
3243{
3244 return dos_expandpath(gap, path, 0, flags, FALSE);
3245}
3246#endif // MSWIN
3247
3248#if (defined(UNIX) && !defined(VMS)) || defined(USE_UNIXFILENAME) \
3249 || defined(PROTO)
3250/*
3251 * Unix style wildcard expansion code.
3252 * It's here because it's used both for Unix and Mac.
3253 */
3254 static int
3255pstrcmp(const void *a, const void *b)
3256{
3257 return (pathcmp(*(char **)a, *(char **)b, -1));
3258}
3259
3260/*
3261 * Recursively expand one path component into all matching files and/or
3262 * directories. Adds matches to "gap". Handles "*", "?", "[a-z]", "**", etc.
3263 * "path" has backslashes before chars that are not to be expanded, starting
3264 * at "path + wildoff".
3265 * Return the number of matches found.
3266 * NOTE: much of this is identical to dos_expandpath(), keep in sync!
3267 */
3268 int
3269unix_expandpath(
3270 garray_T *gap,
3271 char_u *path,
3272 int wildoff,
3273 int flags, // EW_* flags
3274 int didstar) // expanded "**" once already
3275{
3276 char_u *buf;
3277 char_u *path_end;
3278 char_u *p, *s, *e;
3279 int start_len = gap->ga_len;
3280 char_u *pat;
3281 regmatch_T regmatch;
3282 int starts_with_dot;
3283 int matches;
3284 int len;
3285 int starstar = FALSE;
3286 static int stardepth = 0; // depth for "**" expansion
3287
3288 DIR *dirp;
3289 struct dirent *dp;
3290
3291 // Expanding "**" may take a long time, check for CTRL-C.
3292 if (stardepth > 0)
3293 {
3294 ui_breakcheck();
3295 if (got_int)
3296 return 0;
3297 }
3298
3299 // make room for file name
3300 buf = alloc(STRLEN(path) + BASENAMELEN + 5);
3301 if (buf == NULL)
3302 return 0;
3303
3304 /*
3305 * Find the first part in the path name that contains a wildcard.
3306 * When EW_ICASE is set every letter is considered to be a wildcard.
3307 * Copy it into "buf", including the preceding characters.
3308 */
3309 p = buf;
3310 s = buf;
3311 e = NULL;
3312 path_end = path;
3313 while (*path_end != NUL)
3314 {
3315 // May ignore a wildcard that has a backslash before it; it will
3316 // be removed by rem_backslash() or file_pat_to_reg_pat() below.
3317 if (path_end >= path + wildoff && rem_backslash(path_end))
3318 *p++ = *path_end++;
3319 else if (*path_end == '/')
3320 {
3321 if (e != NULL)
3322 break;
3323 s = p + 1;
3324 }
3325 else if (path_end >= path + wildoff
3326 && (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL
3327 || (!p_fic && (flags & EW_ICASE)
3328 && isalpha(PTR2CHAR(path_end)))))
3329 e = p;
3330 if (has_mbyte)
3331 {
3332 len = (*mb_ptr2len)(path_end);
3333 STRNCPY(p, path_end, len);
3334 p += len;
3335 path_end += len;
3336 }
3337 else
3338 *p++ = *path_end++;
3339 }
3340 e = p;
3341 *e = NUL;
3342
3343 // Now we have one wildcard component between "s" and "e".
3344 // Remove backslashes between "wildoff" and the start of the wildcard
3345 // component.
3346 for (p = buf + wildoff; p < s; ++p)
3347 if (rem_backslash(p))
3348 {
3349 STRMOVE(p, p + 1);
3350 --e;
3351 --s;
3352 }
3353
3354 // Check for "**" between "s" and "e".
3355 for (p = s; p < e; ++p)
3356 if (p[0] == '*' && p[1] == '*')
3357 starstar = TRUE;
3358
3359 // convert the file pattern to a regexp pattern
3360 starts_with_dot = *s == '.';
3361 pat = file_pat_to_reg_pat(s, e, NULL, FALSE);
3362 if (pat == NULL)
3363 {
3364 vim_free(buf);
3365 return 0;
3366 }
3367
3368 // compile the regexp into a program
3369 if (flags & EW_ICASE)
3370 regmatch.rm_ic = TRUE; // 'wildignorecase' set
3371 else
3372 regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set
3373 if (flags & (EW_NOERROR | EW_NOTWILD))
3374 ++emsg_silent;
3375 regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
3376 if (flags & (EW_NOERROR | EW_NOTWILD))
3377 --emsg_silent;
3378 vim_free(pat);
3379
3380 if (regmatch.regprog == NULL && (flags & EW_NOTWILD) == 0)
3381 {
3382 vim_free(buf);
3383 return 0;
3384 }
3385
3386 // If "**" is by itself, this is the first time we encounter it and more
3387 // is following then find matches without any directory.
3388 if (!didstar && stardepth < 100 && starstar && e - s == 2
3389 && *path_end == '/')
3390 {
3391 STRCPY(s, path_end + 1);
3392 ++stardepth;
3393 (void)unix_expandpath(gap, buf, (int)(s - buf), flags, TRUE);
3394 --stardepth;
3395 }
3396
3397 // open the directory for scanning
3398 *s = NUL;
3399 dirp = opendir(*buf == NUL ? "." : (char *)buf);
3400
3401 // Find all matching entries
3402 if (dirp != NULL)
3403 {
3404 for (;;)
3405 {
3406 dp = readdir(dirp);
3407 if (dp == NULL)
3408 break;
3409 if ((dp->d_name[0] != '.' || starts_with_dot
3410 || ((flags & EW_DODOT)
3411 && dp->d_name[1] != NUL
3412 && (dp->d_name[1] != '.' || dp->d_name[2] != NUL)))
3413 && ((regmatch.regprog != NULL && vim_regexec(&regmatch,
3414 (char_u *)dp->d_name, (colnr_T)0))
3415 || ((flags & EW_NOTWILD)
3416 && fnamencmp(path + (s - buf), dp->d_name, e - s) == 0)))
3417 {
3418 STRCPY(s, dp->d_name);
3419 len = STRLEN(buf);
3420
3421 if (starstar && stardepth < 100)
3422 {
3423 // For "**" in the pattern first go deeper in the tree to
3424 // find matches.
3425 STRCPY(buf + len, "/**");
3426 STRCPY(buf + len + 3, path_end);
3427 ++stardepth;
3428 (void)unix_expandpath(gap, buf, len + 1, flags, TRUE);
3429 --stardepth;
3430 }
3431
3432 STRCPY(buf + len, path_end);
3433 if (mch_has_exp_wildcard(path_end)) // handle more wildcards
3434 {
3435 // need to expand another component of the path
3436 // remove backslashes for the remaining components only
3437 (void)unix_expandpath(gap, buf, len + 1, flags, FALSE);
3438 }
3439 else
3440 {
3441 stat_T sb;
3442
3443 // no more wildcards, check if there is a match
3444 // remove backslashes for the remaining components only
3445 if (*path_end != NUL)
3446 backslash_halve(buf + len + 1);
3447 // add existing file or symbolic link
3448 if ((flags & EW_ALLLINKS) ? mch_lstat((char *)buf, &sb) >= 0
3449 : mch_getperm(buf) >= 0)
3450 {
3451#ifdef MACOS_CONVERT
3452 size_t precomp_len = STRLEN(buf)+1;
3453 char_u *precomp_buf =
3454 mac_precompose_path(buf, precomp_len, &precomp_len);
3455
3456 if (precomp_buf)
3457 {
3458 mch_memmove(buf, precomp_buf, precomp_len);
3459 vim_free(precomp_buf);
3460 }
3461#endif
3462 addfile(gap, buf, flags);
3463 }
3464 }
3465 }
3466 }
3467
3468 closedir(dirp);
3469 }
3470
3471 vim_free(buf);
3472 vim_regfree(regmatch.regprog);
3473
3474 matches = gap->ga_len - start_len;
3475 if (matches > 0)
3476 qsort(((char_u **)gap->ga_data) + start_len, matches,
3477 sizeof(char_u *), pstrcmp);
3478 return matches;
3479}
3480#endif
3481
3482/*
3483 * Return TRUE if "p" contains what looks like an environment variable.
3484 * Allowing for escaping.
3485 */
3486 static int
3487has_env_var(char_u *p)
3488{
3489 for ( ; *p; MB_PTR_ADV(p))
3490 {
3491 if (*p == '\\' && p[1] != NUL)
3492 ++p;
3493 else if (vim_strchr((char_u *)
3494#if defined(MSWIN)
3495 "$%"
3496#else
3497 "$"
3498#endif
3499 , *p) != NULL)
3500 return TRUE;
3501 }
3502 return FALSE;
3503}
3504
3505#ifdef SPECIAL_WILDCHAR
3506/*
3507 * Return TRUE if "p" contains a special wildcard character, one that Vim
3508 * cannot expand, requires using a shell.
3509 */
3510 static int
3511has_special_wildchar(char_u *p)
3512{
3513 for ( ; *p; MB_PTR_ADV(p))
3514 {
3515 // Disallow line break characters.
3516 if (*p == '\r' || *p == '\n')
3517 break;
3518 // Allow for escaping.
3519 if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n')
3520 ++p;
3521 else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL)
3522 {
3523 // A { must be followed by a matching }.
3524 if (*p == '{' && vim_strchr(p, '}') == NULL)
3525 continue;
3526 // A quote and backtick must be followed by another one.
3527 if ((*p == '`' || *p == '\'') && vim_strchr(p, *p) == NULL)
3528 continue;
3529 return TRUE;
3530 }
3531 }
3532 return FALSE;
3533}
3534#endif
3535
3536/*
3537 * Generic wildcard expansion code.
3538 *
3539 * Characters in "pat" that should not be expanded must be preceded with a
3540 * backslash. E.g., "/path\ with\ spaces/my\*star*"
3541 *
3542 * Return FAIL when no single file was found. In this case "num_file" is not
3543 * set, and "file" may contain an error message.
3544 * Return OK when some files found. "num_file" is set to the number of
3545 * matches, "file" to the array of matches. Call FreeWild() later.
3546 */
3547 int
3548gen_expand_wildcards(
3549 int num_pat, // number of input patterns
3550 char_u **pat, // array of input patterns
3551 int *num_file, // resulting number of files
3552 char_u ***file, // array of resulting files
3553 int flags) // EW_* flags
3554{
3555 int i;
3556 garray_T ga;
3557 char_u *p;
3558 static int recursive = FALSE;
3559 int add_pat;
3560 int retval = OK;
3561#if defined(FEAT_SEARCHPATH)
3562 int did_expand_in_path = FALSE;
3563#endif
3564
3565 /*
3566 * expand_env() is called to expand things like "~user". If this fails,
3567 * it calls ExpandOne(), which brings us back here. In this case, always
3568 * call the machine specific expansion function, if possible. Otherwise,
3569 * return FAIL.
3570 */
3571 if (recursive)
3572#ifdef SPECIAL_WILDCHAR
3573 return mch_expand_wildcards(num_pat, pat, num_file, file, flags);
3574#else
3575 return FAIL;
3576#endif
3577
3578#ifdef SPECIAL_WILDCHAR
3579 /*
3580 * If there are any special wildcard characters which we cannot handle
3581 * here, call machine specific function for all the expansion. This
3582 * avoids starting the shell for each argument separately.
3583 * For `=expr` do use the internal function.
3584 */
3585 for (i = 0; i < num_pat; i++)
3586 {
3587 if (has_special_wildchar(pat[i])
3588# ifdef VIM_BACKTICK
3589 && !(vim_backtick(pat[i]) && pat[i][1] == '=')
3590# endif
3591 )
3592 return mch_expand_wildcards(num_pat, pat, num_file, file, flags);
3593 }
3594#endif
3595
3596 recursive = TRUE;
3597
3598 /*
3599 * The matching file names are stored in a growarray. Init it empty.
3600 */
3601 ga_init2(&ga, (int)sizeof(char_u *), 30);
3602
3603 for (i = 0; i < num_pat; ++i)
3604 {
3605 add_pat = -1;
3606 p = pat[i];
3607
3608#ifdef VIM_BACKTICK
3609 if (vim_backtick(p))
3610 {
3611 add_pat = expand_backtick(&ga, p, flags);
3612 if (add_pat == -1)
3613 retval = FAIL;
3614 }
3615 else
3616#endif
3617 {
3618 /*
3619 * First expand environment variables, "~/" and "~user/".
3620 */
3621 if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~')
3622 {
3623 p = expand_env_save_opt(p, TRUE);
3624 if (p == NULL)
3625 p = pat[i];
3626#ifdef UNIX
3627 /*
3628 * On Unix, if expand_env() can't expand an environment
3629 * variable, use the shell to do that. Discard previously
3630 * found file names and start all over again.
3631 */
3632 else if (has_env_var(p) || *p == '~')
3633 {
3634 vim_free(p);
3635 ga_clear_strings(&ga);
3636 i = mch_expand_wildcards(num_pat, pat, num_file, file,
3637 flags|EW_KEEPDOLLAR);
3638 recursive = FALSE;
3639 return i;
3640 }
3641#endif
3642 }
3643
3644 /*
3645 * If there are wildcards: Expand file names and add each match to
3646 * the list. If there is no match, and EW_NOTFOUND is given, add
3647 * the pattern.
3648 * If there are no wildcards: Add the file name if it exists or
3649 * when EW_NOTFOUND is given.
3650 */
3651 if (mch_has_exp_wildcard(p))
3652 {
3653#if defined(FEAT_SEARCHPATH)
3654 if ((flags & EW_PATH)
3655 && !mch_isFullName(p)
3656 && !(p[0] == '.'
3657 && (vim_ispathsep(p[1])
3658 || (p[1] == '.' && vim_ispathsep(p[2]))))
3659 )
3660 {
3661 // :find completion where 'path' is used.
3662 // Recursiveness is OK here.
3663 recursive = FALSE;
3664 add_pat = expand_in_path(&ga, p, flags);
3665 recursive = TRUE;
3666 did_expand_in_path = TRUE;
3667 }
3668 else
3669#endif
3670 add_pat = mch_expandpath(&ga, p, flags);
3671 }
3672 }
3673
3674 if (add_pat == -1 || (add_pat == 0 && (flags & EW_NOTFOUND)))
3675 {
3676 char_u *t = backslash_halve_save(p);
3677
3678 // When EW_NOTFOUND is used, always add files and dirs. Makes
3679 // "vim c:/" work.
3680 if (flags & EW_NOTFOUND)
3681 addfile(&ga, t, flags | EW_DIR | EW_FILE);
3682 else
3683 addfile(&ga, t, flags);
3684
3685 if (t != p)
3686 vim_free(t);
3687 }
3688
3689#if defined(FEAT_SEARCHPATH)
3690 if (did_expand_in_path && ga.ga_len > 0 && (flags & EW_PATH))
3691 uniquefy_paths(&ga, p);
3692#endif
3693 if (p != pat[i])
3694 vim_free(p);
3695 }
3696
3697 *num_file = ga.ga_len;
3698 *file = (ga.ga_data != NULL) ? (char_u **)ga.ga_data : (char_u **)"";
3699
3700 recursive = FALSE;
3701
3702 return ((flags & EW_EMPTYOK) || ga.ga_data != NULL) ? retval : FAIL;
3703}
3704
3705/*
3706 * Add a file to a file list. Accepted flags:
3707 * EW_DIR add directories
3708 * EW_FILE add files
3709 * EW_EXEC add executable files
3710 * EW_NOTFOUND add even when it doesn't exist
3711 * EW_ADDSLASH add slash after directory name
3712 * EW_ALLLINKS add symlink also when the referred file does not exist
3713 */
3714 void
3715addfile(
3716 garray_T *gap,
Bram Moolenaar217e1b82019-12-01 21:41:28 +01003717 char_u *f, // filename
Bram Moolenaar26262f82019-09-04 20:59:15 +02003718 int flags)
3719{
3720 char_u *p;
3721 int isdir;
3722 stat_T sb;
3723
3724 // if the file/dir/link doesn't exist, may not add it
3725 if (!(flags & EW_NOTFOUND) && ((flags & EW_ALLLINKS)
3726 ? mch_lstat((char *)f, &sb) < 0 : mch_getperm(f) < 0))
3727 return;
3728
3729#ifdef FNAME_ILLEGAL
3730 // if the file/dir contains illegal characters, don't add it
3731 if (vim_strpbrk(f, (char_u *)FNAME_ILLEGAL) != NULL)
3732 return;
3733#endif
3734
3735 isdir = mch_isdir(f);
3736 if ((isdir && !(flags & EW_DIR)) || (!isdir && !(flags & EW_FILE)))
3737 return;
3738
3739 // If the file isn't executable, may not add it. Do accept directories.
3740 // When invoked from expand_shellcmd() do not use $PATH.
3741 if (!isdir && (flags & EW_EXEC)
3742 && !mch_can_exe(f, NULL, !(flags & EW_SHELLCMD)))
3743 return;
3744
3745 // Make room for another item in the file list.
3746 if (ga_grow(gap, 1) == FAIL)
3747 return;
3748
3749 p = alloc(STRLEN(f) + 1 + isdir);
3750 if (p == NULL)
3751 return;
3752
3753 STRCPY(p, f);
3754#ifdef BACKSLASH_IN_FILENAME
3755 slash_adjust(p);
3756#endif
3757 /*
3758 * Append a slash or backslash after directory names if none is present.
3759 */
3760#ifndef DONT_ADD_PATHSEP_TO_DIR
3761 if (isdir && (flags & EW_ADDSLASH))
3762 add_pathsep(p);
3763#endif
3764 ((char_u **)gap->ga_data)[gap->ga_len++] = p;
3765}
3766
3767/*
3768 * Free the list of files returned by expand_wildcards() or other expansion
3769 * functions.
3770 */
3771 void
3772FreeWild(int count, char_u **files)
3773{
3774 if (count <= 0 || files == NULL)
3775 return;
3776 while (count--)
3777 vim_free(files[count]);
3778 vim_free(files);
3779}
3780
3781/*
3782 * Compare path "p[]" to "q[]".
3783 * If "maxlen" >= 0 compare "p[maxlen]" to "q[maxlen]"
3784 * Return value like strcmp(p, q), but consider path separators.
3785 */
3786 int
3787pathcmp(const char *p, const char *q, int maxlen)
3788{
3789 int i, j;
3790 int c1, c2;
3791 const char *s = NULL;
3792
3793 for (i = 0, j = 0; maxlen < 0 || (i < maxlen && j < maxlen);)
3794 {
3795 c1 = PTR2CHAR((char_u *)p + i);
3796 c2 = PTR2CHAR((char_u *)q + j);
3797
3798 // End of "p": check if "q" also ends or just has a slash.
3799 if (c1 == NUL)
3800 {
3801 if (c2 == NUL) // full match
3802 return 0;
3803 s = q;
3804 i = j;
3805 break;
3806 }
3807
3808 // End of "q": check if "p" just has a slash.
3809 if (c2 == NUL)
3810 {
3811 s = p;
3812 break;
3813 }
3814
3815 if ((p_fic ? MB_TOUPPER(c1) != MB_TOUPPER(c2) : c1 != c2)
3816#ifdef BACKSLASH_IN_FILENAME
3817 // consider '/' and '\\' to be equal
3818 && !((c1 == '/' && c2 == '\\')
3819 || (c1 == '\\' && c2 == '/'))
3820#endif
3821 )
3822 {
3823 if (vim_ispathsep(c1))
3824 return -1;
3825 if (vim_ispathsep(c2))
3826 return 1;
3827 return p_fic ? MB_TOUPPER(c1) - MB_TOUPPER(c2)
3828 : c1 - c2; // no match
3829 }
3830
Bram Moolenaar1614a142019-10-06 22:00:13 +02003831 i += mb_ptr2len((char_u *)p + i);
3832 j += mb_ptr2len((char_u *)q + j);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003833 }
3834 if (s == NULL) // "i" or "j" ran into "maxlen"
3835 return 0;
3836
3837 c1 = PTR2CHAR((char_u *)s + i);
Bram Moolenaar1614a142019-10-06 22:00:13 +02003838 c2 = PTR2CHAR((char_u *)s + i + mb_ptr2len((char_u *)s + i));
Bram Moolenaar26262f82019-09-04 20:59:15 +02003839 // ignore a trailing slash, but not "//" or ":/"
3840 if (c2 == NUL
3841 && i > 0
3842 && !after_pathsep((char_u *)s, (char_u *)s + i)
3843#ifdef BACKSLASH_IN_FILENAME
3844 && (c1 == '/' || c1 == '\\')
3845#else
3846 && c1 == '/'
3847#endif
3848 )
3849 return 0; // match with trailing slash
3850 if (s == q)
3851 return -1; // no match
3852 return 1;
3853}
3854
3855/*
3856 * Return TRUE if "name" is a full (absolute) path name or URL.
3857 */
3858 int
3859vim_isAbsName(char_u *name)
3860{
3861 return (path_with_url(name) != 0 || mch_isFullName(name));
3862}
3863
3864/*
3865 * Get absolute file name into buffer "buf[len]".
3866 *
3867 * return FAIL for failure, OK otherwise
3868 */
3869 int
3870vim_FullName(
3871 char_u *fname,
3872 char_u *buf,
3873 int len,
3874 int force) // force expansion even when already absolute
3875{
3876 int retval = OK;
3877 int url;
3878
3879 *buf = NUL;
3880 if (fname == NULL)
3881 return FAIL;
3882
3883 url = path_with_url(fname);
3884 if (!url)
3885 retval = mch_FullName(fname, buf, len, force);
3886 if (url || retval == FAIL)
3887 {
3888 // something failed; use the file name (truncate when too long)
3889 vim_strncpy(buf, fname, len - 1);
3890 }
3891#if defined(MSWIN)
3892 slash_adjust(buf);
3893#endif
3894 return retval;
3895}