fs.c (52352B)
1 // eval/fs.c: Filesystem related builtin functions 2 3 #include <assert.h> 4 #include <limits.h> 5 #include <stdbool.h> 6 #include <stddef.h> 7 #include <stdint.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <sys/stat.h> 12 13 #include "auto/config.h" 14 #include "nvim/ascii_defs.h" 15 #include "nvim/buffer_defs.h" 16 #include "nvim/cmdexpand.h" 17 #include "nvim/cmdexpand_defs.h" 18 #include "nvim/errors.h" 19 #include "nvim/eval.h" 20 #include "nvim/eval/fs.h" 21 #include "nvim/eval/typval.h" 22 #include "nvim/eval/userfunc.h" 23 #include "nvim/eval/vars.h" 24 #include "nvim/eval/window.h" 25 #include "nvim/ex_cmds.h" 26 #include "nvim/ex_docmd.h" 27 #include "nvim/file_search.h" 28 #include "nvim/fileio.h" 29 #include "nvim/garray.h" 30 #include "nvim/garray_defs.h" 31 #include "nvim/gettext_defs.h" 32 #include "nvim/globals.h" 33 #include "nvim/macros_defs.h" 34 #include "nvim/mbyte.h" 35 #include "nvim/memory.h" 36 #include "nvim/message.h" 37 #include "nvim/option_vars.h" 38 #include "nvim/os/fileio.h" 39 #include "nvim/os/fileio_defs.h" 40 #include "nvim/os/fs.h" 41 #include "nvim/os/fs_defs.h" 42 #include "nvim/os/os.h" 43 #include "nvim/path.h" 44 #include "nvim/pos_defs.h" 45 #include "nvim/strings.h" 46 #include "nvim/types_defs.h" 47 #include "nvim/vim_defs.h" 48 #include "nvim/window.h" 49 50 #include "eval/fs.c.generated.h" 51 52 static const char e_error_while_writing_str[] = N_("E80: Error while writing: %s"); 53 54 /// Adjust a filename, according to a string of modifiers. 55 /// *fnamep must be NUL terminated when called. When returning, the length is 56 /// determined by *fnamelen. 57 /// Returns VALID_ flags or -1 for failure. 58 /// When there is an error, *fnamep is set to NULL. 59 /// 60 /// @param src string with modifiers 61 /// @param tilde_file "~" is a file name, not $HOME 62 /// @param usedlen characters after src that are used 63 /// @param fnamep file name so far 64 /// @param bufp buffer for allocated file name or NULL 65 /// @param fnamelen length of fnamep 66 int modify_fname(char *src, bool tilde_file, size_t *usedlen, char **fnamep, char **bufp, 67 size_t *fnamelen) 68 { 69 int valid = 0; 70 char *s, *p, *pbuf; 71 char dirname[MAXPATHL]; 72 bool has_fullname = false; 73 bool has_homerelative = false; 74 75 repeat: 76 // ":p" - full path/file_name 77 if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') { 78 has_fullname = true; 79 80 valid |= VALID_PATH; 81 *usedlen += 2; 82 83 // Expand "~/path" for all systems and "~user/path" for Unix 84 if ((*fnamep)[0] == '~' 85 #if !defined(UNIX) 86 && ((*fnamep)[1] == '/' 87 # ifdef BACKSLASH_IN_FILENAME 88 || (*fnamep)[1] == '\\' 89 # endif 90 || (*fnamep)[1] == NUL) 91 #endif 92 && !(tilde_file && (*fnamep)[1] == NUL)) { 93 *fnamep = expand_env_save(*fnamep); 94 xfree(*bufp); // free any allocated file name 95 *bufp = *fnamep; 96 if (*fnamep == NULL) { 97 return -1; 98 } 99 } 100 101 // When "/." or "/.." is used: force expansion to get rid of it. 102 for (p = *fnamep; *p != NUL; MB_PTR_ADV(p)) { 103 if (vim_ispathsep(*p) 104 && p[1] == '.' 105 && (p[2] == NUL 106 || vim_ispathsep(p[2]) 107 || (p[2] == '.' 108 && (p[3] == NUL || vim_ispathsep(p[3]))))) { 109 break; 110 } 111 } 112 113 // FullName_save() is slow, don't use it when not needed. 114 if (*p != NUL || !vim_isAbsName(*fnamep) 115 #ifdef MSWIN // enforce drive letter on Windows paths 116 || **fnamep == '/' || **fnamep == '\\' 117 #endif 118 ) { 119 *fnamep = FullName_save(*fnamep, *p != NUL); 120 xfree(*bufp); // free any allocated file name 121 *bufp = *fnamep; 122 if (*fnamep == NULL) { 123 return -1; 124 } 125 } 126 127 // Append a path separator to a directory. 128 if (os_isdir(*fnamep)) { 129 // Make room for one or two extra characters. 130 *fnamep = xstrnsave(*fnamep, strlen(*fnamep) + 2); 131 xfree(*bufp); // free any allocated file name 132 *bufp = *fnamep; 133 add_pathsep(*fnamep); 134 } 135 } 136 137 int c; 138 139 // ":." - path relative to the current directory 140 // ":~" - path relative to the home directory 141 // ":8" - shortname path - postponed till after 142 while (src[*usedlen] == ':' 143 && ((c = (uint8_t)src[*usedlen + 1]) == '.' || c == '~' || c == '8')) { 144 *usedlen += 2; 145 if (c == '8') { 146 continue; 147 } 148 pbuf = NULL; 149 // Need full path first (use expand_env() to remove a "~/") 150 if (!has_fullname && !has_homerelative) { 151 if (**fnamep == '~') { 152 p = pbuf = expand_env_save(*fnamep); 153 } else { 154 p = pbuf = FullName_save(*fnamep, false); 155 } 156 } else { 157 p = *fnamep; 158 } 159 160 has_fullname = false; 161 162 if (p != NULL) { 163 if (c == '.') { 164 os_dirname(dirname, MAXPATHL); 165 if (has_homerelative) { 166 s = xstrdup(dirname); 167 home_replace(NULL, s, dirname, MAXPATHL, true); 168 xfree(s); 169 } 170 size_t namelen = strlen(dirname); 171 172 // Do not call shorten_fname() here since it removes the prefix 173 // even though the path does not have a prefix. 174 if (path_fnamencmp(p, dirname, namelen) == 0) { 175 p += namelen; 176 if (vim_ispathsep(*p)) { 177 while (*p && vim_ispathsep(*p)) { 178 p++; 179 } 180 *fnamep = p; 181 if (pbuf != NULL) { 182 // free any allocated file name 183 xfree(*bufp); 184 *bufp = pbuf; 185 pbuf = NULL; 186 } 187 } 188 } 189 } else { 190 home_replace(NULL, p, dirname, MAXPATHL, true); 191 // Only replace it when it starts with '~' 192 if (*dirname == '~') { 193 s = xstrdup(dirname); 194 assert(s != NULL); // suppress clang "Argument with 'nonnull' attribute passed null" 195 *fnamep = s; 196 xfree(*bufp); 197 *bufp = s; 198 has_homerelative = true; 199 } 200 } 201 xfree(pbuf); 202 } 203 } 204 205 char *tail = path_tail(*fnamep); 206 *fnamelen = strlen(*fnamep); 207 208 // ":h" - head, remove "/file_name", can be repeated 209 // Don't remove the first "/" or "c:\" 210 while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') { 211 valid |= VALID_HEAD; 212 *usedlen += 2; 213 s = get_past_head(*fnamep); 214 while (tail > s && after_pathsep(s, tail)) { 215 MB_PTR_BACK(*fnamep, tail); 216 } 217 *fnamelen = (size_t)(tail - *fnamep); 218 if (*fnamelen == 0) { 219 // Result is empty. Turn it into "." to make ":cd %:h" work. 220 xfree(*bufp); 221 *bufp = *fnamep = tail = xstrdup("."); 222 *fnamelen = 1; 223 } else { 224 while (tail > s && !after_pathsep(s, tail)) { 225 MB_PTR_BACK(*fnamep, tail); 226 } 227 } 228 } 229 230 // ":8" - shortname 231 if (src[*usedlen] == ':' && src[*usedlen + 1] == '8') { 232 *usedlen += 2; 233 } 234 235 // ":t" - tail, just the basename 236 if (src[*usedlen] == ':' && src[*usedlen + 1] == 't') { 237 *usedlen += 2; 238 *fnamelen -= (size_t)(tail - *fnamep); 239 *fnamep = tail; 240 } 241 242 // ":e" - extension, can be repeated 243 // ":r" - root, without extension, can be repeated 244 while (src[*usedlen] == ':' 245 && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) { 246 // find a '.' in the tail: 247 // - for second :e: before the current fname 248 // - otherwise: The last '.' 249 const bool is_second_e = *fnamep > tail; 250 if (src[*usedlen + 1] == 'e' && is_second_e) { 251 s = (*fnamep) - 2; 252 } else { 253 s = (*fnamep) + *fnamelen - 1; 254 } 255 256 for (; s > tail; s--) { 257 if (s[0] == '.') { 258 break; 259 } 260 } 261 if (src[*usedlen + 1] == 'e') { 262 if (s > tail || (0 && is_second_e && s == tail)) { 263 // we stopped at a '.' (so anchor to &'.' + 1) 264 char *newstart = s + 1; 265 size_t distance_stepped_back = (size_t)(*fnamep - newstart); 266 *fnamelen += distance_stepped_back; 267 *fnamep = newstart; 268 } else if (*fnamep <= tail) { 269 *fnamelen = 0; 270 } 271 } else { 272 // :r - Remove one extension 273 // 274 // Ensure that `s` doesn't go before `*fnamep`, 275 // since then we're taking too many roots: 276 // 277 // "path/to/this.file.ext" :e:e:r:r 278 // ^ ^-------- *fnamep 279 // +------------- tail 280 // 281 // Also ensure `s` doesn't go before `tail`, 282 // since then we're taking too many roots again: 283 // 284 // "path/to/this.file.ext" :r:r:r 285 // ^ ^------------- tail 286 // +--------------------- *fnamep 287 if (s > MAX(tail, *fnamep)) { 288 *fnamelen = (size_t)(s - *fnamep); 289 } 290 } 291 *usedlen += 2; 292 } 293 294 // ":s?pat?foo?" - substitute 295 // ":gs?pat?foo?" - global substitute 296 if (src[*usedlen] == ':' 297 && (src[*usedlen + 1] == 's' 298 || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) { 299 bool didit = false; 300 301 char *flags = ""; 302 s = src + *usedlen + 2; 303 if (src[*usedlen + 1] == 'g') { 304 flags = "g"; 305 s++; 306 } 307 308 int sep = (uint8_t)(*s++); 309 if (sep) { 310 // find end of pattern 311 p = vim_strchr(s, sep); 312 if (p != NULL) { 313 char *const pat = xmemdupz(s, (size_t)(p - s)); 314 s = p + 1; 315 // find end of substitution 316 p = vim_strchr(s, sep); 317 if (p != NULL) { 318 char *const sub = xmemdupz(s, (size_t)(p - s)); 319 char *const str = xmemdupz(*fnamep, *fnamelen); 320 *usedlen = (size_t)(p + 1 - src); 321 size_t slen; 322 s = do_string_sub(str, *fnamelen, pat, sub, NULL, flags, &slen); 323 *fnamep = s; 324 *fnamelen = slen; 325 xfree(*bufp); 326 *bufp = s; 327 didit = true; 328 xfree(sub); 329 xfree(str); 330 } 331 xfree(pat); 332 } 333 // after using ":s", repeat all the modifiers 334 if (didit) { 335 goto repeat; 336 } 337 } 338 } 339 340 if (src[*usedlen] == ':' && src[*usedlen + 1] == 'S') { 341 // vim_strsave_shellescape() needs a NUL terminated string. 342 c = (uint8_t)(*fnamep)[*fnamelen]; 343 if (c != NUL) { 344 (*fnamep)[*fnamelen] = NUL; 345 } 346 p = vim_strsave_shellescape(*fnamep, false, false); 347 if (c != NUL) { 348 (*fnamep)[*fnamelen] = (char)c; 349 } 350 xfree(*bufp); 351 *bufp = *fnamep = p; 352 *fnamelen = strlen(p); 353 *usedlen += 2; 354 } 355 356 return valid; 357 } 358 359 /// "chdir(dir)" function 360 void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 361 { 362 rettv->v_type = VAR_STRING; 363 rettv->vval.v_string = NULL; 364 365 if (argvars[0].v_type != VAR_STRING) { 366 // Returning an empty string means it failed. 367 // No error message, for historic reasons. 368 return; 369 } 370 371 // Return the current directory 372 char *cwd = xmalloc(MAXPATHL); 373 if (os_dirname(cwd, MAXPATHL) != FAIL) { 374 #ifdef BACKSLASH_IN_FILENAME 375 slash_adjust(cwd); 376 #endif 377 rettv->vval.v_string = xstrdup(cwd); 378 } 379 xfree(cwd); 380 381 CdScope scope = kCdScopeGlobal; 382 if (argvars[1].v_type != VAR_UNKNOWN) { 383 const char *s = tv_get_string(&argvars[1]); 384 if (strcmp(s, "global") == 0) { 385 scope = kCdScopeGlobal; 386 } else if (strcmp(s, "tabpage") == 0) { 387 scope = kCdScopeTabpage; 388 } else if (strcmp(s, "window") == 0) { 389 scope = kCdScopeWindow; 390 } else { 391 semsg(_(e_invargNval), "scope", s); 392 return; 393 } 394 } else if (curwin->w_localdir != NULL) { 395 scope = kCdScopeWindow; 396 } else if (curtab->tp_localdir != NULL) { 397 scope = kCdScopeTabpage; 398 } 399 400 if (!changedir_func(argvars[0].vval.v_string, scope)) { 401 // Directory change failed 402 XFREE_CLEAR(rettv->vval.v_string); 403 } 404 } 405 406 /// "delete()" function 407 void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 408 { 409 rettv->vval.v_number = -1; 410 if (check_secure()) { 411 return; 412 } 413 414 const char *const name = tv_get_string(&argvars[0]); 415 if (*name == NUL) { 416 emsg(_(e_invarg)); 417 return; 418 } 419 420 char nbuf[NUMBUFLEN]; 421 const char *flags; 422 if (argvars[1].v_type != VAR_UNKNOWN) { 423 flags = tv_get_string_buf(&argvars[1], nbuf); 424 } else { 425 flags = ""; 426 } 427 428 if (*flags == NUL) { 429 // delete a file 430 rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; 431 } else if (strcmp(flags, "d") == 0) { 432 // delete an empty directory 433 rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; 434 } else if (strcmp(flags, "rf") == 0) { 435 // delete a directory recursively 436 rettv->vval.v_number = delete_recursive(name); 437 } else { 438 semsg(_(e_invexpr2), flags); 439 } 440 } 441 442 /// "executable()" function 443 void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 444 { 445 if (tv_check_for_string_arg(argvars, 0) == FAIL) { 446 return; 447 } 448 449 // Check in $PATH and also check directly if there is a directory name 450 rettv->vval.v_number = os_can_exe(tv_get_string(&argvars[0]), NULL, true); 451 } 452 453 /// "exepath()" function 454 void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 455 { 456 if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) { 457 return; 458 } 459 460 char *path = NULL; 461 462 os_can_exe(tv_get_string(&argvars[0]), &path, true); 463 464 #ifdef BACKSLASH_IN_FILENAME 465 if (path != NULL) { 466 slash_adjust(path); 467 } 468 #endif 469 470 rettv->v_type = VAR_STRING; 471 rettv->vval.v_string = path; 472 } 473 474 /// "filecopy()" function 475 void f_filecopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 476 { 477 rettv->vval.v_number = false; 478 479 if (check_secure() 480 || tv_check_for_string_arg(argvars, 0) == FAIL 481 || tv_check_for_string_arg(argvars, 1) == FAIL) { 482 return; 483 } 484 485 const char *from = tv_get_string(&argvars[0]); 486 487 FileInfo from_info; 488 if (os_fileinfo_link(from, &from_info) 489 && (S_ISREG(from_info.stat.st_mode) || S_ISLNK(from_info.stat.st_mode))) { 490 rettv->vval.v_number 491 = vim_copyfile(tv_get_string(&argvars[0]), tv_get_string(&argvars[1])) == OK; 492 } 493 } 494 495 /// "filereadable()" function 496 void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 497 { 498 const char *const p = tv_get_string(&argvars[0]); 499 rettv->vval.v_number = (*p && !os_isdir(p) && os_file_is_readable(p)); 500 } 501 502 /// @return 0 for not writable 503 /// 1 for writable file 504 /// 2 for a dir which we have rights to write into. 505 void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 506 { 507 const char *filename = tv_get_string(&argvars[0]); 508 rettv->vval.v_number = os_file_is_writable(filename); 509 } 510 511 static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) 512 { 513 char *fresult = NULL; 514 char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; 515 int count = 1; 516 bool first = true; 517 bool error = false; 518 519 rettv->vval.v_string = NULL; 520 rettv->v_type = VAR_STRING; 521 522 const char *fname = tv_get_string(&argvars[0]); 523 524 char pathbuf[NUMBUFLEN]; 525 if (argvars[1].v_type != VAR_UNKNOWN) { 526 const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); 527 if (p == NULL) { 528 error = true; 529 } else { 530 if (*p != NUL) { 531 path = (char *)p; 532 } 533 534 if (argvars[2].v_type != VAR_UNKNOWN) { 535 count = (int)tv_get_number_chk(&argvars[2], &error); 536 } 537 } 538 } 539 540 if (count < 0) { 541 tv_list_alloc_ret(rettv, kListLenUnknown); 542 } 543 544 if (*fname != NUL && !error) { 545 char *file_to_find = NULL; 546 char *search_ctx = NULL; 547 548 do { 549 if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) { 550 xfree(fresult); 551 } 552 fresult = find_file_in_path_option(first ? (char *)fname : NULL, 553 first ? strlen(fname) : 0, 554 0, first, path, 555 find_what, curbuf->b_ffname, 556 (find_what == FINDFILE_DIR 557 ? "" 558 : curbuf->b_p_sua), 559 &file_to_find, &search_ctx); 560 first = false; 561 562 if (fresult != NULL && rettv->v_type == VAR_LIST) { 563 tv_list_append_string(rettv->vval.v_list, fresult, -1); 564 } 565 } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); 566 567 xfree(file_to_find); 568 vim_findfile_cleanup(search_ctx); 569 } 570 571 if (rettv->v_type == VAR_STRING) { 572 rettv->vval.v_string = fresult; 573 } 574 } 575 576 /// "finddir({fname}[, {path}[, {count}]])" function 577 void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 578 { 579 findfilendir(argvars, rettv, FINDFILE_DIR); 580 } 581 582 /// "findfile({fname}[, {path}[, {count}]])" function 583 void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 584 { 585 findfilendir(argvars, rettv, FINDFILE_FILE); 586 } 587 588 /// "fnamemodify({fname}, {mods})" function 589 void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 590 { 591 char *fbuf = NULL; 592 size_t len = 0; 593 char buf[NUMBUFLEN]; 594 const char *fname = tv_get_string_chk(&argvars[0]); 595 const char *const mods = tv_get_string_buf_chk(&argvars[1], buf); 596 if (mods == NULL || fname == NULL) { 597 fname = NULL; 598 } else { 599 len = strlen(fname); 600 if (*mods != NUL) { 601 size_t usedlen = 0; 602 modify_fname((char *)mods, false, &usedlen, 603 (char **)&fname, &fbuf, &len); 604 } 605 } 606 607 rettv->v_type = VAR_STRING; 608 if (fname == NULL) { 609 rettv->vval.v_string = NULL; 610 } else { 611 rettv->vval.v_string = xmemdupz(fname, len); 612 } 613 xfree(fbuf); 614 } 615 616 /// `getcwd([{win}[, {tab}]])` function 617 /// 618 /// Every scope not specified implies the currently selected scope object. 619 /// 620 /// @pre The arguments must be of type number. 621 /// @pre There may not be more than two arguments. 622 /// @pre An argument may not be -1 if preceding arguments are not all -1. 623 /// 624 /// @post The return value will be a string. 625 void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 626 { 627 // Possible scope of working directory to return. 628 CdScope scope = kCdScopeInvalid; 629 630 // Numbers of the scope objects (window, tab) we want the working directory 631 // of. A `-1` means to skip this scope, a `0` means the current object. 632 int scope_number[] = { 633 [kCdScopeWindow] = 0, // Number of window to look at. 634 [kCdScopeTabpage] = 0, // Number of tab to look at. 635 }; 636 637 char *cwd = NULL; // Current working directory to print 638 char *from = NULL; // The original string to copy 639 640 tabpage_T *tp = curtab; // The tabpage to look at. 641 win_T *win = curwin; // The window to look at. 642 643 rettv->v_type = VAR_STRING; 644 rettv->vval.v_string = NULL; 645 646 // Pre-conditions and scope extraction together 647 for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { 648 // If there is no argument there are no more scopes after it, break out. 649 if (argvars[i].v_type == VAR_UNKNOWN) { 650 break; 651 } 652 if (argvars[i].v_type != VAR_NUMBER) { 653 emsg(_(e_invarg)); 654 return; 655 } 656 scope_number[i] = (int)argvars[i].vval.v_number; 657 // It is an error for the scope number to be less than `-1`. 658 if (scope_number[i] < -1) { 659 emsg(_(e_invarg)); 660 return; 661 } 662 // Use the narrowest scope the user requested 663 if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { 664 // The scope is the current iteration step. 665 scope = i; 666 } else if (scope_number[i] < 0) { 667 scope = i + 1; 668 } 669 } 670 671 // Find the tabpage by number 672 if (scope_number[kCdScopeTabpage] > 0) { 673 tp = find_tabpage(scope_number[kCdScopeTabpage]); 674 if (!tp) { 675 emsg(_("E5000: Cannot find tab number.")); 676 return; 677 } 678 } 679 680 // Find the window in `tp` by number, `NULL` if none. 681 if (scope_number[kCdScopeWindow] >= 0) { 682 if (scope_number[kCdScopeTabpage] < 0) { 683 emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); 684 return; 685 } 686 687 if (scope_number[kCdScopeWindow] > 0) { 688 win = find_win_by_nr(&argvars[0], tp); 689 if (!win) { 690 emsg(_("E5002: Cannot find window number.")); 691 return; 692 } 693 } 694 } 695 696 cwd = xmalloc(MAXPATHL); 697 698 switch (scope) { 699 case kCdScopeWindow: 700 assert(win); 701 from = win->w_localdir; 702 if (from) { 703 break; 704 } 705 FALLTHROUGH; 706 case kCdScopeTabpage: 707 assert(tp); 708 from = tp->tp_localdir; 709 if (from) { 710 break; 711 } 712 FALLTHROUGH; 713 case kCdScopeGlobal: 714 if (globaldir) { // `globaldir` is not always set. 715 from = globaldir; 716 break; 717 } 718 FALLTHROUGH; // In global directory, just need to get OS CWD. 719 case kCdScopeInvalid: // If called without any arguments, get OS CWD. 720 if (os_dirname(cwd, MAXPATHL) == FAIL) { 721 from = ""; // Return empty string on failure. 722 } 723 } 724 725 if (from) { 726 xstrlcpy(cwd, from, MAXPATHL); 727 } 728 729 rettv->vval.v_string = xstrdup(cwd); 730 #ifdef BACKSLASH_IN_FILENAME 731 slash_adjust(rettv->vval.v_string); 732 #endif 733 734 xfree(cwd); 735 } 736 737 /// "getfperm({fname})" function 738 void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 739 { 740 char *perm = NULL; 741 char flags[] = "rwx"; 742 743 const char *filename = tv_get_string(&argvars[0]); 744 int32_t file_perm = os_getperm(filename); 745 if (file_perm >= 0) { 746 perm = xstrdup("---------"); 747 for (int i = 0; i < 9; i++) { 748 if (file_perm & (1 << (8 - i))) { 749 perm[i] = flags[i % 3]; 750 } 751 } 752 } 753 rettv->v_type = VAR_STRING; 754 rettv->vval.v_string = perm; 755 } 756 757 /// "getfsize({fname})" function 758 void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 759 { 760 const char *fname = tv_get_string(&argvars[0]); 761 762 rettv->v_type = VAR_NUMBER; 763 764 FileInfo file_info; 765 if (os_fileinfo(fname, &file_info)) { 766 uint64_t filesize = os_fileinfo_size(&file_info); 767 if (os_isdir(fname)) { 768 rettv->vval.v_number = 0; 769 } else { 770 rettv->vval.v_number = (varnumber_T)filesize; 771 772 // non-perfect check for overflow 773 if ((uint64_t)rettv->vval.v_number != filesize) { 774 rettv->vval.v_number = -2; 775 } 776 } 777 } else { 778 rettv->vval.v_number = -1; 779 } 780 } 781 782 /// "getftime({fname})" function 783 void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 784 { 785 const char *fname = tv_get_string(&argvars[0]); 786 787 FileInfo file_info; 788 if (os_fileinfo(fname, &file_info)) { 789 rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; 790 } else { 791 rettv->vval.v_number = -1; 792 } 793 } 794 795 /// "getftype({fname})" function 796 void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 797 { 798 char *type = NULL; 799 char *t; 800 801 const char *fname = tv_get_string(&argvars[0]); 802 803 rettv->v_type = VAR_STRING; 804 FileInfo file_info; 805 if (os_fileinfo_link(fname, &file_info)) { 806 uint64_t mode = file_info.stat.st_mode; 807 if (S_ISREG(mode)) { 808 t = "file"; 809 } else if (S_ISDIR(mode)) { 810 t = "dir"; 811 } else if (S_ISLNK(mode)) { 812 t = "link"; 813 } else if (S_ISBLK(mode)) { 814 t = "bdev"; 815 } else if (S_ISCHR(mode)) { 816 t = "cdev"; 817 } else if (S_ISFIFO(mode)) { 818 t = "fifo"; 819 } else if (S_ISSOCK(mode)) { 820 t = "socket"; 821 } else { 822 t = "other"; 823 } 824 type = xstrdup(t); 825 } 826 rettv->vval.v_string = type; 827 } 828 829 /// "glob()" function 830 void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 831 { 832 int options = WILD_SILENT|WILD_USE_NL; 833 expand_T xpc; 834 bool error = false; 835 836 // When the optional second argument is non-zero, don't remove matches 837 // for 'wildignore' and don't put matches for 'suffixes' at the end. 838 rettv->v_type = VAR_STRING; 839 if (argvars[1].v_type != VAR_UNKNOWN) { 840 if (tv_get_number_chk(&argvars[1], &error)) { 841 options |= WILD_KEEP_ALL; 842 } 843 if (argvars[2].v_type != VAR_UNKNOWN) { 844 if (tv_get_number_chk(&argvars[2], &error)) { 845 tv_list_set_ret(rettv, NULL); 846 } 847 if (argvars[3].v_type != VAR_UNKNOWN 848 && tv_get_number_chk(&argvars[3], &error)) { 849 options |= WILD_ALLLINKS; 850 } 851 } 852 } 853 if (!error) { 854 ExpandInit(&xpc); 855 xpc.xp_context = EXPAND_FILES; 856 if (p_wic) { 857 options += WILD_ICASE; 858 } 859 if (rettv->v_type == VAR_STRING) { 860 rettv->vval.v_string = ExpandOne(&xpc, (char *) 861 tv_get_string(&argvars[0]), NULL, options, 862 WILD_ALL); 863 } else { 864 ExpandOne(&xpc, (char *)tv_get_string(&argvars[0]), NULL, options, 865 WILD_ALL_KEEP); 866 tv_list_alloc_ret(rettv, xpc.xp_numfiles); 867 for (int i = 0; i < xpc.xp_numfiles; i++) { 868 tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); 869 } 870 ExpandCleanup(&xpc); 871 } 872 } else { 873 rettv->vval.v_string = NULL; 874 } 875 } 876 877 /// "globpath()" function 878 void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 879 { 880 int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. 881 bool error = false; 882 883 // Return a string, or a list if the optional third argument is non-zero. 884 rettv->v_type = VAR_STRING; 885 886 if (argvars[2].v_type != VAR_UNKNOWN) { 887 // When the optional second argument is non-zero, don't remove matches 888 // for 'wildignore' and don't put matches for 'suffixes' at the end. 889 if (tv_get_number_chk(&argvars[2], &error)) { 890 flags |= WILD_KEEP_ALL; 891 } 892 893 if (argvars[3].v_type != VAR_UNKNOWN) { 894 if (tv_get_number_chk(&argvars[3], &error)) { 895 tv_list_set_ret(rettv, NULL); 896 } 897 if (argvars[4].v_type != VAR_UNKNOWN 898 && tv_get_number_chk(&argvars[4], &error)) { 899 flags |= WILD_ALLLINKS; 900 } 901 } 902 } 903 904 char buf1[NUMBUFLEN]; 905 const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); 906 if (file != NULL && !error) { 907 garray_T ga; 908 ga_init(&ga, (int)sizeof(char *), 10); 909 globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false); 910 911 if (rettv->v_type == VAR_STRING) { 912 rettv->vval.v_string = ga_concat_strings(&ga, "\n"); 913 } else { 914 tv_list_alloc_ret(rettv, ga.ga_len); 915 for (int i = 0; i < ga.ga_len; i++) { 916 tv_list_append_string(rettv->vval.v_list, 917 ((const char **)(ga.ga_data))[i], -1); 918 } 919 } 920 921 ga_clear_strings(&ga); 922 } else { 923 rettv->vval.v_string = NULL; 924 } 925 } 926 927 /// "glob2regpat()" function 928 void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 929 { 930 const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error 931 932 rettv->v_type = VAR_STRING; 933 rettv->vval.v_string = pat == NULL ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false); 934 } 935 936 /// `haslocaldir([{win}[, {tab}]])` function 937 /// 938 /// Returns `1` if the scope object has a local directory, `0` otherwise. If a 939 /// scope object is not specified the current one is implied. This function 940 /// share a lot of code with `f_getcwd`. 941 /// 942 /// @pre The arguments must be of type number. 943 /// @pre There may not be more than two arguments. 944 /// @pre An argument may not be -1 if preceding arguments are not all -1. 945 /// 946 /// @post The return value will be either the number `1` or `0`. 947 void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 948 { 949 // Possible scope of working directory to return. 950 CdScope scope = kCdScopeInvalid; 951 952 // Numbers of the scope objects (window, tab) we want the working directory 953 // of. A `-1` means to skip this scope, a `0` means the current object. 954 int scope_number[] = { 955 [kCdScopeWindow] = 0, // Number of window to look at. 956 [kCdScopeTabpage] = 0, // Number of tab to look at. 957 }; 958 959 tabpage_T *tp = curtab; // The tabpage to look at. 960 win_T *win = curwin; // The window to look at. 961 962 rettv->v_type = VAR_NUMBER; 963 rettv->vval.v_number = 0; 964 965 // Pre-conditions and scope extraction together 966 for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { 967 if (argvars[i].v_type == VAR_UNKNOWN) { 968 break; 969 } 970 if (argvars[i].v_type != VAR_NUMBER) { 971 emsg(_(e_invarg)); 972 return; 973 } 974 scope_number[i] = (int)argvars[i].vval.v_number; 975 if (scope_number[i] < -1) { 976 emsg(_(e_invarg)); 977 return; 978 } 979 // Use the narrowest scope the user requested 980 if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { 981 // The scope is the current iteration step. 982 scope = i; 983 } else if (scope_number[i] < 0) { 984 scope = i + 1; 985 } 986 } 987 988 // If the user didn't specify anything, default to window scope 989 if (scope == kCdScopeInvalid) { 990 scope = MIN_CD_SCOPE; 991 } 992 993 // Find the tabpage by number 994 if (scope_number[kCdScopeTabpage] > 0) { 995 tp = find_tabpage(scope_number[kCdScopeTabpage]); 996 if (!tp) { 997 emsg(_("E5000: Cannot find tab number.")); 998 return; 999 } 1000 } 1001 1002 // Find the window in `tp` by number, `NULL` if none. 1003 if (scope_number[kCdScopeWindow] >= 0) { 1004 if (scope_number[kCdScopeTabpage] < 0) { 1005 emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); 1006 return; 1007 } 1008 1009 if (scope_number[kCdScopeWindow] > 0) { 1010 win = find_win_by_nr(&argvars[0], tp); 1011 if (!win) { 1012 emsg(_("E5002: Cannot find window number.")); 1013 return; 1014 } 1015 } 1016 } 1017 1018 switch (scope) { 1019 case kCdScopeWindow: 1020 assert(win); 1021 rettv->vval.v_number = win->w_localdir ? 1 : 0; 1022 break; 1023 case kCdScopeTabpage: 1024 assert(tp); 1025 rettv->vval.v_number = tp->tp_localdir ? 1 : 0; 1026 break; 1027 case kCdScopeGlobal: 1028 // The global scope never has a local directory 1029 break; 1030 case kCdScopeInvalid: 1031 // We should never get here 1032 abort(); 1033 } 1034 } 1035 1036 /// "isabsolutepath()" function 1037 void f_isabsolutepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1038 { 1039 rettv->vval.v_number = path_is_absolute(tv_get_string(&argvars[0])); 1040 } 1041 1042 /// "isdirectory()" function 1043 void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1044 { 1045 rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0])); 1046 } 1047 1048 /// "mkdir()" function 1049 void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1050 { 1051 int prot = 0755; 1052 1053 rettv->vval.v_number = FAIL; 1054 if (check_secure()) { 1055 return; 1056 } 1057 1058 char buf[NUMBUFLEN]; 1059 const char *const dir = tv_get_string_buf(&argvars[0], buf); 1060 if (*dir == NUL) { 1061 return; 1062 } 1063 1064 if (*path_tail(dir) == NUL) { 1065 // Remove trailing slashes. 1066 *path_tail_with_sep((char *)dir) = NUL; 1067 } 1068 1069 bool defer = false; 1070 bool defer_recurse = false; 1071 char *created = NULL; 1072 if (argvars[1].v_type != VAR_UNKNOWN) { 1073 if (argvars[2].v_type != VAR_UNKNOWN) { 1074 prot = (int)tv_get_number_chk(&argvars[2], NULL); 1075 if (prot == -1) { 1076 return; 1077 } 1078 } 1079 const char *arg2 = tv_get_string(&argvars[1]); 1080 defer = vim_strchr(arg2, 'D') != NULL; 1081 defer_recurse = vim_strchr(arg2, 'R') != NULL; 1082 if ((defer || defer_recurse) && !can_add_defer()) { 1083 return; 1084 } 1085 1086 if (vim_strchr(arg2, 'p') != NULL) { 1087 char *failed_dir; 1088 int ret = os_mkdir_recurse(dir, prot, &failed_dir, 1089 defer || defer_recurse ? &created : NULL); 1090 if (ret != 0) { 1091 semsg(_(e_mkdir), failed_dir, os_strerror(ret)); 1092 xfree(failed_dir); 1093 rettv->vval.v_number = FAIL; 1094 return; 1095 } 1096 rettv->vval.v_number = OK; 1097 } 1098 } 1099 if (rettv->vval.v_number == FAIL) { 1100 rettv->vval.v_number = vim_mkdir_emsg(dir, prot); 1101 } 1102 1103 // Handle "D" and "R": deferred deletion of the created directory. 1104 if (rettv->vval.v_number == OK 1105 && created == NULL && (defer || defer_recurse)) { 1106 created = FullName_save(dir, false); 1107 } 1108 if (created != NULL) { 1109 typval_T tv[2]; 1110 tv[0].v_type = VAR_STRING; 1111 tv[0].v_lock = VAR_UNLOCKED; 1112 tv[0].vval.v_string = created; 1113 tv[1].v_type = VAR_STRING; 1114 tv[1].v_lock = VAR_UNLOCKED; 1115 tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d"); 1116 add_defer("delete", 2, tv); 1117 } 1118 } 1119 1120 /// "pathshorten()" function 1121 void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1122 { 1123 int trim_len = 1; 1124 1125 if (argvars[1].v_type != VAR_UNKNOWN) { 1126 trim_len = (int)tv_get_number(&argvars[1]); 1127 if (trim_len < 1) { 1128 trim_len = 1; 1129 } 1130 } 1131 1132 rettv->v_type = VAR_STRING; 1133 const char *p = tv_get_string_chk(&argvars[0]); 1134 if (p == NULL) { 1135 rettv->vval.v_string = NULL; 1136 } else { 1137 rettv->vval.v_string = xstrdup(p); 1138 shorten_dir_len(rettv->vval.v_string, trim_len); 1139 } 1140 } 1141 1142 /// Evaluate "expr" (= "context") for readdir(). 1143 static varnumber_T readdir_checkitem(void *context, const char *name) 1144 FUNC_ATTR_NONNULL_ALL 1145 { 1146 typval_T *expr = (typval_T *)context; 1147 typval_T argv[2]; 1148 varnumber_T retval = 0; 1149 bool error = false; 1150 1151 if (expr->v_type == VAR_UNKNOWN) { 1152 return 1; 1153 } 1154 1155 typval_T save_val; 1156 prepare_vimvar(VV_VAL, &save_val); 1157 set_vim_var_string(VV_VAL, name, -1); 1158 argv[0].v_type = VAR_STRING; 1159 argv[0].vval.v_string = (char *)name; 1160 1161 typval_T rettv; 1162 if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) { 1163 goto theend; 1164 } 1165 1166 retval = tv_get_number_chk(&rettv, &error); 1167 if (error) { 1168 retval = -1; 1169 } 1170 1171 tv_clear(&rettv); 1172 1173 theend: 1174 set_vim_var_string(VV_VAL, NULL, 0); 1175 restore_vimvar(VV_VAL, &save_val); 1176 return retval; 1177 } 1178 1179 /// "readdir()" function 1180 void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1181 { 1182 tv_list_alloc_ret(rettv, kListLenUnknown); 1183 1184 const char *path = tv_get_string(&argvars[0]); 1185 typval_T *expr = &argvars[1]; 1186 garray_T ga; 1187 int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); 1188 if (ret == OK && ga.ga_len > 0) { 1189 for (int i = 0; i < ga.ga_len; i++) { 1190 const char *p = ((const char **)ga.ga_data)[i]; 1191 tv_list_append_string(rettv->vval.v_list, p, -1); 1192 } 1193 } 1194 ga_clear_strings(&ga); 1195 } 1196 1197 /// Read blob from file "fd". 1198 /// Caller has allocated a blob in "rettv". 1199 /// 1200 /// @param[in] fd File to read from. 1201 /// @param[in,out] rettv Blob to write to. 1202 /// @param[in] offset Read the file from the specified offset. 1203 /// @param[in] size Read the specified size, or -1 if no limit. 1204 /// 1205 /// @return OK on success, or FAIL on failure. 1206 static int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg) 1207 FUNC_ATTR_NONNULL_ALL 1208 { 1209 blob_T *const blob = rettv->vval.v_blob; 1210 FileInfo file_info; 1211 if (!os_fileinfo_fd(fileno(fd), &file_info)) { 1212 return FAIL; // can't read the file, error 1213 } 1214 1215 int whence; 1216 off_T size = size_arg; 1217 const off_T file_size = (off_T)os_fileinfo_size(&file_info); 1218 if (offset >= 0) { 1219 // The size defaults to the whole file. If a size is given it is 1220 // limited to not go past the end of the file. 1221 if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) { 1222 // size may become negative, checked below 1223 size = (off_T)os_fileinfo_size(&file_info) - offset; 1224 } 1225 whence = SEEK_SET; 1226 } else { 1227 // limit the offset to not go before the start of the file 1228 if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) { 1229 offset = -file_size; 1230 } 1231 // Size defaults to reading until the end of the file. 1232 if (size == -1 || size > -offset) { 1233 size = -offset; 1234 } 1235 whence = SEEK_END; 1236 } 1237 if (size <= 0) { 1238 return OK; 1239 } 1240 if (offset != 0 && vim_fseek(fd, offset, whence) != 0) { 1241 return OK; 1242 } 1243 1244 ga_grow(&blob->bv_ga, (int)size); 1245 blob->bv_ga.ga_len = (int)size; 1246 if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd) 1247 < (size_t)blob->bv_ga.ga_len) { 1248 // An empty blob is returned on error. 1249 tv_blob_free(rettv->vval.v_blob); 1250 rettv->vval.v_blob = NULL; 1251 return FAIL; 1252 } 1253 return OK; 1254 } 1255 1256 /// "readfile()" or "readblob()" function 1257 static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob) 1258 { 1259 bool binary = false; 1260 bool blob = always_blob; 1261 FILE *fd; 1262 char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 1263 int io_size = sizeof(buf); 1264 char *prev = NULL; // previously read bytes, if any 1265 ptrdiff_t prevlen = 0; // length of data in prev 1266 ptrdiff_t prevsize = 0; // size of prev buffer 1267 int64_t maxline = MAXLNUM; 1268 off_T offset = 0; 1269 off_T size = -1; 1270 1271 if (argvars[1].v_type != VAR_UNKNOWN) { 1272 if (always_blob) { 1273 offset = (off_T)tv_get_number(&argvars[1]); 1274 if (argvars[2].v_type != VAR_UNKNOWN) { 1275 size = (off_T)tv_get_number(&argvars[2]); 1276 } 1277 } else { 1278 if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { 1279 binary = true; 1280 } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) { 1281 blob = true; 1282 } 1283 if (argvars[2].v_type != VAR_UNKNOWN) { 1284 maxline = tv_get_number(&argvars[2]); 1285 } 1286 } 1287 } 1288 1289 if (blob) { 1290 tv_blob_alloc_ret(rettv); 1291 } else { 1292 tv_list_alloc_ret(rettv, kListLenUnknown); 1293 } 1294 1295 // Always open the file in binary mode, library functions have a mind of 1296 // their own about CR-LF conversion. 1297 const char *const fname = tv_get_string(&argvars[0]); 1298 1299 if (os_isdir(fname)) { 1300 semsg(_(e_isadir2), fname); 1301 return; 1302 } 1303 if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { 1304 semsg(_(e_notopen), *fname == NUL ? _("<empty>") : fname); 1305 return; 1306 } 1307 1308 if (blob) { 1309 if (read_blob(fd, rettv, offset, size) == FAIL) { 1310 semsg(_(e_cant_read_file_str), fname); 1311 } 1312 fclose(fd); 1313 return; 1314 } 1315 1316 list_T *const l = rettv->vval.v_list; 1317 1318 while (maxline < 0 || tv_list_len(l) < maxline) { 1319 int readlen = (int)fread(buf, 1, (size_t)io_size, fd); 1320 1321 // This for loop processes what was read, but is also entered at end 1322 // of file so that either: 1323 // - an incomplete line gets written 1324 // - a "binary" file gets an empty line at the end if it ends in a 1325 // newline. 1326 char *p; // Position in buf. 1327 char *start; // Start of current line. 1328 for (p = buf, start = buf; 1329 p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); 1330 p++) { 1331 if (readlen <= 0 || *p == '\n') { 1332 char *s = NULL; 1333 size_t len = (size_t)(p - start); 1334 1335 // Finished a line. Remove CRs before NL. 1336 if (readlen > 0 && !binary) { 1337 while (len > 0 && start[len - 1] == '\r') { 1338 len--; 1339 } 1340 // removal may cross back to the "prev" string 1341 if (len == 0) { 1342 while (prevlen > 0 && prev[prevlen - 1] == '\r') { 1343 prevlen--; 1344 } 1345 } 1346 } 1347 if (prevlen == 0) { 1348 assert(len < INT_MAX); 1349 s = xmemdupz(start, len); 1350 } else { 1351 // Change "prev" buffer to be the right size. This way 1352 // the bytes are only copied once, and very long lines are 1353 // allocated only once. 1354 s = xrealloc(prev, (size_t)prevlen + len + 1); 1355 memcpy(s + prevlen, start, len); 1356 s[(size_t)prevlen + len] = NUL; 1357 prev = NULL; // the list will own the string 1358 prevlen = prevsize = 0; 1359 } 1360 1361 tv_list_append_owned_tv(l, (typval_T) { 1362 .v_type = VAR_STRING, 1363 .v_lock = VAR_UNLOCKED, 1364 .vval.v_string = s, 1365 }); 1366 1367 start = p + 1; // Step over newline. 1368 if (maxline < 0) { 1369 if (tv_list_len(l) > -maxline) { 1370 assert(tv_list_len(l) == 1 + (-maxline)); 1371 tv_list_item_remove(l, tv_list_first(l)); 1372 } 1373 } else if (tv_list_len(l) >= maxline) { 1374 assert(tv_list_len(l) == maxline); 1375 break; 1376 } 1377 if (readlen <= 0) { 1378 break; 1379 } 1380 } else if (*p == NUL) { 1381 *p = '\n'; 1382 // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this 1383 // when finding the BF and check the previous two bytes. 1384 } else if ((uint8_t)(*p) == 0xbf && !binary) { 1385 // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, 1386 // these may be in the "prev" string. 1387 char back1 = p >= buf + 1 ? p[-1] 1388 : prevlen >= 1 ? prev[prevlen - 1] : NUL; 1389 char back2 = p >= buf + 2 ? p[-2] 1390 : (p == buf + 1 && prevlen >= 1 1391 ? prev[prevlen - 1] 1392 : prevlen >= 2 ? prev[prevlen - 2] : NUL); 1393 1394 if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) { 1395 char *dest = p - 2; 1396 1397 // Usually a BOM is at the beginning of a file, and so at 1398 // the beginning of a line; then we can just step over it. 1399 if (start == dest) { 1400 start = p + 1; 1401 } else { 1402 // have to shuffle buf to close gap 1403 int adjust_prevlen = 0; 1404 1405 if (dest < buf) { 1406 // adjust_prevlen must be 1 or 2. 1407 adjust_prevlen = (int)(buf - dest); 1408 dest = buf; 1409 } 1410 if (readlen > p - buf + 1) { 1411 memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1); 1412 } 1413 readlen -= 3 - adjust_prevlen; 1414 prevlen -= adjust_prevlen; 1415 p = dest - 1; 1416 } 1417 } 1418 } 1419 } // for 1420 1421 if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { 1422 break; 1423 } 1424 if (start < p) { 1425 // There's part of a line in buf, store it in "prev". 1426 if (p - start + prevlen >= prevsize) { 1427 // A common use case is ordinary text files and "prev" gets a 1428 // fragment of a line, so the first allocation is made 1429 // small, to avoid repeatedly 'allocing' large and 1430 // 'reallocing' small. 1431 if (prevsize == 0) { 1432 prevsize = p - start; 1433 } else { 1434 ptrdiff_t grow50pc = (prevsize * 3) / 2; 1435 ptrdiff_t growmin = (p - start) * 2 + prevlen; 1436 prevsize = grow50pc > growmin ? grow50pc : growmin; 1437 } 1438 prev = xrealloc(prev, (size_t)prevsize); 1439 } 1440 // Add the line part to end of "prev". 1441 memmove(prev + prevlen, start, (size_t)(p - start)); 1442 prevlen += p - start; 1443 } 1444 } // while 1445 1446 xfree(prev); 1447 fclose(fd); 1448 } 1449 1450 /// "readblob()" function 1451 void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1452 { 1453 read_file_or_blob(argvars, rettv, true); 1454 } 1455 1456 /// "readfile()" function 1457 void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1458 { 1459 read_file_or_blob(argvars, rettv, false); 1460 } 1461 1462 /// "rename({from}, {to})" function 1463 void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1464 { 1465 if (check_secure()) { 1466 rettv->vval.v_number = -1; 1467 } else { 1468 char buf[NUMBUFLEN]; 1469 rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]), 1470 tv_get_string_buf(&argvars[1], buf)); 1471 } 1472 } 1473 1474 /// "resolve()" function 1475 void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1476 { 1477 rettv->v_type = VAR_STRING; 1478 const char *fname = tv_get_string(&argvars[0]); 1479 #ifdef MSWIN 1480 char *v = os_resolve_shortcut(fname); 1481 if (v == NULL) { 1482 if (os_is_reparse_point_include(fname)) { 1483 v = os_realpath(fname, NULL, MAXPATHL + 1); 1484 } 1485 } 1486 rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); 1487 #else 1488 # ifdef HAVE_READLINK 1489 { 1490 bool is_relative_to_current = false; 1491 bool has_trailing_pathsep = false; 1492 int limit = 100; 1493 1494 char *p = xstrdup(fname); 1495 1496 if (p[0] == '.' && (vim_ispathsep(p[1]) 1497 || (p[1] == '.' && (vim_ispathsep(p[2]))))) { 1498 is_relative_to_current = true; 1499 } 1500 1501 ptrdiff_t len = (ptrdiff_t)strlen(p); 1502 if (len > 1 && after_pathsep(p, p + len)) { 1503 has_trailing_pathsep = true; 1504 p[len - 1] = NUL; // The trailing slash breaks readlink(). 1505 } 1506 1507 char *q = (char *)path_next_component(p); 1508 char *remain = NULL; 1509 if (*q != NUL) { 1510 // Separate the first path component in "p", and keep the 1511 // remainder (beginning with the path separator). 1512 remain = xstrdup(q - 1); 1513 q[-1] = NUL; 1514 } 1515 1516 char *const buf = xmallocz(MAXPATHL); 1517 1518 char *cpy; 1519 while (true) { 1520 while (true) { 1521 len = readlink(p, buf, MAXPATHL); 1522 if (len <= 0) { 1523 break; 1524 } 1525 buf[len] = NUL; 1526 1527 if (limit-- == 0) { 1528 xfree(p); 1529 xfree(remain); 1530 emsg(_("E655: Too many symbolic links (cycle?)")); 1531 rettv->vval.v_string = NULL; 1532 xfree(buf); 1533 return; 1534 } 1535 1536 // Ensure that the result will have a trailing path separator 1537 // if the argument has one. 1538 if (remain == NULL && has_trailing_pathsep) { 1539 add_pathsep(buf); 1540 } 1541 1542 // Separate the first path component in the link value and 1543 // concatenate the remainders. 1544 q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); 1545 if (*q != NUL) { 1546 cpy = remain; 1547 remain = remain != NULL ? concat_str(q - 1, remain) : xstrdup(q - 1); 1548 xfree(cpy); 1549 q[-1] = NUL; 1550 } 1551 1552 q = path_tail(p); 1553 if (q > p && *q == NUL) { 1554 // Ignore trailing path separator. 1555 p[q - p - 1] = NUL; 1556 q = path_tail(p); 1557 } 1558 if (q > p && !path_is_absolute(buf)) { 1559 // Symlink is relative to directory of argument. Replace the 1560 // symlink with the resolved name in the same directory. 1561 const size_t p_len = strlen(p); 1562 const size_t buf_len = strlen(buf); 1563 p = xrealloc(p, p_len + buf_len + 1); 1564 memcpy(path_tail(p), buf, buf_len + 1); 1565 } else { 1566 xfree(p); 1567 p = xstrdup(buf); 1568 } 1569 } 1570 1571 if (remain == NULL) { 1572 break; 1573 } 1574 1575 // Append the first path component of "remain" to "p". 1576 q = (char *)path_next_component(remain + 1); 1577 len = q - remain - (*q != NUL); 1578 const size_t p_len = strlen(p); 1579 cpy = xmallocz(p_len + (size_t)len); 1580 memcpy(cpy, p, p_len + 1); 1581 xstrlcat(cpy + p_len, remain, (size_t)len + 1); 1582 xfree(p); 1583 p = cpy; 1584 1585 // Shorten "remain". 1586 if (*q != NUL) { 1587 STRMOVE(remain, q - 1); 1588 } else { 1589 XFREE_CLEAR(remain); 1590 } 1591 } 1592 1593 // If the result is a relative path name, make it explicitly relative to 1594 // the current directory if and only if the argument had this form. 1595 if (!vim_ispathsep(*p)) { 1596 if (is_relative_to_current 1597 && *p != NUL 1598 && !(p[0] == '.' 1599 && (p[1] == NUL 1600 || vim_ispathsep(p[1]) 1601 || (p[1] == '.' 1602 && (p[2] == NUL 1603 || vim_ispathsep(p[2])))))) { 1604 // Prepend "./". 1605 cpy = concat_str("./", p); 1606 xfree(p); 1607 p = cpy; 1608 } else if (!is_relative_to_current) { 1609 // Strip leading "./". 1610 q = p; 1611 while (q[0] == '.' && vim_ispathsep(q[1])) { 1612 q += 2; 1613 } 1614 if (q > p) { 1615 STRMOVE(p, p + 2); 1616 } 1617 } 1618 } 1619 1620 // Ensure that the result will have no trailing path separator 1621 // if the argument had none. But keep "/" or "//". 1622 if (!has_trailing_pathsep) { 1623 q = p + strlen(p); 1624 if (after_pathsep(p, q)) { 1625 *path_tail_with_sep(p) = NUL; 1626 } 1627 } 1628 1629 rettv->vval.v_string = p; 1630 xfree(buf); 1631 } 1632 # else 1633 char *v = os_realpath(fname, NULL, MAXPATHL + 1); 1634 rettv->vval.v_string = v == NULL ? xstrdup(fname) : v; 1635 # endif 1636 #endif 1637 1638 simplify_filename(rettv->vval.v_string); 1639 } 1640 1641 /// "simplify()" function 1642 void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1643 { 1644 const char *const p = tv_get_string(&argvars[0]); 1645 rettv->vval.v_string = xstrdup(p); 1646 simplify_filename(rettv->vval.v_string); // Simplify in place. 1647 rettv->v_type = VAR_STRING; 1648 } 1649 1650 /// "tempname()" function 1651 void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1652 { 1653 rettv->v_type = VAR_STRING; 1654 rettv->vval.v_string = vim_tempname(); 1655 } 1656 1657 /// Write "list" of strings to file "fd". 1658 /// 1659 /// @param fp File to write to. 1660 /// @param[in] list List to write. 1661 /// @param[in] binary Whether to write in binary mode. 1662 /// 1663 /// @return true in case of success, false otherwise. 1664 static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) 1665 FUNC_ATTR_NONNULL_ARG(1) 1666 { 1667 int error = 0; 1668 TV_LIST_ITER_CONST(list, li, { 1669 const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li)); 1670 if (s == NULL) { 1671 return false; 1672 } 1673 const char *hunk_start = s; 1674 for (const char *p = hunk_start;; p++) { 1675 if (*p == NUL || *p == NL) { 1676 if (p != hunk_start) { 1677 const ptrdiff_t written = file_write(fp, hunk_start, 1678 (size_t)(p - hunk_start)); 1679 if (written < 0) { 1680 error = (int)written; 1681 goto write_list_error; 1682 } 1683 } 1684 if (*p == NUL) { 1685 break; 1686 } else { 1687 hunk_start = p + 1; 1688 const ptrdiff_t written = file_write(fp, (char[]){ NUL }, 1); 1689 if (written < 0) { 1690 error = (int)written; 1691 break; 1692 } 1693 } 1694 } 1695 } 1696 if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) { 1697 const ptrdiff_t written = file_write(fp, "\n", 1); 1698 if (written < 0) { 1699 error = (int)written; 1700 goto write_list_error; 1701 } 1702 } 1703 }); 1704 if ((error = file_flush(fp)) != 0) { 1705 goto write_list_error; 1706 } 1707 return true; 1708 write_list_error: 1709 semsg(_(e_error_while_writing_str), os_strerror(error)); 1710 return false; 1711 } 1712 1713 /// Write a blob to file with descriptor `fp`. 1714 /// 1715 /// @param[in] fp File to write to. 1716 /// @param[in] blob Blob to write. 1717 /// 1718 /// @return true on success, or false on failure. 1719 static bool write_blob(FileDescriptor *const fp, const blob_T *const blob) 1720 FUNC_ATTR_NONNULL_ARG(1) 1721 { 1722 int error = 0; 1723 const int len = tv_blob_len(blob); 1724 if (len > 0) { 1725 const ptrdiff_t written = file_write(fp, blob->bv_ga.ga_data, (size_t)len); 1726 if (written < (ptrdiff_t)len) { 1727 error = (int)written; 1728 goto write_blob_error; 1729 } 1730 } 1731 error = file_flush(fp); 1732 if (error != 0) { 1733 goto write_blob_error; 1734 } 1735 return true; 1736 write_blob_error: 1737 semsg(_(e_error_while_writing_str), os_strerror(error)); 1738 return false; 1739 } 1740 1741 /// "writefile()" function 1742 void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1743 { 1744 rettv->vval.v_number = -1; 1745 1746 if (check_secure()) { 1747 return; 1748 } 1749 1750 if (argvars[0].v_type == VAR_LIST) { 1751 TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { 1752 if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { 1753 return; 1754 } 1755 }); 1756 } else if (argvars[0].v_type != VAR_BLOB) { 1757 semsg(_(e_invarg2), 1758 _("writefile() first argument must be a List or a Blob")); 1759 return; 1760 } 1761 1762 bool binary = false; 1763 bool append = false; 1764 bool defer = false; 1765 bool do_fsync = !!p_fs; 1766 bool mkdir_p = false; 1767 if (argvars[2].v_type != VAR_UNKNOWN) { 1768 const char *const flags = tv_get_string_chk(&argvars[2]); 1769 if (flags == NULL) { 1770 return; 1771 } 1772 for (const char *p = flags; *p; p++) { 1773 switch (*p) { 1774 case 'b': 1775 binary = true; break; 1776 case 'a': 1777 append = true; break; 1778 case 'D': 1779 defer = true; break; 1780 case 's': 1781 do_fsync = true; break; 1782 case 'S': 1783 do_fsync = false; break; 1784 case 'p': 1785 mkdir_p = true; break; 1786 default: 1787 // Using %s, p and not %c, *p to preserve multibyte characters 1788 semsg(_("E5060: Unknown flag: %s"), p); 1789 return; 1790 } 1791 } 1792 } 1793 1794 char buf[NUMBUFLEN]; 1795 const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); 1796 if (fname == NULL) { 1797 return; 1798 } 1799 1800 if (defer && !can_add_defer()) { 1801 return; 1802 } 1803 1804 FileDescriptor fp; 1805 int error; 1806 if (*fname == NUL) { 1807 emsg(_("E482: Can't open file with an empty name")); 1808 } else if ((error = file_open(&fp, fname, 1809 ((append ? kFileAppend : kFileTruncate) 1810 | (mkdir_p ? kFileMkDir : kFileCreate) 1811 | kFileCreate), 0666)) != 0) { 1812 semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); 1813 } else { 1814 if (defer) { 1815 typval_T tv = { 1816 .v_type = VAR_STRING, 1817 .v_lock = VAR_UNLOCKED, 1818 .vval.v_string = FullName_save(fname, false), 1819 }; 1820 add_defer("delete", 1, &tv); 1821 } 1822 1823 bool write_ok; 1824 if (argvars[0].v_type == VAR_BLOB) { 1825 write_ok = write_blob(&fp, argvars[0].vval.v_blob); 1826 } else { 1827 write_ok = write_list(&fp, argvars[0].vval.v_list, binary); 1828 } 1829 if (write_ok) { 1830 rettv->vval.v_number = 0; 1831 } 1832 if ((error = file_close(&fp, do_fsync)) != 0) { 1833 semsg(_("E80: Error when closing file %s: %s"), 1834 fname, os_strerror(error)); 1835 } 1836 } 1837 } 1838 1839 /// "browse(save, title, initdir, default)" function 1840 void f_browse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1841 { 1842 rettv->vval.v_string = NULL; 1843 rettv->v_type = VAR_STRING; 1844 } 1845 1846 /// "browsedir(title, initdir)" function 1847 void f_browsedir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1848 { 1849 f_browse(argvars, rettv, fptr); 1850 }