diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-08-06 20:13:07 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-06 20:13:07 +0800 |
commit | b5f92c4e5c7428fe1c1f91620f4b545b30b49855 (patch) | |
tree | 58f5b5ec6625257ed7ce9514f39823442e92a10e /src/nvim/eval | |
parent | b04b263e1f827e113e51a43bab0a3d5a4a28f83f (diff) | |
download | rneovim-b5f92c4e5c7428fe1c1f91620f4b545b30b49855.tar.gz rneovim-b5f92c4e5c7428fe1c1f91620f4b545b30b49855.tar.bz2 rneovim-b5f92c4e5c7428fe1c1f91620f4b545b30b49855.zip |
refactor: extract eval/fs.c from eval/funcs.c (#29985)
In Vim a lot of filesystem functions have been moved to filepath.c.
However, some of these functions actually deal with file contents, and
Nvim's filesystem-related functions are spread out in a different way.
Therefore, it's better to use a different file for these functions.
Diffstat (limited to 'src/nvim/eval')
-rw-r--r-- | src/nvim/eval/fs.c | 1465 | ||||
-rw-r--r-- | src/nvim/eval/fs.h | 8 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 1280 |
3 files changed, 1473 insertions, 1280 deletions
diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c new file mode 100644 index 0000000000..9719caa52e --- /dev/null +++ b/src/nvim/eval/fs.c @@ -0,0 +1,1465 @@ +// eval/fs.c: Filesystem related builtin functions + +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "auto/config.h" +#include "nvim/ascii_defs.h" +#include "nvim/buffer_defs.h" +#include "nvim/cmdexpand.h" +#include "nvim/cmdexpand_defs.h" +#include "nvim/errors.h" +#include "nvim/eval.h" +#include "nvim/eval/fs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/eval/window.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_docmd.h" +#include "nvim/file_search.h" +#include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/garray_defs.h" +#include "nvim/gettext_defs.h" +#include "nvim/globals.h" +#include "nvim/macros_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_vars.h" +#include "nvim/os/fileio.h" +#include "nvim/os/fileio_defs.h" +#include "nvim/os/fs.h" +#include "nvim/os/fs_defs.h" +#include "nvim/os/os_defs.h" +#include "nvim/path.h" +#include "nvim/pos_defs.h" +#include "nvim/strings.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/fs.c.generated.h" +#endif + +static const char e_error_while_writing_str[] = N_("E80: Error while writing: %s"); + +/// "chdir(dir)" function +void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (argvars[0].v_type != VAR_STRING) { + // Returning an empty string means it failed. + // No error message, for historic reasons. + return; + } + + // Return the current directory + char *cwd = xmalloc(MAXPATHL); + if (os_dirname(cwd, MAXPATHL) != FAIL) { +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(cwd); +#endif + rettv->vval.v_string = xstrdup(cwd); + } + xfree(cwd); + + CdScope scope = kCdScopeGlobal; + if (curwin->w_localdir != NULL) { + scope = kCdScopeWindow; + } else if (curtab->tp_localdir != NULL) { + scope = kCdScopeTabpage; + } + + if (!changedir_func(argvars[0].vval.v_string, scope)) { + // Directory change failed + XFREE_CLEAR(rettv->vval.v_string); + } +} + +/// "delete()" function +void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + if (check_secure()) { + return; + } + + const char *const name = tv_get_string(&argvars[0]); + if (*name == NUL) { + emsg(_(e_invarg)); + return; + } + + char nbuf[NUMBUFLEN]; + const char *flags; + if (argvars[1].v_type != VAR_UNKNOWN) { + flags = tv_get_string_buf(&argvars[1], nbuf); + } else { + flags = ""; + } + + if (*flags == NUL) { + // delete a file + rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; + } else if (strcmp(flags, "d") == 0) { + // delete an empty directory + rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; + } else if (strcmp(flags, "rf") == 0) { + // delete a directory recursively + rettv->vval.v_number = delete_recursive(name); + } else { + semsg(_(e_invexpr2), flags); + } +} + +/// "executable()" function +void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (tv_check_for_string_arg(argvars, 0) == FAIL) { + return; + } + + // Check in $PATH and also check directly if there is a directory name + rettv->vval.v_number = os_can_exe(tv_get_string(&argvars[0]), NULL, true); +} + +/// "exepath()" function +void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) { + return; + } + + char *path = NULL; + + os_can_exe(tv_get_string(&argvars[0]), &path, true); + +#ifdef BACKSLASH_IN_FILENAME + if (path != NULL) { + slash_adjust(path); + } +#endif + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = path; +} + +/// "filereadable()" function +void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + rettv->vval.v_number = (*p && !os_isdir(p) && os_file_is_readable(p)); +} + +/// @return 0 for not writable +/// 1 for writable file +/// 2 for a dir which we have rights to write into. +void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *filename = tv_get_string(&argvars[0]); + rettv->vval.v_number = os_file_is_writable(filename); +} + +static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) +{ + char *fresult = NULL; + char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + int count = 1; + bool first = true; + bool error = false; + + rettv->vval.v_string = NULL; + rettv->v_type = VAR_STRING; + + const char *fname = tv_get_string(&argvars[0]); + + char pathbuf[NUMBUFLEN]; + if (argvars[1].v_type != VAR_UNKNOWN) { + const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); + if (p == NULL) { + error = true; + } else { + if (*p != NUL) { + path = (char *)p; + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + count = (int)tv_get_number_chk(&argvars[2], &error); + } + } + } + + if (count < 0) { + tv_list_alloc_ret(rettv, kListLenUnknown); + } + + if (*fname != NUL && !error) { + char *file_to_find = NULL; + char *search_ctx = NULL; + + do { + if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) { + xfree(fresult); + } + fresult = find_file_in_path_option(first ? (char *)fname : NULL, + first ? strlen(fname) : 0, + 0, first, path, + find_what, curbuf->b_ffname, + (find_what == FINDFILE_DIR + ? "" + : curbuf->b_p_sua), + &file_to_find, &search_ctx); + first = false; + + if (fresult != NULL && rettv->v_type == VAR_LIST) { + tv_list_append_string(rettv->vval.v_list, fresult, -1); + } + } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); + + xfree(file_to_find); + vim_findfile_cleanup(search_ctx); + } + + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = fresult; + } +} + +/// "finddir({fname}[, {path}[, {count}]])" function +void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + findfilendir(argvars, rettv, FINDFILE_DIR); +} + +/// "findfile({fname}[, {path}[, {count}]])" function +void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + findfilendir(argvars, rettv, FINDFILE_FILE); +} + +/// `getcwd([{win}[, {tab}]])` function +/// +/// Every scope not specified implies the currently selected scope object. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be a string. +void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + // Possible scope of working directory to return. + CdScope scope = kCdScopeInvalid; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTabpage] = 0, // Number of tab to look at. + }; + + char *cwd = NULL; // Current working directory to print + char *from = NULL; // The original string to copy + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + // Pre-conditions and scope extraction together + for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { + // If there is no argument there are no more scopes after it, break out. + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + emsg(_(e_invarg)); + return; + } + scope_number[i] = (int)argvars[i].vval.v_number; + // It is an error for the scope number to be less than `-1`. + if (scope_number[i] < -1) { + emsg(_(e_invarg)); + return; + } + // Use the narrowest scope the user requested + if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { + // The scope is the current iteration step. + scope = i; + } else if (scope_number[i] < 0) { + scope = i + 1; + } + } + + // Find the tabpage by number + if (scope_number[kCdScopeTabpage] > 0) { + tp = find_tabpage(scope_number[kCdScopeTabpage]); + if (!tp) { + emsg(_("E5000: Cannot find tab number.")); + return; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] >= 0) { + if (scope_number[kCdScopeTabpage] < 0) { + emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + return; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], tp); + if (!win) { + emsg(_("E5002: Cannot find window number.")); + return; + } + } + } + + cwd = xmalloc(MAXPATHL); + + switch (scope) { + case kCdScopeWindow: + assert(win); + from = win->w_localdir; + if (from) { + break; + } + FALLTHROUGH; + case kCdScopeTabpage: + assert(tp); + from = tp->tp_localdir; + if (from) { + break; + } + FALLTHROUGH; + case kCdScopeGlobal: + if (globaldir) { // `globaldir` is not always set. + from = globaldir; + break; + } + FALLTHROUGH; // In global directory, just need to get OS CWD. + case kCdScopeInvalid: // If called without any arguments, get OS CWD. + if (os_dirname(cwd, MAXPATHL) == FAIL) { + from = ""; // Return empty string on failure. + } + } + + if (from) { + xstrlcpy(cwd, from, MAXPATHL); + } + + rettv->vval.v_string = xstrdup(cwd); +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(rettv->vval.v_string); +#endif + + xfree(cwd); +} + +/// "getfperm({fname})" function +void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *perm = NULL; + char flags[] = "rwx"; + + const char *filename = tv_get_string(&argvars[0]); + int32_t file_perm = os_getperm(filename); + if (file_perm >= 0) { + perm = xstrdup("---------"); + for (int i = 0; i < 9; i++) { + if (file_perm & (1 << (8 - i))) { + perm[i] = flags[i % 3]; + } + } + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = perm; +} + +/// "getfsize({fname})" function +void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *fname = tv_get_string(&argvars[0]); + + rettv->v_type = VAR_NUMBER; + + FileInfo file_info; + if (os_fileinfo(fname, &file_info)) { + uint64_t filesize = os_fileinfo_size(&file_info); + if (os_isdir(fname)) { + rettv->vval.v_number = 0; + } else { + rettv->vval.v_number = (varnumber_T)filesize; + + // non-perfect check for overflow + if ((uint64_t)rettv->vval.v_number != filesize) { + rettv->vval.v_number = -2; + } + } + } else { + rettv->vval.v_number = -1; + } +} + +/// "getftime({fname})" function +void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *fname = tv_get_string(&argvars[0]); + + FileInfo file_info; + if (os_fileinfo(fname, &file_info)) { + rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; + } else { + rettv->vval.v_number = -1; + } +} + +/// "getftype({fname})" function +void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *type = NULL; + char *t; + + const char *fname = tv_get_string(&argvars[0]); + + rettv->v_type = VAR_STRING; + FileInfo file_info; + if (os_fileinfo_link(fname, &file_info)) { + uint64_t mode = file_info.stat.st_mode; + if (S_ISREG(mode)) { + t = "file"; + } else if (S_ISDIR(mode)) { + t = "dir"; + } else if (S_ISLNK(mode)) { + t = "link"; + } else if (S_ISBLK(mode)) { + t = "bdev"; + } else if (S_ISCHR(mode)) { + t = "cdev"; + } else if (S_ISFIFO(mode)) { + t = "fifo"; + } else if (S_ISSOCK(mode)) { + t = "socket"; + } else { + t = "other"; + } + type = xstrdup(t); + } + rettv->vval.v_string = type; +} + +/// "glob()" function +void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int options = WILD_SILENT|WILD_USE_NL; + expand_T xpc; + bool error = false; + + // When the optional second argument is non-zero, don't remove matches + // for 'wildignore' and don't put matches for 'suffixes' at the end. + rettv->v_type = VAR_STRING; + if (argvars[1].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[1], &error)) { + options |= WILD_KEEP_ALL; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[2], &error)) { + tv_list_set_ret(rettv, NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[3], &error)) { + options |= WILD_ALLLINKS; + } + } + } + if (!error) { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + if (p_wic) { + options += WILD_ICASE; + } + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = ExpandOne(&xpc, (char *) + tv_get_string(&argvars[0]), NULL, options, + WILD_ALL); + } else { + ExpandOne(&xpc, (char *)tv_get_string(&argvars[0]), NULL, options, + WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); + for (int i = 0; i < xpc.xp_numfiles; i++) { + tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); + } + ExpandCleanup(&xpc); + } + } else { + rettv->vval.v_string = NULL; + } +} + +/// "globpath()" function +void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. + bool error = false; + + // Return a string, or a list if the optional third argument is non-zero. + rettv->v_type = VAR_STRING; + + if (argvars[2].v_type != VAR_UNKNOWN) { + // When the optional second argument is non-zero, don't remove matches + // for 'wildignore' and don't put matches for 'suffixes' at the end. + if (tv_get_number_chk(&argvars[2], &error)) { + flags |= WILD_KEEP_ALL; + } + + if (argvars[3].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[3], &error)) { + tv_list_set_ret(rettv, NULL); + } + if (argvars[4].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[4], &error)) { + flags |= WILD_ALLLINKS; + } + } + } + + char buf1[NUMBUFLEN]; + const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); + if (file != NULL && !error) { + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 10); + globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false); + + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); + } else { + tv_list_alloc_ret(rettv, ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + tv_list_append_string(rettv->vval.v_list, + ((const char **)(ga.ga_data))[i], -1); + } + } + + ga_clear_strings(&ga); + } else { + rettv->vval.v_string = NULL; + } +} + +/// "glob2regpat()" function +void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = pat == NULL ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false); +} + +/// `haslocaldir([{win}[, {tab}]])` function +/// +/// Returns `1` if the scope object has a local directory, `0` otherwise. If a +/// scope object is not specified the current one is implied. This function +/// share a lot of code with `f_getcwd`. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be either the number `1` or `0`. +void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + // Possible scope of working directory to return. + CdScope scope = kCdScopeInvalid; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTabpage] = 0, // Number of tab to look at. + }; + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + // Pre-conditions and scope extraction together + for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + emsg(_(e_invarg)); + return; + } + scope_number[i] = (int)argvars[i].vval.v_number; + if (scope_number[i] < -1) { + emsg(_(e_invarg)); + return; + } + // Use the narrowest scope the user requested + if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { + // The scope is the current iteration step. + scope = i; + } else if (scope_number[i] < 0) { + scope = i + 1; + } + } + + // If the user didn't specify anything, default to window scope + if (scope == kCdScopeInvalid) { + scope = MIN_CD_SCOPE; + } + + // Find the tabpage by number + if (scope_number[kCdScopeTabpage] > 0) { + tp = find_tabpage(scope_number[kCdScopeTabpage]); + if (!tp) { + emsg(_("E5000: Cannot find tab number.")); + return; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] >= 0) { + if (scope_number[kCdScopeTabpage] < 0) { + emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + return; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], tp); + if (!win) { + emsg(_("E5002: Cannot find window number.")); + return; + } + } + } + + switch (scope) { + case kCdScopeWindow: + assert(win); + rettv->vval.v_number = win->w_localdir ? 1 : 0; + break; + case kCdScopeTabpage: + assert(tp); + rettv->vval.v_number = tp->tp_localdir ? 1 : 0; + break; + case kCdScopeGlobal: + // The global scope never has a local directory + break; + case kCdScopeInvalid: + // We should never get here + abort(); + } +} + +/// "isdirectory()" function +void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0])); +} + +/// "mkdir()" function +void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int prot = 0755; + + rettv->vval.v_number = FAIL; + if (check_secure()) { + return; + } + + char buf[NUMBUFLEN]; + const char *const dir = tv_get_string_buf(&argvars[0], buf); + if (*dir == NUL) { + return; + } + + if (*path_tail(dir) == NUL) { + // Remove trailing slashes. + *path_tail_with_sep((char *)dir) = NUL; + } + + bool defer = false; + bool defer_recurse = false; + char *created = NULL; + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_UNKNOWN) { + prot = (int)tv_get_number_chk(&argvars[2], NULL); + if (prot == -1) { + return; + } + } + const char *arg2 = tv_get_string(&argvars[1]); + defer = vim_strchr(arg2, 'D') != NULL; + defer_recurse = vim_strchr(arg2, 'R') != NULL; + if ((defer || defer_recurse) && !can_add_defer()) { + return; + } + + if (vim_strchr(arg2, 'p') != NULL) { + char *failed_dir; + int ret = os_mkdir_recurse(dir, prot, &failed_dir, + defer || defer_recurse ? &created : NULL); + if (ret != 0) { + semsg(_(e_mkdir), failed_dir, os_strerror(ret)); + xfree(failed_dir); + rettv->vval.v_number = FAIL; + return; + } + rettv->vval.v_number = OK; + } + } + if (rettv->vval.v_number == FAIL) { + rettv->vval.v_number = vim_mkdir_emsg(dir, prot); + } + + // Handle "D" and "R": deferred deletion of the created directory. + if (rettv->vval.v_number == OK + && created == NULL && (defer || defer_recurse)) { + created = FullName_save(dir, false); + } + if (created != NULL) { + typval_T tv[2]; + tv[0].v_type = VAR_STRING; + tv[0].v_lock = VAR_UNLOCKED; + tv[0].vval.v_string = created; + tv[1].v_type = VAR_STRING; + tv[1].v_lock = VAR_UNLOCKED; + tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d"); + add_defer("delete", 2, tv); + } +} + +/// "pathshorten()" function +void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int trim_len = 1; + + if (argvars[1].v_type != VAR_UNKNOWN) { + trim_len = (int)tv_get_number(&argvars[1]); + if (trim_len < 1) { + trim_len = 1; + } + } + + rettv->v_type = VAR_STRING; + const char *p = tv_get_string_chk(&argvars[0]); + if (p == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = xstrdup(p); + shorten_dir_len(rettv->vval.v_string, trim_len); + } +} + +/// Evaluate "expr" (= "context") for readdir(). +static varnumber_T readdir_checkitem(void *context, const char *name) + FUNC_ATTR_NONNULL_ALL +{ + typval_T *expr = (typval_T *)context; + typval_T argv[2]; + varnumber_T retval = 0; + bool error = false; + + if (expr->v_type == VAR_UNKNOWN) { + return 1; + } + + typval_T save_val; + prepare_vimvar(VV_VAL, &save_val); + set_vim_var_string(VV_VAL, name, -1); + argv[0].v_type = VAR_STRING; + argv[0].vval.v_string = (char *)name; + + typval_T rettv; + if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) { + goto theend; + } + + retval = tv_get_number_chk(&rettv, &error); + if (error) { + retval = -1; + } + + tv_clear(&rettv); + +theend: + set_vim_var_string(VV_VAL, NULL, 0); + restore_vimvar(VV_VAL, &save_val); + return retval; +} + +/// "readdir()" function +void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, kListLenUnknown); + + const char *path = tv_get_string(&argvars[0]); + typval_T *expr = &argvars[1]; + garray_T ga; + int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); + if (ret == OK && ga.ga_len > 0) { + for (int i = 0; i < ga.ga_len; i++) { + const char *p = ((const char **)ga.ga_data)[i]; + tv_list_append_string(rettv->vval.v_list, p, -1); + } + } + ga_clear_strings(&ga); +} + +/// Read blob from file "fd". +/// Caller has allocated a blob in "rettv". +/// +/// @param[in] fd File to read from. +/// @param[in,out] rettv Blob to write to. +/// @param[in] offset Read the file from the specified offset. +/// @param[in] size Read the specified size, or -1 if no limit. +/// +/// @return OK on success, or FAIL on failure. +static int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg) + FUNC_ATTR_NONNULL_ALL +{ + blob_T *const blob = rettv->vval.v_blob; + FileInfo file_info; + if (!os_fileinfo_fd(fileno(fd), &file_info)) { + return FAIL; // can't read the file, error + } + + int whence; + off_T size = size_arg; + const off_T file_size = (off_T)os_fileinfo_size(&file_info); + if (offset >= 0) { + // The size defaults to the whole file. If a size is given it is + // limited to not go past the end of the file. + if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) { + // size may become negative, checked below + size = (off_T)os_fileinfo_size(&file_info) - offset; + } + whence = SEEK_SET; + } else { + // limit the offset to not go before the start of the file + if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) { + offset = -file_size; + } + // Size defaults to reading until the end of the file. + if (size == -1 || size > -offset) { + size = -offset; + } + whence = SEEK_END; + } + if (size <= 0) { + return OK; + } + if (offset != 0 && vim_fseek(fd, offset, whence) != 0) { + return OK; + } + + ga_grow(&blob->bv_ga, (int)size); + blob->bv_ga.ga_len = (int)size; + if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd) + < (size_t)blob->bv_ga.ga_len) { + // An empty blob is returned on error. + tv_blob_free(rettv->vval.v_blob); + rettv->vval.v_blob = NULL; + return FAIL; + } + return OK; +} + +/// "readfile()" or "readblob()" function +static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob) +{ + bool binary = false; + bool blob = always_blob; + FILE *fd; + char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 + int io_size = sizeof(buf); + char *prev = NULL; // previously read bytes, if any + ptrdiff_t prevlen = 0; // length of data in prev + ptrdiff_t prevsize = 0; // size of prev buffer + int64_t maxline = MAXLNUM; + off_T offset = 0; + off_T size = -1; + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (always_blob) { + offset = (off_T)tv_get_number(&argvars[1]); + if (argvars[2].v_type != VAR_UNKNOWN) { + size = (off_T)tv_get_number(&argvars[2]); + } + } else { + if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { + binary = true; + } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) { + blob = true; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + maxline = tv_get_number(&argvars[2]); + } + } + } + + if (blob) { + tv_blob_alloc_ret(rettv); + } else { + tv_list_alloc_ret(rettv, kListLenUnknown); + } + + // Always open the file in binary mode, library functions have a mind of + // their own about CR-LF conversion. + const char *const fname = tv_get_string(&argvars[0]); + + if (os_isdir(fname)) { + semsg(_(e_isadir2), fname); + return; + } + if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { + semsg(_(e_notopen), *fname == NUL ? _("<empty>") : fname); + return; + } + + if (blob) { + if (read_blob(fd, rettv, offset, size) == FAIL) { + semsg(_(e_notread), fname); + } + fclose(fd); + return; + } + + list_T *const l = rettv->vval.v_list; + + while (maxline < 0 || tv_list_len(l) < maxline) { + int readlen = (int)fread(buf, 1, (size_t)io_size, fd); + + // This for loop processes what was read, but is also entered at end + // of file so that either: + // - an incomplete line gets written + // - a "binary" file gets an empty line at the end if it ends in a + // newline. + char *p; // Position in buf. + char *start; // Start of current line. + for (p = buf, start = buf; + p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); + p++) { + if (readlen <= 0 || *p == '\n') { + char *s = NULL; + size_t len = (size_t)(p - start); + + // Finished a line. Remove CRs before NL. + if (readlen > 0 && !binary) { + while (len > 0 && start[len - 1] == '\r') { + len--; + } + // removal may cross back to the "prev" string + if (len == 0) { + while (prevlen > 0 && prev[prevlen - 1] == '\r') { + prevlen--; + } + } + } + if (prevlen == 0) { + assert(len < INT_MAX); + s = xmemdupz(start, len); + } else { + // Change "prev" buffer to be the right size. This way + // the bytes are only copied once, and very long lines are + // allocated only once. + s = xrealloc(prev, (size_t)prevlen + len + 1); + memcpy(s + prevlen, start, len); + s[(size_t)prevlen + len] = NUL; + prev = NULL; // the list will own the string + prevlen = prevsize = 0; + } + + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = s, + }); + + start = p + 1; // Step over newline. + if (maxline < 0) { + if (tv_list_len(l) > -maxline) { + assert(tv_list_len(l) == 1 + (-maxline)); + tv_list_item_remove(l, tv_list_first(l)); + } + } else if (tv_list_len(l) >= maxline) { + assert(tv_list_len(l) == maxline); + break; + } + if (readlen <= 0) { + break; + } + } else if (*p == NUL) { + *p = '\n'; + // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + // when finding the BF and check the previous two bytes. + } else if ((uint8_t)(*p) == 0xbf && !binary) { + // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, + // these may be in the "prev" string. + char back1 = p >= buf + 1 ? p[-1] + : prevlen >= 1 ? prev[prevlen - 1] : NUL; + char back2 = p >= buf + 2 ? p[-2] + : (p == buf + 1 && prevlen >= 1 + ? prev[prevlen - 1] + : prevlen >= 2 ? prev[prevlen - 2] : NUL); + + if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) { + char *dest = p - 2; + + // Usually a BOM is at the beginning of a file, and so at + // the beginning of a line; then we can just step over it. + if (start == dest) { + start = p + 1; + } else { + // have to shuffle buf to close gap + int adjust_prevlen = 0; + + if (dest < buf) { + // adjust_prevlen must be 1 or 2. + adjust_prevlen = (int)(buf - dest); + dest = buf; + } + if (readlen > p - buf + 1) { + memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1); + } + readlen -= 3 - adjust_prevlen; + prevlen -= adjust_prevlen; + p = dest - 1; + } + } + } + } // for + + if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { + break; + } + if (start < p) { + // There's part of a line in buf, store it in "prev". + if (p - start + prevlen >= prevsize) { + // A common use case is ordinary text files and "prev" gets a + // fragment of a line, so the first allocation is made + // small, to avoid repeatedly 'allocing' large and + // 'reallocing' small. + if (prevsize == 0) { + prevsize = p - start; + } else { + ptrdiff_t grow50pc = (prevsize * 3) / 2; + ptrdiff_t growmin = (p - start) * 2 + prevlen; + prevsize = grow50pc > growmin ? grow50pc : growmin; + } + prev = xrealloc(prev, (size_t)prevsize); + } + // Add the line part to end of "prev". + memmove(prev + prevlen, start, (size_t)(p - start)); + prevlen += p - start; + } + } // while + + xfree(prev); + fclose(fd); +} + +/// "readblob()" function +void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, true); +} + +/// "readfile()" function +void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, false); +} + +/// "rename({from}, {to})" function +void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (check_secure()) { + rettv->vval.v_number = -1; + } else { + char buf[NUMBUFLEN]; + rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]), + tv_get_string_buf(&argvars[1], buf)); + } +} + +/// "resolve()" function +void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + const char *fname = tv_get_string(&argvars[0]); +#ifdef MSWIN + char *v = os_resolve_shortcut(fname); + if (v == NULL) { + if (os_is_reparse_point_include(fname)) { + v = os_realpath(fname, NULL, MAXPATHL + 1); + } + } + rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); +#else +# ifdef HAVE_READLINK + { + bool is_relative_to_current = false; + bool has_trailing_pathsep = false; + int limit = 100; + + char *p = xstrdup(fname); + + if (p[0] == '.' && (vim_ispathsep(p[1]) + || (p[1] == '.' && (vim_ispathsep(p[2]))))) { + is_relative_to_current = true; + } + + ptrdiff_t len = (ptrdiff_t)strlen(p); + if (len > 1 && after_pathsep(p, p + len)) { + has_trailing_pathsep = true; + p[len - 1] = NUL; // The trailing slash breaks readlink(). + } + + char *q = (char *)path_next_component(p); + char *remain = NULL; + if (*q != NUL) { + // Separate the first path component in "p", and keep the + // remainder (beginning with the path separator). + remain = xstrdup(q - 1); + q[-1] = NUL; + } + + char *const buf = xmallocz(MAXPATHL); + + char *cpy; + while (true) { + while (true) { + len = readlink(p, buf, MAXPATHL); + if (len <= 0) { + break; + } + buf[len] = NUL; + + if (limit-- == 0) { + xfree(p); + xfree(remain); + emsg(_("E655: Too many symbolic links (cycle?)")); + rettv->vval.v_string = NULL; + xfree(buf); + return; + } + + // Ensure that the result will have a trailing path separator + // if the argument has one. + if (remain == NULL && has_trailing_pathsep) { + add_pathsep(buf); + } + + // Separate the first path component in the link value and + // concatenate the remainders. + q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); + if (*q != NUL) { + cpy = remain; + remain = remain != NULL ? concat_str(q - 1, remain) : xstrdup(q - 1); + xfree(cpy); + q[-1] = NUL; + } + + q = path_tail(p); + if (q > p && *q == NUL) { + // Ignore trailing path separator. + p[q - p - 1] = NUL; + q = path_tail(p); + } + if (q > p && !path_is_absolute(buf)) { + // Symlink is relative to directory of argument. Replace the + // symlink with the resolved name in the same directory. + const size_t p_len = strlen(p); + const size_t buf_len = strlen(buf); + p = xrealloc(p, p_len + buf_len + 1); + memcpy(path_tail(p), buf, buf_len + 1); + } else { + xfree(p); + p = xstrdup(buf); + } + } + + if (remain == NULL) { + break; + } + + // Append the first path component of "remain" to "p". + q = (char *)path_next_component(remain + 1); + len = q - remain - (*q != NUL); + const size_t p_len = strlen(p); + cpy = xmallocz(p_len + (size_t)len); + memcpy(cpy, p, p_len + 1); + xstrlcat(cpy + p_len, remain, (size_t)len + 1); + xfree(p); + p = cpy; + + // Shorten "remain". + if (*q != NUL) { + STRMOVE(remain, q - 1); + } else { + XFREE_CLEAR(remain); + } + } + + // If the result is a relative path name, make it explicitly relative to + // the current directory if and only if the argument had this form. + if (!vim_ispathsep(*p)) { + if (is_relative_to_current + && *p != NUL + && !(p[0] == '.' + && (p[1] == NUL + || vim_ispathsep(p[1]) + || (p[1] == '.' + && (p[2] == NUL + || vim_ispathsep(p[2])))))) { + // Prepend "./". + cpy = concat_str("./", p); + xfree(p); + p = cpy; + } else if (!is_relative_to_current) { + // Strip leading "./". + q = p; + while (q[0] == '.' && vim_ispathsep(q[1])) { + q += 2; + } + if (q > p) { + STRMOVE(p, p + 2); + } + } + } + + // Ensure that the result will have no trailing path separator + // if the argument had none. But keep "/" or "//". + if (!has_trailing_pathsep) { + q = p + strlen(p); + if (after_pathsep(p, q)) { + *path_tail_with_sep(p) = NUL; + } + } + + rettv->vval.v_string = p; + xfree(buf); + } +# else + char *v = os_realpath(fname, NULL, MAXPATHL + 1); + rettv->vval.v_string = v == NULL ? xstrdup(fname) : v; +# endif +#endif + + simplify_filename(rettv->vval.v_string); +} + +/// "simplify()" function +void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + rettv->vval.v_string = xstrdup(p); + simplify_filename(rettv->vval.v_string); // Simplify in place. + rettv->v_type = VAR_STRING; +} + +/// "tempname()" function +void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_tempname(); +} + +/// Write "list" of strings to file "fd". +/// +/// @param fp File to write to. +/// @param[in] list List to write. +/// @param[in] binary Whether to write in binary mode. +/// +/// @return true in case of success, false otherwise. +static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) + FUNC_ATTR_NONNULL_ARG(1) +{ + int error = 0; + TV_LIST_ITER_CONST(list, li, { + const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + if (s == NULL) { + return false; + } + const char *hunk_start = s; + for (const char *p = hunk_start;; p++) { + if (*p == NUL || *p == NL) { + if (p != hunk_start) { + const ptrdiff_t written = file_write(fp, hunk_start, + (size_t)(p - hunk_start)); + if (written < 0) { + error = (int)written; + goto write_list_error; + } + } + if (*p == NUL) { + break; + } else { + hunk_start = p + 1; + const ptrdiff_t written = file_write(fp, (char[]){ NUL }, 1); + if (written < 0) { + error = (int)written; + break; + } + } + } + } + if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) { + const ptrdiff_t written = file_write(fp, "\n", 1); + if (written < 0) { + error = (int)written; + goto write_list_error; + } + } + }); + if ((error = file_flush(fp)) != 0) { + goto write_list_error; + } + return true; +write_list_error: + semsg(_(e_error_while_writing_str), os_strerror(error)); + return false; +} + +/// Write a blob to file with descriptor `fp`. +/// +/// @param[in] fp File to write to. +/// @param[in] blob Blob to write. +/// +/// @return true on success, or false on failure. +static bool write_blob(FileDescriptor *const fp, const blob_T *const blob) + FUNC_ATTR_NONNULL_ARG(1) +{ + int error = 0; + const int len = tv_blob_len(blob); + if (len > 0) { + const ptrdiff_t written = file_write(fp, blob->bv_ga.ga_data, (size_t)len); + if (written < (ptrdiff_t)len) { + error = (int)written; + goto write_blob_error; + } + } + error = file_flush(fp); + if (error != 0) { + goto write_blob_error; + } + return true; +write_blob_error: + semsg(_(e_error_while_writing_str), os_strerror(error)); + return false; +} + +/// "writefile()" function +void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + + if (check_secure()) { + return; + } + + if (argvars[0].v_type == VAR_LIST) { + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { + return; + } + }); + } else if (argvars[0].v_type != VAR_BLOB) { + semsg(_(e_invarg2), + _("writefile() first argument must be a List or a Blob")); + return; + } + + bool binary = false; + bool append = false; + bool defer = false; + bool do_fsync = !!p_fs; + bool mkdir_p = false; + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const flags = tv_get_string_chk(&argvars[2]); + if (flags == NULL) { + return; + } + for (const char *p = flags; *p; p++) { + switch (*p) { + case 'b': + binary = true; break; + case 'a': + append = true; break; + case 'D': + defer = true; break; + case 's': + do_fsync = true; break; + case 'S': + do_fsync = false; break; + case 'p': + mkdir_p = true; break; + default: + // Using %s, p and not %c, *p to preserve multibyte characters + semsg(_("E5060: Unknown flag: %s"), p); + return; + } + } + } + + char buf[NUMBUFLEN]; + const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); + if (fname == NULL) { + return; + } + + if (defer && !can_add_defer()) { + return; + } + + FileDescriptor fp; + int error; + if (*fname == NUL) { + emsg(_("E482: Can't open file with an empty name")); + } else if ((error = file_open(&fp, fname, + ((append ? kFileAppend : kFileTruncate) + | (mkdir_p ? kFileMkDir : kFileCreate) + | kFileCreate), 0666)) != 0) { + semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); + } else { + if (defer) { + typval_T tv = { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = FullName_save(fname, false), + }; + add_defer("delete", 1, &tv); + } + + bool write_ok; + if (argvars[0].v_type == VAR_BLOB) { + write_ok = write_blob(&fp, argvars[0].vval.v_blob); + } else { + write_ok = write_list(&fp, argvars[0].vval.v_list, binary); + } + if (write_ok) { + rettv->vval.v_number = 0; + } + if ((error = file_close(&fp, do_fsync)) != 0) { + semsg(_("E80: Error when closing file %s: %s"), + fname, os_strerror(error)); + } + } +} diff --git a/src/nvim/eval/fs.h b/src/nvim/eval/fs.h new file mode 100644 index 0000000000..ae6a93d0dc --- /dev/null +++ b/src/nvim/eval/fs.h @@ -0,0 +1,8 @@ +#pragma once + +#include "nvim/eval/typval_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" // IWYU pragma: keep + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/fs.h.generated.h" +#endif diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 2f51532ec4..de1d784577 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1,5 +1,4 @@ #include <assert.h> -#include <fcntl.h> #include <float.h> #include <inttypes.h> #include <limits.h> @@ -10,7 +9,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/stat.h> #include <time.h> #include <uv.h> @@ -58,8 +56,6 @@ #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/file_search.h" -#include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/garray_defs.h" #include "nvim/getchar.h" @@ -102,10 +98,7 @@ #include "nvim/option_vars.h" #include "nvim/optionstr.h" #include "nvim/os/dl.h" -#include "nvim/os/fileio.h" -#include "nvim/os/fileio_defs.h" #include "nvim/os/fs.h" -#include "nvim/os/fs_defs.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" #include "nvim/os/pty_process.h" @@ -773,41 +766,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) get_col(argvars, rettv, true); } -/// "chdir(dir)" function -static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - if (argvars[0].v_type != VAR_STRING) { - // Returning an empty string means it failed. - // No error message, for historic reasons. - return; - } - - // Return the current directory - char *cwd = xmalloc(MAXPATHL); - if (os_dirname(cwd, MAXPATHL) != FAIL) { -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(cwd); -#endif - rettv->vval.v_string = xstrdup(cwd); - } - xfree(cwd); - - CdScope scope = kCdScopeGlobal; - if (curwin->w_localdir != NULL) { - scope = kCdScopeWindow; - } else if (curtab->tp_localdir != NULL) { - scope = kCdScopeTabpage; - } - - if (!changedir_func(argvars[0].vval.v_string, scope)) { - // Directory change failed - XFREE_CLEAR(rettv->vval.v_string); - } -} - /// "cindent(lnum)" function static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -1251,42 +1209,6 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() : 0)); } -/// "delete()" function -static void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - if (check_secure()) { - return; - } - - const char *const name = tv_get_string(&argvars[0]); - if (*name == NUL) { - emsg(_(e_invarg)); - return; - } - - char nbuf[NUMBUFLEN]; - const char *flags; - if (argvars[1].v_type != VAR_UNKNOWN) { - flags = tv_get_string_buf(&argvars[1], nbuf); - } else { - flags = ""; - } - - if (*flags == NUL) { - // delete a file - rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "d") == 0) { - // delete an empty directory - rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "rf") == 0) { - // delete a directory recursively - rettv->vval.v_number = delete_recursive(name); - } else { - semsg(_(e_invexpr2), flags); - } -} - /// dictwatcheradd(dict, key, funcref) function static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -1569,17 +1491,6 @@ static void f_eventhandler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr rettv->vval.v_number = vgetc_busy; } -/// "executable()" function -static void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - if (tv_check_for_string_arg(argvars, 0) == FAIL) { - return; - } - - // Check in $PATH and also check directly if there is a directory name - rettv->vval.v_number = os_can_exe(tv_get_string(&argvars[0]), NULL, true); -} - typedef struct { const list_T *const l; const listitem_T *li; @@ -1683,27 +1594,6 @@ static void f_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) execute_common(argvars, rettv, 0); } -/// "exepath()" function -static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) { - return; - } - - char *path = NULL; - - os_can_exe(tv_get_string(&argvars[0]), &path, true); - -#ifdef BACKSLASH_IN_FILENAME - if (path != NULL) { - slash_adjust(path); - } -#endif - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = path; -} - /// "exists()" function static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2105,100 +1995,6 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) cstr_as_string(flags), true); } -/// "filereadable()" function -static void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_number = - (*p && !os_isdir(p) && os_file_is_readable(p)); -} - -/// @return 0 for not writable -/// 1 for writable file -/// 2 for a dir which we have rights to write into. -static void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *filename = tv_get_string(&argvars[0]); - rettv->vval.v_number = os_file_is_writable(filename); -} - -static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) -{ - char *fresult = NULL; - char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; - int count = 1; - bool first = true; - bool error = false; - - rettv->vval.v_string = NULL; - rettv->v_type = VAR_STRING; - - const char *fname = tv_get_string(&argvars[0]); - - char pathbuf[NUMBUFLEN]; - if (argvars[1].v_type != VAR_UNKNOWN) { - const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); - if (p == NULL) { - error = true; - } else { - if (*p != NUL) { - path = (char *)p; - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - count = (int)tv_get_number_chk(&argvars[2], &error); - } - } - } - - if (count < 0) { - tv_list_alloc_ret(rettv, kListLenUnknown); - } - - if (*fname != NUL && !error) { - char *file_to_find = NULL; - char *search_ctx = NULL; - - do { - if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) { - xfree(fresult); - } - fresult = find_file_in_path_option(first ? (char *)fname : NULL, - first ? strlen(fname) : 0, - 0, first, path, - find_what, curbuf->b_ffname, - (find_what == FINDFILE_DIR - ? "" - : curbuf->b_p_sua), - &file_to_find, &search_ctx); - first = false; - - if (fresult != NULL && rettv->v_type == VAR_LIST) { - tv_list_append_string(rettv->vval.v_list, fresult, -1); - } - } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); - - xfree(file_to_find); - vim_findfile_cleanup(search_ctx); - } - - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = fresult; - } -} - -/// "finddir({fname}[, {path}[, {count}]])" function -static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - findfilendir(argvars, rettv, FINDFILE_DIR); -} - -/// "findfile({fname}[, {path}[, {count}]])" function -static void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - findfilendir(argvars, rettv, FINDFILE_FILE); -} - /// "float2nr({float})" function static void f_float2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2702,127 +2498,6 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); } -/// `getcwd([{win}[, {tab}]])` function -/// -/// Every scope not specified implies the currently selected scope object. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be a string. -static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTabpage] = 0, // Number of tab to look at. - }; - - char *cwd = NULL; // Current working directory to print - char *from = NULL; // The original string to copy - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - // If there is no argument there are no more scopes after it, break out. - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - emsg(_(e_invarg)); - return; - } - scope_number[i] = (int)argvars[i].vval.v_number; - // It is an error for the scope number to be less than `-1`. - if (scope_number[i] < -1) { - emsg(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // Find the tabpage by number - if (scope_number[kCdScopeTabpage] > 0) { - tp = find_tabpage(scope_number[kCdScopeTabpage]); - if (!tp) { - emsg(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTabpage] < 0) { - emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - emsg(_("E5002: Cannot find window number.")); - return; - } - } - } - - cwd = xmalloc(MAXPATHL); - - switch (scope) { - case kCdScopeWindow: - assert(win); - from = win->w_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeTabpage: - assert(tp); - from = tp->tp_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeGlobal: - if (globaldir) { // `globaldir` is not always set. - from = globaldir; - break; - } - FALLTHROUGH; // In global directory, just need to get OS CWD. - case kCdScopeInvalid: // If called without any arguments, get OS CWD. - if (os_dirname(cwd, MAXPATHL) == FAIL) { - from = ""; // Return empty string on failure. - } - } - - if (from) { - xstrlcpy(cwd, from, MAXPATHL); - } - - rettv->vval.v_string = xstrdup(cwd); -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(rettv->vval.v_string); -#endif - - xfree(cwd); -} - /// "getfontname()" function static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2830,98 +2505,6 @@ static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = NULL; } -/// "getfperm({fname})" function -static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char *perm = NULL; - char flags[] = "rwx"; - - const char *filename = tv_get_string(&argvars[0]); - int32_t file_perm = os_getperm(filename); - if (file_perm >= 0) { - perm = xstrdup("---------"); - for (int i = 0; i < 9; i++) { - if (file_perm & (1 << (8 - i))) { - perm[i] = flags[i % 3]; - } - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = perm; -} - -/// "getfsize({fname})" function -static void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_NUMBER; - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - uint64_t filesize = os_fileinfo_size(&file_info); - if (os_isdir(fname)) { - rettv->vval.v_number = 0; - } else { - rettv->vval.v_number = (varnumber_T)filesize; - - // non-perfect check for overflow - if ((uint64_t)rettv->vval.v_number != filesize) { - rettv->vval.v_number = -2; - } - } - } else { - rettv->vval.v_number = -1; - } -} - -/// "getftime({fname})" function -static void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; - } else { - rettv->vval.v_number = -1; - } -} - -/// "getftype({fname})" function -static void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char *type = NULL; - char *t; - - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_STRING; - FileInfo file_info; - if (os_fileinfo_link(fname, &file_info)) { - uint64_t mode = file_info.stat.st_mode; - if (S_ISREG(mode)) { - t = "file"; - } else if (S_ISDIR(mode)) { - t = "dir"; - } else if (S_ISLNK(mode)) { - t = "link"; - } else if (S_ISBLK(mode)) { - t = "bdev"; - } else if (S_ISCHR(mode)) { - t = "cdev"; - } else if (S_ISFIFO(mode)) { - t = "fifo"; - } else if (S_ISSOCK(mode)) { - t = "socket"; - } else { - t = "other"; - } - type = xstrdup(t); - } - rettv->vval.v_string = type; -} - /// "getjumplist()" function static void f_getjumplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3464,113 +3047,6 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) time_watcher_close(tw, dummy_timer_close_cb); } -/// "glob()" function -static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int options = WILD_SILENT|WILD_USE_NL; - expand_T xpc; - bool error = false; - - // When the optional second argument is non-zero, don't remove matches - // for 'wildignore' and don't put matches for 'suffixes' at the end. - rettv->v_type = VAR_STRING; - if (argvars[1].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[1], &error)) { - options |= WILD_KEEP_ALL; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[2], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[3], &error)) { - options |= WILD_ALLLINKS; - } - } - } - if (!error) { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - if (p_wic) { - options += WILD_ICASE; - } - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ExpandOne(&xpc, (char *) - tv_get_string(&argvars[0]), NULL, options, - WILD_ALL); - } else { - ExpandOne(&xpc, (char *)tv_get_string(&argvars[0]), NULL, options, - WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); - } - ExpandCleanup(&xpc); - } - } else { - rettv->vval.v_string = NULL; - } -} - -/// "globpath()" function -static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. - bool error = false; - - // Return a string, or a list if the optional third argument is non-zero. - rettv->v_type = VAR_STRING; - - if (argvars[2].v_type != VAR_UNKNOWN) { - // When the optional second argument is non-zero, don't remove matches - // for 'wildignore' and don't put matches for 'suffixes' at the end. - if (tv_get_number_chk(&argvars[2], &error)) { - flags |= WILD_KEEP_ALL; - } - - if (argvars[3].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[3], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[4].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[4], &error)) { - flags |= WILD_ALLLINKS; - } - } - } - - char buf1[NUMBUFLEN]; - const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); - if (file != NULL && !error) { - garray_T ga; - ga_init(&ga, (int)sizeof(char *), 10); - globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false); - - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); - } else { - tv_list_alloc_ret(rettv, ga.ga_len); - for (int i = 0; i < ga.ga_len; i++) { - tv_list_append_string(rettv->vval.v_list, - ((const char **)(ga.ga_data))[i], -1); - } - } - - ga_clear_strings(&ga); - } else { - rettv->vval.v_string = NULL; - } -} - -/// "glob2regpat()" function -static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (pat == NULL) ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false); -} - /// "gettext()" function static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3790,106 +3266,6 @@ static bool has_wsl(void) return has_wsl == kTrue; } -/// `haslocaldir([{win}[, {tab}]])` function -/// -/// Returns `1` if the scope object has a local directory, `0` otherwise. If a -/// scope object is not specified the current one is implied. This function -/// share a lot of code with `f_getcwd`. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be either the number `1` or `0`. -static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTabpage] = 0, // Number of tab to look at. - }; - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - emsg(_(e_invarg)); - return; - } - scope_number[i] = (int)argvars[i].vval.v_number; - if (scope_number[i] < -1) { - emsg(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - - // Find the tabpage by number - if (scope_number[kCdScopeTabpage] > 0) { - tp = find_tabpage(scope_number[kCdScopeTabpage]); - if (!tp) { - emsg(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTabpage] < 0) { - emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - emsg(_("E5002: Cannot find window number.")); - return; - } - } - } - - switch (scope) { - case kCdScopeWindow: - assert(win); - rettv->vval.v_number = win->w_localdir ? 1 : 0; - break; - case kCdScopeTabpage: - assert(tp); - rettv->vval.v_number = tp->tp_localdir ? 1 : 0; - break; - case kCdScopeGlobal: - // The global scope never has a local directory - break; - case kCdScopeInvalid: - // We should never get here - abort(); - } -} - /// "highlightID(name)" function static void f_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -4306,12 +3682,6 @@ static void f_invert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); } -/// "isdirectory()" function -static void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0])); -} - /// "islocked()" function static void f_islocked(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -5555,78 +4925,6 @@ static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) max_min(argvars, rettv, false); } -/// "mkdir()" function -static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int prot = 0755; - - rettv->vval.v_number = FAIL; - if (check_secure()) { - return; - } - - char buf[NUMBUFLEN]; - const char *const dir = tv_get_string_buf(&argvars[0], buf); - if (*dir == NUL) { - return; - } - - if (*path_tail(dir) == NUL) { - // Remove trailing slashes. - *path_tail_with_sep((char *)dir) = NUL; - } - - bool defer = false; - bool defer_recurse = false; - char *created = NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_UNKNOWN) { - prot = (int)tv_get_number_chk(&argvars[2], NULL); - if (prot == -1) { - return; - } - } - const char *arg2 = tv_get_string(&argvars[1]); - defer = vim_strchr(arg2, 'D') != NULL; - defer_recurse = vim_strchr(arg2, 'R') != NULL; - if ((defer || defer_recurse) && !can_add_defer()) { - return; - } - - if (vim_strchr(arg2, 'p') != NULL) { - char *failed_dir; - int ret = os_mkdir_recurse(dir, prot, &failed_dir, - defer || defer_recurse ? &created : NULL); - if (ret != 0) { - semsg(_(e_mkdir), failed_dir, os_strerror(ret)); - xfree(failed_dir); - rettv->vval.v_number = FAIL; - return; - } - rettv->vval.v_number = OK; - } - } - if (rettv->vval.v_number == FAIL) { - rettv->vval.v_number = vim_mkdir_emsg(dir, prot); - } - - // Handle "D" and "R": deferred deletion of the created directory. - if (rettv->vval.v_number == OK - && created == NULL && (defer || defer_recurse)) { - created = FullName_save(dir, false); - } - if (created != NULL) { - typval_T tv[2]; - tv[0].v_type = VAR_STRING; - tv[0].v_lock = VAR_UNLOCKED; - tv[0].vval.v_string = created; - tv[1].v_type = VAR_STRING; - tv[1].v_lock = VAR_UNLOCKED; - tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d"); - add_defer("delete", 2, tv); - } -} - /// "mode()" function static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -5898,28 +5196,6 @@ static void f_or(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) | tv_get_number_chk(&argvars[1], NULL); } -/// "pathshorten()" function -static void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int trim_len = 1; - - if (argvars[1].v_type != VAR_UNKNOWN) { - trim_len = (int)tv_get_number(&argvars[1]); - if (trim_len < 1) { - trim_len = 1; - } - } - - rettv->v_type = VAR_STRING; - const char *p = tv_get_string_chk(&argvars[0]); - if (p == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = xstrdup(p); - shorten_dir_len(rettv->vval.v_string, trim_len); - } -} - /// "pow()" function static void f_pow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -6258,267 +5534,6 @@ static void f_range(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// Evaluate "expr" (= "context") for readdir(). -static varnumber_T readdir_checkitem(void *context, const char *name) - FUNC_ATTR_NONNULL_ALL -{ - typval_T *expr = (typval_T *)context; - typval_T argv[2]; - varnumber_T retval = 0; - bool error = false; - - if (expr->v_type == VAR_UNKNOWN) { - return 1; - } - - typval_T save_val; - prepare_vimvar(VV_VAL, &save_val); - set_vim_var_string(VV_VAL, name, -1); - argv[0].v_type = VAR_STRING; - argv[0].vval.v_string = (char *)name; - - typval_T rettv; - if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) { - goto theend; - } - - retval = tv_get_number_chk(&rettv, &error); - if (error) { - retval = -1; - } - - tv_clear(&rettv); - -theend: - set_vim_var_string(VV_VAL, NULL, 0); - restore_vimvar(VV_VAL, &save_val); - return retval; -} - -/// "readdir()" function -static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_list_alloc_ret(rettv, kListLenUnknown); - - const char *path = tv_get_string(&argvars[0]); - typval_T *expr = &argvars[1]; - garray_T ga; - int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); - if (ret == OK && ga.ga_len > 0) { - for (int i = 0; i < ga.ga_len; i++) { - const char *p = ((const char **)ga.ga_data)[i]; - tv_list_append_string(rettv->vval.v_list, p, -1); - } - } - ga_clear_strings(&ga); -} - -/// "readfile()" or "readblob()" function -static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob) -{ - bool binary = false; - bool blob = always_blob; - FILE *fd; - char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 - int io_size = sizeof(buf); - char *prev = NULL; // previously read bytes, if any - ptrdiff_t prevlen = 0; // length of data in prev - ptrdiff_t prevsize = 0; // size of prev buffer - int64_t maxline = MAXLNUM; - off_T offset = 0; - off_T size = -1; - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (always_blob) { - offset = (off_T)tv_get_number(&argvars[1]); - if (argvars[2].v_type != VAR_UNKNOWN) { - size = (off_T)tv_get_number(&argvars[2]); - } - } else { - if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { - binary = true; - } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) { - blob = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - maxline = tv_get_number(&argvars[2]); - } - } - } - - if (blob) { - tv_blob_alloc_ret(rettv); - } else { - tv_list_alloc_ret(rettv, kListLenUnknown); - } - - // Always open the file in binary mode, library functions have a mind of - // their own about CR-LF conversion. - const char *const fname = tv_get_string(&argvars[0]); - - if (os_isdir(fname)) { - semsg(_(e_isadir2), fname); - return; - } - if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { - semsg(_(e_notopen), *fname == NUL ? _("<empty>") : fname); - return; - } - - if (blob) { - if (read_blob(fd, rettv, offset, size) == FAIL) { - semsg(_(e_notread), fname); - } - fclose(fd); - return; - } - - list_T *const l = rettv->vval.v_list; - - while (maxline < 0 || tv_list_len(l) < maxline) { - int readlen = (int)fread(buf, 1, (size_t)io_size, fd); - - // This for loop processes what was read, but is also entered at end - // of file so that either: - // - an incomplete line gets written - // - a "binary" file gets an empty line at the end if it ends in a - // newline. - char *p; // Position in buf. - char *start; // Start of current line. - for (p = buf, start = buf; - p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); - p++) { - if (readlen <= 0 || *p == '\n') { - char *s = NULL; - size_t len = (size_t)(p - start); - - // Finished a line. Remove CRs before NL. - if (readlen > 0 && !binary) { - while (len > 0 && start[len - 1] == '\r') { - len--; - } - // removal may cross back to the "prev" string - if (len == 0) { - while (prevlen > 0 && prev[prevlen - 1] == '\r') { - prevlen--; - } - } - } - if (prevlen == 0) { - assert(len < INT_MAX); - s = xmemdupz(start, len); - } else { - // Change "prev" buffer to be the right size. This way - // the bytes are only copied once, and very long lines are - // allocated only once. - s = xrealloc(prev, (size_t)prevlen + len + 1); - memcpy(s + prevlen, start, len); - s[(size_t)prevlen + len] = NUL; - prev = NULL; // the list will own the string - prevlen = prevsize = 0; - } - - tv_list_append_owned_tv(l, (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = s, - }); - - start = p + 1; // Step over newline. - if (maxline < 0) { - if (tv_list_len(l) > -maxline) { - assert(tv_list_len(l) == 1 + (-maxline)); - tv_list_item_remove(l, tv_list_first(l)); - } - } else if (tv_list_len(l) >= maxline) { - assert(tv_list_len(l) == maxline); - break; - } - if (readlen <= 0) { - break; - } - } else if (*p == NUL) { - *p = '\n'; - // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this - // when finding the BF and check the previous two bytes. - } else if ((uint8_t)(*p) == 0xbf && !binary) { - // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, - // these may be in the "prev" string. - char back1 = p >= buf + 1 ? p[-1] - : prevlen >= 1 ? prev[prevlen - 1] : NUL; - char back2 = p >= buf + 2 ? p[-2] - : (p == buf + 1 && prevlen >= 1 - ? prev[prevlen - 1] - : prevlen >= 2 ? prev[prevlen - 2] : NUL); - - if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) { - char *dest = p - 2; - - // Usually a BOM is at the beginning of a file, and so at - // the beginning of a line; then we can just step over it. - if (start == dest) { - start = p + 1; - } else { - // have to shuffle buf to close gap - int adjust_prevlen = 0; - - if (dest < buf) { - // adjust_prevlen must be 1 or 2. - adjust_prevlen = (int)(buf - dest); - dest = buf; - } - if (readlen > p - buf + 1) { - memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1); - } - readlen -= 3 - adjust_prevlen; - prevlen -= adjust_prevlen; - p = dest - 1; - } - } - } - } // for - - if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { - break; - } - if (start < p) { - // There's part of a line in buf, store it in "prev". - if (p - start + prevlen >= prevsize) { - // A common use case is ordinary text files and "prev" gets a - // fragment of a line, so the first allocation is made - // small, to avoid repeatedly 'allocing' large and - // 'reallocing' small. - if (prevsize == 0) { - prevsize = p - start; - } else { - ptrdiff_t grow50pc = (prevsize * 3) / 2; - ptrdiff_t growmin = (p - start) * 2 + prevlen; - prevsize = grow50pc > growmin ? grow50pc : growmin; - } - prev = xrealloc(prev, (size_t)prevsize); - } - // Add the line part to end of "prev". - memmove(prev + prevlen, start, (size_t)(p - start)); - prevlen += p - start; - } - } // while - - xfree(prev); - fclose(fd); -} - -/// "readblob()" function -static void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - read_file_or_blob(argvars, rettv, true); -} - -/// "readfile()" function -static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - read_file_or_blob(argvars, rettv, false); -} - /// "getreginfo()" function static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -6701,18 +5716,6 @@ static void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "rename({from}, {to})" function -static void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - if (check_secure()) { - rettv->vval.v_number = -1; - } else { - char buf[NUMBUFLEN]; - rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]), - tv_get_string_buf(&argvars[1], buf)); - } -} - /// "repeat()" function static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -6781,175 +5784,6 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "resolve()" function -static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - const char *fname = tv_get_string(&argvars[0]); -#ifdef MSWIN - char *v = os_resolve_shortcut(fname); - if (v == NULL) { - if (os_is_reparse_point_include(fname)) { - v = os_realpath(fname, NULL, MAXPATHL + 1); - } - } - rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); -#else -# ifdef HAVE_READLINK - { - bool is_relative_to_current = false; - bool has_trailing_pathsep = false; - int limit = 100; - - char *p = xstrdup(fname); - - if (p[0] == '.' && (vim_ispathsep(p[1]) - || (p[1] == '.' && (vim_ispathsep(p[2]))))) { - is_relative_to_current = true; - } - - ptrdiff_t len = (ptrdiff_t)strlen(p); - if (len > 1 && after_pathsep(p, p + len)) { - has_trailing_pathsep = true; - p[len - 1] = NUL; // The trailing slash breaks readlink(). - } - - char *q = (char *)path_next_component(p); - char *remain = NULL; - if (*q != NUL) { - // Separate the first path component in "p", and keep the - // remainder (beginning with the path separator). - remain = xstrdup(q - 1); - q[-1] = NUL; - } - - char *const buf = xmallocz(MAXPATHL); - - char *cpy; - while (true) { - while (true) { - len = readlink(p, buf, MAXPATHL); - if (len <= 0) { - break; - } - buf[len] = NUL; - - if (limit-- == 0) { - xfree(p); - xfree(remain); - emsg(_("E655: Too many symbolic links (cycle?)")); - rettv->vval.v_string = NULL; - xfree(buf); - return; - } - - // Ensure that the result will have a trailing path separator - // if the argument has one. - if (remain == NULL && has_trailing_pathsep) { - add_pathsep(buf); - } - - // Separate the first path component in the link value and - // concatenate the remainders. - q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); - if (*q != NUL) { - cpy = remain; - remain = (remain - ? concat_str(q - 1, remain) - : xstrdup(q - 1)); - xfree(cpy); - q[-1] = NUL; - } - - q = path_tail(p); - if (q > p && *q == NUL) { - // Ignore trailing path separator. - p[q - p - 1] = NUL; - q = path_tail(p); - } - if (q > p && !path_is_absolute(buf)) { - // Symlink is relative to directory of argument. Replace the - // symlink with the resolved name in the same directory. - const size_t p_len = strlen(p); - const size_t buf_len = strlen(buf); - p = xrealloc(p, p_len + buf_len + 1); - memcpy(path_tail(p), buf, buf_len + 1); - } else { - xfree(p); - p = xstrdup(buf); - } - } - - if (remain == NULL) { - break; - } - - // Append the first path component of "remain" to "p". - q = (char *)path_next_component(remain + 1); - len = q - remain - (*q != NUL); - const size_t p_len = strlen(p); - cpy = xmallocz(p_len + (size_t)len); - memcpy(cpy, p, p_len + 1); - xstrlcat(cpy + p_len, remain, (size_t)len + 1); - xfree(p); - p = cpy; - - // Shorten "remain". - if (*q != NUL) { - STRMOVE(remain, q - 1); - } else { - XFREE_CLEAR(remain); - } - } - - // If the result is a relative path name, make it explicitly relative to - // the current directory if and only if the argument had this form. - if (!vim_ispathsep(*p)) { - if (is_relative_to_current - && *p != NUL - && !(p[0] == '.' - && (p[1] == NUL - || vim_ispathsep(p[1]) - || (p[1] == '.' - && (p[2] == NUL - || vim_ispathsep(p[2])))))) { - // Prepend "./". - cpy = concat_str("./", p); - xfree(p); - p = cpy; - } else if (!is_relative_to_current) { - // Strip leading "./". - q = p; - while (q[0] == '.' && vim_ispathsep(q[1])) { - q += 2; - } - if (q > p) { - STRMOVE(p, p + 2); - } - } - } - - // Ensure that the result will have no trailing path separator - // if the argument had none. But keep "/" or "//". - if (!has_trailing_pathsep) { - q = p + strlen(p); - if (after_pathsep(p, q)) { - *path_tail_with_sep(p) = NUL; - } - } - - rettv->vval.v_string = p; - xfree(buf); - } -# else - char *v = os_realpath(fname, NULL, MAXPATHL + 1); - rettv->vval.v_string = v == NULL ? xstrdup(fname) : v; -# endif -#endif - - simplify_filename(rettv->vval.v_string); -} - /// "reverse({list})" function static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -8504,15 +7338,6 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = get_sw_value(curbuf); } -/// "simplify()" function -static void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_string = xstrdup(p); - simplify_filename(rettv->vval.v_string); // Simplify in place. - rettv->v_type = VAR_STRING; -} - /// "sockconnect()" function static void f_sockconnect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -9298,13 +8123,6 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) (char *)tag_pattern, (char *)fname); } -/// "tempname()" function -static void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_tempname(); -} - /// "termopen(cmd[, cwd])" function static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -9650,104 +8468,6 @@ static void f_wordcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) cursor_pos_info(rettv->vval.v_dict); } -/// "writefile()" function -static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - - if (check_secure()) { - return; - } - - if (argvars[0].v_type == VAR_LIST) { - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { - return; - } - }); - } else if (argvars[0].v_type != VAR_BLOB) { - semsg(_(e_invarg2), - _("writefile() first argument must be a List or a Blob")); - return; - } - - bool binary = false; - bool append = false; - bool defer = false; - bool do_fsync = !!p_fs; - bool mkdir_p = false; - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const flags = tv_get_string_chk(&argvars[2]); - if (flags == NULL) { - return; - } - for (const char *p = flags; *p; p++) { - switch (*p) { - case 'b': - binary = true; break; - case 'a': - append = true; break; - case 'D': - defer = true; break; - case 's': - do_fsync = true; break; - case 'S': - do_fsync = false; break; - case 'p': - mkdir_p = true; break; - default: - // Using %s, p and not %c, *p to preserve multibyte characters - semsg(_("E5060: Unknown flag: %s"), p); - return; - } - } - } - - char buf[NUMBUFLEN]; - const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); - if (fname == NULL) { - return; - } - - if (defer && !can_add_defer()) { - return; - } - - FileDescriptor fp; - int error; - if (*fname == NUL) { - emsg(_("E482: Can't open file with an empty name")); - } else if ((error = file_open(&fp, fname, - ((append ? kFileAppend : kFileTruncate) - | (mkdir_p ? kFileMkDir : kFileCreate) - | kFileCreate), 0666)) != 0) { - semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); - } else { - if (defer) { - typval_T tv = { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = FullName_save(fname, false), - }; - add_defer("delete", 1, &tv); - } - - bool write_ok; - if (argvars[0].v_type == VAR_BLOB) { - write_ok = write_blob(&fp, argvars[0].vval.v_blob); - } else { - write_ok = write_list(&fp, argvars[0].vval.v_list, binary); - } - if (write_ok) { - rettv->vval.v_number = 0; - } - if ((error = file_close(&fp, do_fsync)) != 0) { - semsg(_("E80: Error when closing file %s: %s"), - fname, os_strerror(error)); - } - } -} - /// "xor(expr, expr)" function static void f_xor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { |