diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2020-02-02 23:13:27 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-02 23:13:27 -0800 |
commit | ead61e5a52abcdff60d3b534bd32f43c61c0221a (patch) | |
tree | 484ecae3687ff9a1b3f02300228476fa53e72464 | |
parent | 983086f42e5841b84d5b24623a51ffc8c725e78f (diff) | |
parent | 2af04e199740d5d2eaab38e272ecf1f9146335f5 (diff) | |
download | rneovim-ead61e5a52abcdff60d3b534bd32f43c61c0221a.tar.gz rneovim-ead61e5a52abcdff60d3b534bd32f43c61c0221a.tar.bz2 rneovim-ead61e5a52abcdff60d3b534bd32f43c61c0221a.zip |
Merge #11807 'refactor: eliminate os_unix.c (almost)'
-rw-r--r-- | src/nvim/main.c | 145 | ||||
-rw-r--r-- | src/nvim/msgpack_rpc/channel.c | 2 | ||||
-rw-r--r-- | src/nvim/os/shell.c | 499 | ||||
-rw-r--r-- | src/nvim/os_unix.c | 531 | ||||
-rw-r--r-- | src/nvim/path.c | 8 |
5 files changed, 583 insertions, 602 deletions
diff --git a/src/nvim/main.c b/src/nvim/main.c index a816221a9e..56d9030a7f 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -207,7 +207,7 @@ void early_init(void) // Allocate the first window and buffer. // Can't do anything without it, exit when it fails. if (!win_alloc_first()) { - mch_exit(0); + os_exit(0); } init_yank(); // init yank buffers @@ -293,8 +293,8 @@ int main(int argc, char **argv) if (params.diff_mode && params.window_count == -1) params.window_count = 0; /* open up to 3 windows */ - /* Don't redraw until much later. */ - ++RedrawingDisabled; + // Don't redraw until much later. + RedrawingDisabled++; setbuf(stdout, NULL); @@ -384,23 +384,17 @@ int main(int argc, char **argv) syn_maybe_on(); } - /* - * Read all the plugin files. - * Only when compiled with +eval, since most plugins need it. - */ + // Read all the plugin files. load_plugins(); // Decide about window layout for diff mode after reading vimrc. set_window_layout(¶ms); - /* - * Recovery mode without a file name: List swap files. - * This uses the 'dir' option, therefore it must be after the - * initializations. - */ + // Recovery mode without a file name: List swap files. + // Uses the 'dir' option, therefore it must be after the initializations. if (recoverymode && fname == NULL) { - recover_names(NULL, TRUE, 0, NULL); - mch_exit(0); + recover_names(NULL, true, 0, NULL); + os_exit(0); } // Set some option defaults after reading vimrc files. @@ -431,17 +425,15 @@ int main(int argc, char **argv) set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } - /* - * "-q errorfile": Load the error file now. - * If the error file can't be read, exit before doing anything else. - */ + // "-q errorfile": Load the error file now. + // If the error file can't be read, exit before doing anything else. handle_quickfix(¶ms); - /* - * Start putting things on the screen. - * Scroll screen down before drawing over it - * Clear screen now, so file message will not be cleared. - */ + // + // Start putting things on the screen. + // Scroll screen down before drawing over it + // Clear screen now, so file message will not be cleared. + // starting = NO_BUFFERS; no_wait_return = false; if (!exmode_active) { @@ -473,27 +465,26 @@ int main(int argc, char **argv) no_wait_return = true; - /* - * Create the requested number of windows and edit buffers in them. - * Also does recovery if "recoverymode" set. - */ + // + // Create the requested number of windows and edit buffers in them. + // Also does recovery if "recoverymode" set. + // create_windows(¶ms); TIME_MSG("opening buffers"); - /* clear v:swapcommand */ + // Clear v:swapcommand set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); - /* Ex starts at last line of the file */ - if (exmode_active) + // Ex starts at last line of the file. + if (exmode_active) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); TIME_MSG("BufEnter autocommands"); setpcmark(); - /* - * When started with "-q errorfile" jump to first error now. - */ + // When started with "-q errorfile" jump to first error now. if (params.edit_type == EDIT_QF) { qf_jump(NULL, 0, 0, FALSE); TIME_MSG("jump to first error"); @@ -505,26 +496,23 @@ int main(int argc, char **argv) xfree(cwd); if (params.diff_mode) { - /* set options in each window for "nvim -d". */ + // set options in each window for "nvim -d". FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { diff_win_options(wp, TRUE); } } - /* - * Shorten any of the filenames, but only when absolute. - */ - shorten_fnames(FALSE); + // Shorten any of the filenames, but only when absolute. + shorten_fnames(false); - /* - * Need to jump to the tag before executing the '-c command'. - * Makes "vim -c '/return' -t main" work. - */ + // Need to jump to the tag before executing the '-c command'. + // Makes "vim -c '/return' -t main" work. handle_tag(params.tagname); - /* Execute any "+", "-c" and "-S" arguments. */ - if (params.n_commands > 0) + // Execute any "+", "-c" and "-S" arguments. + if (params.n_commands > 0) { exe_commands(¶ms); + } starting = 0; @@ -535,9 +523,10 @@ int main(int argc, char **argv) // 'autochdir' has been postponed. do_autochdir(); - /* start in insert mode */ - if (p_im) - need_start_insertmode = TRUE; + // start in insert mode + if (p_im) { + need_start_insertmode = true; + } set_vim_var_nr(VV_VIM_DID_ENTER, 1L); apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf); @@ -553,18 +542,19 @@ int main(int argc, char **argv) // main loop. set_reg_var(get_default_register_name()); - /* When a startup script or session file setup for diff'ing and - * scrollbind, sync the scrollbind now. */ + // When a startup script or session file setup for diff'ing and + // scrollbind, sync the scrollbind now. if (curwin->w_p_diff && curwin->w_p_scb) { update_topline(); check_scrollbind((linenr_T)0, 0L); TIME_MSG("diff scrollbinding"); } - /* If ":startinsert" command used, stuff a dummy command to be able to - * call normal_cmd(), which will then start Insert mode. */ - if (restart_edit != 0) + // If ":startinsert" command used, stuff a dummy command to be able to + // call normal_cmd(), which will then start Insert mode. + if (restart_edit != 0) { stuffcharReadbuff(K_NOP); + } // WORKAROUND(mhi): #3023 if (cb_flags & CB_UNNAMEDMASK) { @@ -574,9 +564,7 @@ int main(int argc, char **argv) TIME_MSG("before starting main loop"); ILOG("starting main loop"); - /* - * Call the main command loop. This never returns. - */ + // Main loop: never returns. normal_enter(false, false); #if defined(WIN32) && !defined(MAKE_LIB) @@ -585,6 +573,31 @@ int main(int argc, char **argv) return 0; } +void os_exit(int r) + FUNC_ATTR_NORETURN +{ + exiting = true; + + ui_flush(); + ui_call_stop(); + ml_close_all(true); // remove all memfiles + + if (!event_teardown() && r == 0) { + r = 1; // Exit with error if main_loop did not teardown gracefully. + } + if (input_global_fd() >= 0) { + stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + } + + ILOG("Nvim exit: %d", r); + +#ifdef EXITFREE + free_all_mem(); +#endif + + exit(r); +} + /// Exit properly void getout(int exitval) FUNC_ATTR_NORETURN @@ -679,7 +692,7 @@ void getout(int exitval) garbage_collect(false); } - mch_exit(exitval); + os_exit(exitval); } /// Gets the integer value of a numeric command line argument if given, @@ -799,10 +812,10 @@ static void command_line_scan(mparm_T *parmp) // "--cmd <cmd>" execute cmd before vimrc if (STRICMP(argv[0] + argv_idx, "help") == 0) { usage(); - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "version") == 0) { version(); - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { FileDescriptor fp; const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, @@ -825,7 +838,7 @@ static void command_line_scan(mparm_T *parmp) if (ff_ret < 0) { msgpack_file_write_error(ff_ret); } - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "headless") == 0) { headless_mode = true; } else if (STRICMP(argv[0] + argv_idx, "embed") == 0) { @@ -891,7 +904,7 @@ static void command_line_scan(mparm_T *parmp) case '?': // "-?" give help message (for MS-Windows) case 'h': { // "-h" give help message usage(); - mch_exit(0); + os_exit(0); } case 'H': { // "-H" start in Hebrew mode: rl + hkmap set. p_hkmap = true; @@ -988,7 +1001,7 @@ static void command_line_scan(mparm_T *parmp) } case 'v': { version(); - mch_exit(0); + os_exit(0); } case 'V': { // "-V{N}" Verbose level // default is 10: a little bit verbose @@ -1116,7 +1129,7 @@ scripterror: _("Attempt to open script file again: \"%s %s\"\n"), argv[-1], argv[0]); mch_errmsg((const char *)IObuff); - mch_exit(2); + os_exit(2); } int error; if (strequal(argv[0], "-")) { @@ -1135,7 +1148,7 @@ scripterror: _("Cannot open for reading: \"%s\": %s\n"), argv[0], os_strerror(error)); mch_errmsg((const char *)IObuff); - mch_exit(2); + os_exit(2); } save_typebuf(); break; @@ -1173,7 +1186,7 @@ scripterror: mch_errmsg(_("Cannot open for script output: \"")); mch_errmsg(argv[0]); mch_errmsg("\"\n"); - mch_exit(2); + os_exit(2); } break; } @@ -1380,7 +1393,7 @@ static void handle_quickfix(mparm_T *paramp) vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { msg_putchar('\n'); - mch_exit(3); + os_exit(3); } TIME_MSG("reading errorfile"); } @@ -1943,7 +1956,7 @@ static void mainerr(const char *errstr, const char *str) mch_errmsg(prgname); mch_errmsg(" -h\"\n"); - mch_exit(1); + os_exit(1); } /// Prints version information for "nvim -v" or "nvim --version". diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 0c874d7922..92ca29209e 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -564,7 +564,7 @@ void rpc_close(Channel *channel) static void exit_event(void **argv) { if (!exiting) { - mch_exit(0); + os_exit(0); } } diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index d1de18d5b3..3b8470182a 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -9,13 +9,17 @@ #include <uv.h> #include "nvim/ascii.h" +#include "nvim/fileio.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/event/loop.h" #include "nvim/event/libuv_process.h" #include "nvim/event/rstream.h" +#include "nvim/ex_cmds.h" +#include "nvim/misc1.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" +#include "nvim/path.h" #include "nvim/types.h" #include "nvim/main.h" #include "nvim/vim.h" @@ -32,6 +36,8 @@ #define NS_1_SECOND 1000000000U // 1 second, in nanoseconds #define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data. +#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|" + typedef struct { char *data; size_t cap, len; @@ -41,6 +47,499 @@ typedef struct { # include "os/shell.c.generated.h" #endif +static void save_patterns(int num_pat, char_u **pat, int *num_file, + char_u ***file) +{ + *file = xmalloc((size_t)num_pat * sizeof(char_u *)); + for (int i = 0; i < num_pat; i++) { + char_u *s = vim_strsave(pat[i]); + // Be compatible with expand_filename(): halve the number of + // backslashes. + backslash_halve(s); + (*file)[i] = s; + } + *num_file = num_pat; +} + +static bool have_wildcard(int num, char_u **file) +{ + for (int i = 0; i < num; i++) { + if (path_has_wildcard(file[i])) { + return true; + } + } + return false; +} + +static bool have_dollars(int num, char_u **file) +{ + for (int i = 0; i < num; i++) { + if (vim_strchr(file[i], '$') != NULL) { + return true; + } + } + return false; +} + +/// Performs wildcard pattern matching using the shell. +/// +/// @param num_pat is the number of input patterns. +/// @param pat is an array of pointers to input patterns. +/// @param[out] num_file is pointer to number of matched file names. +/// Set to the number of pointers in *file. +/// @param[out] file is pointer to array of pointers to matched file names. +/// Memory pointed to by the initial value of *file will +/// not be freed. +/// Set to NULL if FAIL is returned. Otherwise points to +/// allocated memory. +/// @param flags is a combination of EW_* flags used in +/// expand_wildcards(). +/// If matching fails but EW_NOTFOUND is set in flags or +/// there are no wildcards, the patterns from pat are +/// copied into *file. +/// +/// @returns OK for success or FAIL for error. +int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, + char_u ***file, int flags) + FUNC_ATTR_NONNULL_ARG(3) + FUNC_ATTR_NONNULL_ARG(4) +{ + int i; + size_t len; + char_u *p; + bool dir; + char_u *extra_shell_arg = NULL; + ShellOpts shellopts = kShellOptExpand | kShellOptSilent; + int j; + char_u *tempname; + char_u *command; + FILE *fd; + char_u *buffer; +#define STYLE_ECHO 0 // use "echo", the default +#define STYLE_GLOB 1 // use "glob", for csh +#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh +#define STYLE_PRINT 3 // use "print -N", for zsh +#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly + int shell_style = STYLE_ECHO; + int check_spaces; + static bool did_find_nul = false; + bool ampersent = false; + // vimglob() function to define for Posix shell + static char *sh_vimglob_func = + "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; + + bool is_fish_shell = +#if defined(UNIX) + STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; +#else + false; +#endif + + *num_file = 0; // default: no files found + *file = NULL; + + // If there are no wildcards, just copy the names to allocated memory. + // Saves a lot of time, because we don't have to start a new shell. + if (!have_wildcard(num_pat, pat)) { + save_patterns(num_pat, pat, num_file, file); + return OK; + } + + // Don't allow any shell command in the sandbox. + if (sandbox != 0 && check_secure()) { + return FAIL; + } + + // Don't allow the use of backticks in secure and restricted mode. + if (secure || restricted) { + for (i = 0; i < num_pat; i++) { + if (vim_strchr(pat[i], '`') != NULL + && (check_restricted() || check_secure())) { + return FAIL; + } + } + } + + // get a name for the temp file + if ((tempname = vim_tempname()) == NULL) { + EMSG(_(e_notmp)); + return FAIL; + } + + // Let the shell expand the patterns and write the result into the temp + // file. + // STYLE_BT: NL separated + // If expanding `cmd` execute it directly. + // STYLE_GLOB: NUL separated + // If we use *csh, "glob" will work better than "echo". + // STYLE_PRINT: NL or NUL separated + // If we use *zsh, "print -N" will work better than "glob". + // STYLE_VIMGLOB: NL separated + // If we use *sh*, we define "vimglob()". + // STYLE_ECHO: space separated. + // A shell we don't know, stay safe and use "echo". + if (num_pat == 1 && *pat[0] == '`' + && (len = STRLEN(pat[0])) > 2 + && *(pat[0] + len - 1) == '`') { + shell_style = STYLE_BT; + } else if ((len = STRLEN(p_sh)) >= 3) { + if (STRCMP(p_sh + len - 3, "csh") == 0) { + shell_style = STYLE_GLOB; + } else if (STRCMP(p_sh + len - 3, "zsh") == 0) { + shell_style = STYLE_PRINT; + } + } + if (shell_style == STYLE_ECHO + && strstr((char *)path_tail(p_sh), "sh") != NULL) { + shell_style = STYLE_VIMGLOB; + } + + // Compute the length of the command. We need 2 extra bytes: for the + // optional '&' and for the NUL. + // Worst case: "unset nonomatch; print -N >" plus two is 29 + len = STRLEN(tempname) + 29; + if (shell_style == STYLE_VIMGLOB) { + len += STRLEN(sh_vimglob_func); + } + + for (i = 0; i < num_pat; i++) { + // Count the length of the patterns in the same way as they are put in + // "command" below. + len++; // add space + for (j = 0; pat[i][j] != NUL; j++) { + if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + len++; // may add a backslash + } + len++; + } + } + + if (is_fish_shell) { + len += sizeof("egin;"" end") - 1; + } + + command = xmalloc(len); + + // Build the shell command: + // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell + // recognizes this). + // - Add the shell command to print the expanded names. + // - Add the temp file name. + // - Add the file name patterns. + if (shell_style == STYLE_BT) { + // change `command; command& ` to (command; command ) + if (is_fish_shell) { + STRCPY(command, "begin; "); + } else { + STRCPY(command, "("); + } + STRCAT(command, pat[0] + 1); // exclude first backtick + p = command + STRLEN(command) - 1; + if (is_fish_shell) { + *p-- = ';'; + STRCAT(command, " end"); + } else { + *p-- = ')'; // remove last backtick + } + while (p > command && ascii_iswhite(*p)) { + p--; + } + if (*p == '&') { // remove trailing '&' + ampersent = true; + *p = ' '; + } + STRCAT(command, ">"); + } else { + if (flags & EW_NOTFOUND) { + STRCPY(command, "set nonomatch; "); + } else { + STRCPY(command, "unset nonomatch; "); + } + if (shell_style == STYLE_GLOB) { + STRCAT(command, "glob >"); + } else if (shell_style == STYLE_PRINT) { + STRCAT(command, "print -N >"); + } else if (shell_style == STYLE_VIMGLOB) { + STRCAT(command, sh_vimglob_func); + } else { + STRCAT(command, "echo >"); + } + } + + STRCAT(command, tempname); + + if (shell_style != STYLE_BT) { + for (i = 0; i < num_pat; i++) { + // Put a backslash before special + // characters, except inside ``. + bool intick = false; + + p = command + STRLEN(command); + *p++ = ' '; + for (j = 0; pat[i][j] != NUL; j++) { + if (pat[i][j] == '`') { + intick = !intick; + } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) { + // Remove a backslash, take char literally. But keep + // backslash inside backticks, before a special character + // and before a backtick. + if (intick + || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL + || pat[i][j + 1] == '`') { + *p++ = '\\'; + } + j++; + } else if (!intick + && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') + && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + // Put a backslash before a special character, but not + // when inside ``. And not for $var when EW_KEEPDOLLAR is + // set. + *p++ = '\\'; + } + + // Copy one character. + *p++ = pat[i][j]; + } + *p = NUL; + } + } + + if (flags & EW_SILENT) { + shellopts |= kShellOptHideMess; + } + + if (ampersent) { + STRCAT(command, "&"); // put the '&' after the redirection + } + + // Using zsh -G: If a pattern has no matches, it is just deleted from + // the argument list, otherwise zsh gives an error message and doesn't + // expand any other pattern. + if (shell_style == STYLE_PRINT) { + extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option + + // If we use -f then shell variables set in .cshrc won't get expanded. + // vi can do it, so we will too, but it is only necessary if there is a "$" + // in one of the patterns, otherwise we can still use the fast option. + } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { + extra_shell_arg = (char_u *)"-f"; // Use csh fast option + } + + // execute the shell command + i = call_shell(command, shellopts, extra_shell_arg); + + // When running in the background, give it some time to create the temp + // file, but don't wait for it to finish. + if (ampersent) { + os_delay(10L, true); + } + + xfree(command); + + if (i) { // os_call_shell() failed + os_remove((char *)tempname); + xfree(tempname); + // With interactive completion, the error message is not printed. + if (!(flags & EW_SILENT)) { + msg_putchar('\n'); // clear bottom line quickly + cmdline_row = Rows - 1; // continue on last line + MSG(_(e_wildexpand)); + msg_start(); // don't overwrite this message + } + + // If a `cmd` expansion failed, don't list `cmd` as a match, even when + // EW_NOTFOUND is given + if (shell_style == STYLE_BT) { + return FAIL; + } + goto notfound; + } + + // read the names from the file into memory + fd = fopen((char *)tempname, READBIN); + if (fd == NULL) { + // Something went wrong, perhaps a file name with a special char. + if (!(flags & EW_SILENT)) { + MSG(_(e_wildexpand)); + msg_start(); // don't overwrite this message + } + xfree(tempname); + goto notfound; + } + int fseek_res = fseek(fd, 0L, SEEK_END); + if (fseek_res < 0) { + xfree(tempname); + fclose(fd); + return FAIL; + } + int64_t templen = ftell(fd); // get size of temp file + if (templen < 0) { + xfree(tempname); + fclose(fd); + return FAIL; + } +#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T + assert(templen <= (long long)SIZE_MAX); // NOLINT(runtime/int) +#endif + len = (size_t)templen; + fseek(fd, 0L, SEEK_SET); + buffer = xmalloc(len + 1); + // fread() doesn't terminate buffer with NUL; + // appropriate termination (not always NUL) is done below. + size_t readlen = fread((char *)buffer, 1, len, fd); + fclose(fd); + os_remove((char *)tempname); + if (readlen != len) { + // unexpected read error + EMSG2(_(e_notread), tempname); + xfree(tempname); + xfree(buffer); + return FAIL; + } + xfree(tempname); + + // file names are separated with Space + if (shell_style == STYLE_ECHO) { + buffer[len] = '\n'; // make sure the buffer ends in NL + p = buffer; + for (i = 0; *p != '\n'; i++) { // count number of entries + while (*p != ' ' && *p != '\n') { + p++; + } + p = skipwhite(p); // skip to next entry + } + // file names are separated with NL + } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { + buffer[len] = NUL; // make sure the buffer ends in NUL + p = buffer; + for (i = 0; *p != NUL; i++) { // count number of entries + while (*p != '\n' && *p != NUL) { + p++; + } + if (*p != NUL) { + p++; + } + p = skipwhite(p); // skip leading white space + } + // file names are separated with NUL + } else { + // Some versions of zsh use spaces instead of NULs to separate + // results. Only do this when there is no NUL before the end of the + // buffer, otherwise we would never be able to use file names with + // embedded spaces when zsh does use NULs. + // When we found a NUL once, we know zsh is OK, set did_find_nul and + // don't check for spaces again. + check_spaces = false; + if (shell_style == STYLE_PRINT && !did_find_nul) { + // If there is a NUL, set did_find_nul, else set check_spaces + buffer[len] = NUL; + if (len && (int)STRLEN(buffer) < (int)len) { + did_find_nul = true; + } else { + check_spaces = true; + } + } + + // Make sure the buffer ends with a NUL. For STYLE_PRINT there + // already is one, for STYLE_GLOB it needs to be added. + if (len && buffer[len - 1] == NUL) { + len--; + } else { + buffer[len] = NUL; + } + i = 0; + for (p = buffer; p < buffer + len; p++) { + if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry + i++; + *p = NUL; + } + } + if (len) { + i++; // count last entry + } + } + assert(buffer[len] == NUL || buffer[len] == '\n'); + + if (i == 0) { + // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". + // /bin/sh will happily expand it to nothing rather than returning an + // error; and hey, it's good to check anyway -- webb. + xfree(buffer); + goto notfound; + } + *num_file = i; + *file = xmalloc(sizeof(char_u *) * (size_t)i); + + // Isolate the individual file names. + p = buffer; + for (i = 0; i < *num_file; i++) { + (*file)[i] = p; + // Space or NL separates + if (shell_style == STYLE_ECHO || shell_style == STYLE_BT + || shell_style == STYLE_VIMGLOB) { + while (!(shell_style == STYLE_ECHO && *p == ' ') + && *p != '\n' && *p != NUL) { + p++; + } + if (p == buffer + len) { // last entry + *p = NUL; + } else { + *p++ = NUL; + p = skipwhite(p); // skip to next entry + } + } else { // NUL separates + while (*p && p < buffer + len) { // skip entry + p++; + } + p++; // skip NUL + } + } + + // Move the file names to allocated memory. + for (j = 0, i = 0; i < *num_file; i++) { + // Require the files to exist. Helps when using /bin/sh + if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) { + continue; + } + + // check if this entry should be included + dir = (os_isdir((*file)[i])); + if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) { + continue; + } + + // Skip files that are not executable if we check for that. + if (!dir && (flags & EW_EXEC) + && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) { + continue; + } + + p = xmalloc(STRLEN((*file)[i]) + 1 + dir); + STRCPY(p, (*file)[i]); + if (dir) { + add_pathsep((char *)p); // add '/' to a directory name + } + (*file)[j++] = p; + } + xfree(buffer); + *num_file = j; + + if (*num_file == 0) { // rejected all entries + XFREE_CLEAR(*file); + goto notfound; + } + + return OK; + +notfound: + if (flags & EW_NOTFOUND) { + save_patterns(num_pat, pat, num_file, file); + return OK; + } + return FAIL; +} + /// Builds the argument vector for running the user-configured 'shell' (p_sh) /// with an optional command prefixed by 'shellcmdflag' (p_shcf). E.g.: /// diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 0f44df2188..be4bd9709b 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -1,13 +1,6 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * os_unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...) - * - * A lot of this file was originally written by Juergen Weigert and later - * changed beyond recognition. - */ - #include <assert.h> #include <errno.h> #include <inttypes.h> @@ -79,527 +72,3 @@ void mch_free_acl(vim_acl_T aclent) return; } #endif - -void mch_exit(int r) - FUNC_ATTR_NORETURN -{ - exiting = true; - - ui_flush(); - ui_call_stop(); - ml_close_all(true); // remove all memfiles - - if (!event_teardown() && r == 0) { - r = 1; // Exit with error if main_loop did not teardown gracefully. - } - if (input_global_fd() >= 0) { - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) - } - - ILOG("Nvim exit: %d", r); - -#ifdef EXITFREE - free_all_mem(); -#endif - - exit(r); -} - -#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|" - -/// Does wildcard pattern matching using the shell. -/// -/// @param num_pat is the number of input patterns. -/// @param pat is an array of pointers to input patterns. -/// @param[out] num_file is pointer to number of matched file names. -/// Set to the number of pointers in *file. -/// @param[out] file is pointer to array of pointers to matched file names. -/// Memory pointed to by the initial value of *file will -/// not be freed. -/// Set to NULL if FAIL is returned. Otherwise points to -/// allocated memory. -/// @param flags is a combination of EW_* flags used in -/// expand_wildcards(). -/// If matching fails but EW_NOTFOUND is set in flags or -/// there are no wildcards, the patterns from pat are -/// copied into *file. -/// -/// @returns OK for success or FAIL for error. -int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, - char_u ***file, int flags) FUNC_ATTR_NONNULL_ARG(3) - FUNC_ATTR_NONNULL_ARG(4) -{ - int i; - size_t len; - char_u *p; - bool dir; - char_u *extra_shell_arg = NULL; - ShellOpts shellopts = kShellOptExpand | kShellOptSilent; - int j; - char_u *tempname; - char_u *command; - FILE *fd; - char_u *buffer; -#define STYLE_ECHO 0 /* use "echo", the default */ -#define STYLE_GLOB 1 /* use "glob", for csh */ -#define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */ -#define STYLE_PRINT 3 /* use "print -N", for zsh */ -#define STYLE_BT 4 /* `cmd` expansion, execute the pattern - * directly */ - int shell_style = STYLE_ECHO; - int check_spaces; - static bool did_find_nul = false; - bool ampersent = false; - // vimglob() function to define for Posix shell - static char *sh_vimglob_func = - "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; - - bool is_fish_shell = -#if defined(UNIX) - STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; -#else - false; -#endif - - *num_file = 0; // default: no files found - *file = NULL; - - // If there are no wildcards, just copy the names to allocated memory. - // Saves a lot of time, because we don't have to start a new shell. - if (!have_wildcard(num_pat, pat)) { - save_patterns(num_pat, pat, num_file, file); - return OK; - } - - // Don't allow any shell command in the sandbox. - if (sandbox != 0 && check_secure()) { - return FAIL; - } - - // Don't allow the use of backticks in secure and restricted mode. - if (secure || restricted) { - for (i = 0; i < num_pat; i++) { - if (vim_strchr(pat[i], '`') != NULL - && (check_restricted() || check_secure())) { - return FAIL; - } - } - } - - // get a name for the temp file - if ((tempname = vim_tempname()) == NULL) { - EMSG(_(e_notmp)); - return FAIL; - } - - // Let the shell expand the patterns and write the result into the temp - // file. - // STYLE_BT: NL separated - // If expanding `cmd` execute it directly. - // STYLE_GLOB: NUL separated - // If we use *csh, "glob" will work better than "echo". - // STYLE_PRINT: NL or NUL separated - // If we use *zsh, "print -N" will work better than "glob". - // STYLE_VIMGLOB: NL separated - // If we use *sh*, we define "vimglob()". - // STYLE_ECHO: space separated. - // A shell we don't know, stay safe and use "echo". - if (num_pat == 1 && *pat[0] == '`' - && (len = STRLEN(pat[0])) > 2 - && *(pat[0] + len - 1) == '`') { - shell_style = STYLE_BT; - } else if ((len = STRLEN(p_sh)) >= 3) { - if (STRCMP(p_sh + len - 3, "csh") == 0) { - shell_style = STYLE_GLOB; - } else if (STRCMP(p_sh + len - 3, "zsh") == 0) { - shell_style = STYLE_PRINT; - } - } - if (shell_style == STYLE_ECHO && strstr((char *)path_tail(p_sh), - "sh") != NULL) - shell_style = STYLE_VIMGLOB; - - // Compute the length of the command. We need 2 extra bytes: for the - // optional '&' and for the NUL. - // Worst case: "unset nonomatch; print -N >" plus two is 29 - len = STRLEN(tempname) + 29; - if (shell_style == STYLE_VIMGLOB) - len += STRLEN(sh_vimglob_func); - - for (i = 0; i < num_pat; i++) { - // Count the length of the patterns in the same way as they are put in - // "command" below. - len++; // add space - for (j = 0; pat[i][j] != NUL; j++) { - if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { - len++; // may add a backslash - } - len++; - } - } - - if (is_fish_shell) { - len += sizeof("egin;"" end") - 1; - } - - command = xmalloc(len); - - // Build the shell command: - // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell - // recognizes this). - // - Add the shell command to print the expanded names. - // - Add the temp file name. - // - Add the file name patterns. - if (shell_style == STYLE_BT) { - // change `command; command& ` to (command; command ) - if (is_fish_shell) { - STRCPY(command, "begin; "); - } else { - STRCPY(command, "("); - } - STRCAT(command, pat[0] + 1); // exclude first backtick - p = command + STRLEN(command) - 1; - if (is_fish_shell) { - *p-- = ';'; - STRCAT(command, " end"); - } else { - *p-- = ')'; // remove last backtick - } - while (p > command && ascii_iswhite(*p)) { - p--; - } - if (*p == '&') { // remove trailing '&' - ampersent = true; - *p = ' '; - } - STRCAT(command, ">"); - } else { - if (flags & EW_NOTFOUND) - STRCPY(command, "set nonomatch; "); - else - STRCPY(command, "unset nonomatch; "); - if (shell_style == STYLE_GLOB) - STRCAT(command, "glob >"); - else if (shell_style == STYLE_PRINT) - STRCAT(command, "print -N >"); - else if (shell_style == STYLE_VIMGLOB) - STRCAT(command, sh_vimglob_func); - else - STRCAT(command, "echo >"); - } - - STRCAT(command, tempname); - - if (shell_style != STYLE_BT) { - for (i = 0; i < num_pat; i++) { - // Put a backslash before special - // characters, except inside ``. - bool intick = false; - - p = command + STRLEN(command); - *p++ = ' '; - for (j = 0; pat[i][j] != NUL; j++) { - if (pat[i][j] == '`') { - intick = !intick; - } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) { - // Remove a backslash, take char literally. But keep - // backslash inside backticks, before a special character - // and before a backtick. - if (intick - || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL - || pat[i][j + 1] == '`') { - *p++ = '\\'; - } - j++; - } else if (!intick - && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') - && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { - // Put a backslash before a special character, but not - // when inside ``. And not for $var when EW_KEEPDOLLAR is - // set. - *p++ = '\\'; - } - - // Copy one character. - *p++ = pat[i][j]; - } - *p = NUL; - } - } - - if (flags & EW_SILENT) { - shellopts |= kShellOptHideMess; - } - - if (ampersent) { - STRCAT(command, "&"); // put the '&' after the redirection - } - - // Using zsh -G: If a pattern has no matches, it is just deleted from - // the argument list, otherwise zsh gives an error message and doesn't - // expand any other pattern. - if (shell_style == STYLE_PRINT) { - extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option - - // If we use -f then shell variables set in .cshrc won't get expanded. - // vi can do it, so we will too, but it is only necessary if there is a "$" - // in one of the patterns, otherwise we can still use the fast option. - } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { - extra_shell_arg = (char_u *)"-f"; // Use csh fast option - } - - // execute the shell command - i = call_shell( - command, - shellopts, - extra_shell_arg - ); - - // When running in the background, give it some time to create the temp - // file, but don't wait for it to finish. - if (ampersent) { - os_delay(10L, true); - } - - xfree(command); - - if (i) { // os_call_shell() failed - os_remove((char *)tempname); - xfree(tempname); - // With interactive completion, the error message is not printed. - if (!(flags & EW_SILENT)) { - msg_putchar('\n'); // clear bottom line quickly - cmdline_row = Rows - 1; // continue on last line - MSG(_(e_wildexpand)); - msg_start(); // don't overwrite this message - } - - // If a `cmd` expansion failed, don't list `cmd` as a match, even when - // EW_NOTFOUND is given - if (shell_style == STYLE_BT) { - return FAIL; - } - goto notfound; - } - - // read the names from the file into memory - fd = fopen((char *)tempname, READBIN); - if (fd == NULL) { - // Something went wrong, perhaps a file name with a special char. - if (!(flags & EW_SILENT)) { - MSG(_(e_wildexpand)); - msg_start(); // don't overwrite this message - } - xfree(tempname); - goto notfound; - } - int fseek_res = fseek(fd, 0L, SEEK_END); - if (fseek_res < 0) { - xfree(tempname); - fclose(fd); - return FAIL; - } - int64_t templen = ftell(fd); // get size of temp file - if (templen < 0) { - xfree(tempname); - fclose(fd); - return FAIL; - } -#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T - assert(templen <= (long long)SIZE_MAX); -#endif - len = (size_t)templen; - fseek(fd, 0L, SEEK_SET); - buffer = xmalloc(len + 1); - // fread() doesn't terminate buffer with NUL; - // appropriate termination (not always NUL) is done below. - size_t readlen = fread((char *)buffer, 1, len, fd); - fclose(fd); - os_remove((char *)tempname); - if (readlen != len) { - // unexpected read error - EMSG2(_(e_notread), tempname); - xfree(tempname); - xfree(buffer); - return FAIL; - } - xfree(tempname); - - // file names are separated with Space - if (shell_style == STYLE_ECHO) { - buffer[len] = '\n'; // make sure the buffer ends in NL - p = buffer; - for (i = 0; *p != '\n'; i++) { // count number of entries - while (*p != ' ' && *p != '\n') { - p++; - } - p = skipwhite(p); // skip to next entry - } - // file names are separated with NL - } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { - buffer[len] = NUL; // make sure the buffer ends in NUL - p = buffer; - for (i = 0; *p != NUL; i++) { // count number of entries - while (*p != '\n' && *p != NUL) { - p++; - } - if (*p != NUL) { - p++; - } - p = skipwhite(p); // skip leading white space - } - // file names are separated with NUL - } else { - // Some versions of zsh use spaces instead of NULs to separate - // results. Only do this when there is no NUL before the end of the - // buffer, otherwise we would never be able to use file names with - // embedded spaces when zsh does use NULs. - // When we found a NUL once, we know zsh is OK, set did_find_nul and - // don't check for spaces again. - check_spaces = false; - if (shell_style == STYLE_PRINT && !did_find_nul) { - // If there is a NUL, set did_find_nul, else set check_spaces - buffer[len] = NUL; - if (len && (int)STRLEN(buffer) < (int)len) - did_find_nul = true; - else - check_spaces = true; - } - - // Make sure the buffer ends with a NUL. For STYLE_PRINT there - // already is one, for STYLE_GLOB it needs to be added. - if (len && buffer[len - 1] == NUL) { - len--; - } else { - buffer[len] = NUL; - } - i = 0; - for (p = buffer; p < buffer + len; p++) { - if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry - i++; - *p = NUL; - } - } - if (len) { - i++; // count last entry - } - } - assert(buffer[len] == NUL || buffer[len] == '\n'); - - if (i == 0) { - // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". - // /bin/sh will happily expand it to nothing rather than returning an - // error; and hey, it's good to check anyway -- webb. - xfree(buffer); - goto notfound; - } - *num_file = i; - *file = xmalloc(sizeof(char_u *) * (size_t)i); - - // Isolate the individual file names. - p = buffer; - for (i = 0; i < *num_file; ++i) { - (*file)[i] = p; - // Space or NL separates - if (shell_style == STYLE_ECHO || shell_style == STYLE_BT - || shell_style == STYLE_VIMGLOB) { - while (!(shell_style == STYLE_ECHO && *p == ' ') - && *p != '\n' && *p != NUL) { - p++; - } - if (p == buffer + len) { // last entry - *p = NUL; - } else { - *p++ = NUL; - p = skipwhite(p); // skip to next entry - } - } else { // NUL separates - while (*p && p < buffer + len) { // skip entry - p++; - } - p++; // skip NUL - } - } - - // Move the file names to allocated memory. - for (j = 0, i = 0; i < *num_file; i++) { - // Require the files to exist. Helps when using /bin/sh - if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) { - continue; - } - - // check if this entry should be included - dir = (os_isdir((*file)[i])); - if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) - continue; - - // Skip files that are not executable if we check for that. - if (!dir && (flags & EW_EXEC) - && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) { - continue; - } - - p = xmalloc(STRLEN((*file)[i]) + 1 + dir); - STRCPY(p, (*file)[i]); - if (dir) { - add_pathsep((char *)p); // add '/' to a directory name - } - (*file)[j++] = p; - } - xfree(buffer); - *num_file = j; - - if (*num_file == 0) { // rejected all entries - XFREE_CLEAR(*file); - goto notfound; - } - - return OK; - -notfound: - if (flags & EW_NOTFOUND) { - save_patterns(num_pat, pat, num_file, file); - return OK; - } - return FAIL; - -} - - -static void save_patterns(int num_pat, char_u **pat, int *num_file, - char_u ***file) -{ - int i; - char_u *s; - - *file = xmalloc((size_t)num_pat * sizeof(char_u *)); - - for (i = 0; i < num_pat; i++) { - s = vim_strsave(pat[i]); - // Be compatible with expand_filename(): halve the number of - // backslashes. - backslash_halve(s); - (*file)[i] = s; - } - *num_file = num_pat; -} - -static bool have_wildcard(int num, char_u **file) -{ - int i; - - for (i = 0; i < num; i++) - if (path_has_wildcard(file[i])) - return true; - return false; -} - -static bool have_dollars(int num, char_u **file) -{ - int i; - - for (i = 0; i < num; i++) - if (vim_strchr(file[i], '$') != NULL) - return true; - return false; -} diff --git a/src/nvim/path.c b/src/nvim/path.c index 0142724a5b..31318f6bea 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1178,7 +1178,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, */ if (recursive) #ifdef SPECIAL_WILDCHAR - return mch_expand_wildcards(num_pat, pat, num_file, file, flags); + return os_expand_wildcards(num_pat, pat, num_file, file, flags); #else return FAIL; #endif @@ -1193,7 +1193,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, for (int i = 0; i < num_pat; i++) { if (has_special_wildchar(pat[i]) && !(vim_backtick(pat[i]) && pat[i][1] == '=')) { - return mch_expand_wildcards(num_pat, pat, num_file, file, flags); + return os_expand_wildcards(num_pat, pat, num_file, file, flags); } } #endif @@ -1233,8 +1233,8 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, else if (has_env_var(p) || *p == '~') { xfree(p); ga_clear_strings(&ga); - i = mch_expand_wildcards(num_pat, pat, num_file, file, - flags | EW_KEEPDOLLAR); + i = os_expand_wildcards(num_pat, pat, num_file, file, + flags | EW_KEEPDOLLAR); recursive = false; return i; } |