neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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 }