/// The exit builtin. Calls reader_exit to exit and returns the value specified. int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv) { const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); exit_cmd_opts_t opts; int optind; int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } if (optind + 1 < argc) { streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } if (optind == argc) { retval = proc_get_last_status(); } else { retval = fish_wcstoi(argv[optind]); if (errno) { streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), cmd, argv[optind]); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } } reader_exit(1, 0); return retval; }
/// Implementation of the builtin breakpoint command, used to launch the interactive debugger. static int builtin_breakpoint(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; if (argv[1] != NULL) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 0, builtin_count_args(argv) - 1); return STATUS_INVALID_ARGS; } // If we're not interactive then we can't enter the debugger. So treat this command as a no-op. if (!shell_is_interactive()) { return STATUS_CMD_ERROR; } // Ensure we don't allow creating a breakpoint at an interactive prompt. There may be a simpler // or clearer way to do this but this works. const block_t *block1 = parser.block_at_index(1); if (!block1 || block1->type() == BREAKPOINT) { streams.err.append_format(_(L"%ls: Command not valid at an interactive prompt\n"), cmd); return STATUS_ILLEGAL_CMD; } const breakpoint_block_t *bpb = parser.push_block<breakpoint_block_t>(); reader_read(STDIN_FILENO, streams.io_chain ? *streams.io_chain : io_chain_t()); parser.pop_block(bpb); return proc_get_last_status(); }
int job_reap(bool allow_interactive) { ASSERT_IS_MAIN_THREAD(); int found = 0; process_mark_finished_children(false); // Preserve the exit status. const int saved_status = proc_get_last_status(); found = process_clean_after_marking(allow_interactive); // Restore the exit status. proc_set_last_status(saved_status); return found; }
/** Perform the action of the specified binding */ static wint_t input_exec_binding( input_mapping_t *m, const wchar_t *seq ) { wchar_t code = input_function_get_code( m->command ); if( code != -1 ) { switch( code ) { case R_SELF_INSERT: { return seq[0]; } default: { return code; } } } else { /* This key sequence is bound to a command, which is sent to the parser for evaluation. */ int last_status = proc_get_last_status(); eval( m->command, 0, TOP ); proc_set_last_status( last_status ); /* We still need to return something to the caller, R_NULL tells the reader that no key press needs to be handled, and no repaint is needed. Bindings that produce output should emit a R_REPAINT function by calling 'commandline -f repaint' to tell fish that a repaint is in order. */ return R_NULL; } }
/// The set builtin creates, updates, and erases (removes, deletes) variables. int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) { const int incoming_exit_status = proc_get_last_status(); wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); set_cmd_opts_t opts; int optind; int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; argv += optind; argc -= optind; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } retval = validate_cmd_opts(cmd, opts, argc, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.query) { retval = builtin_set_query(cmd, opts, argc, argv, parser, streams); } else if (opts.erase) { retval = builtin_set_erase(cmd, opts, argc, argv, parser, streams); } else if (opts.list) { // explicit list the vars we know about retval = builtin_set_list(cmd, opts, argc, argv, parser, streams); } else if (opts.show) { retval = builtin_set_show(cmd, opts, argc, argv, parser, streams); } else if (argc == 0) { // implicit list the vars we know about retval = builtin_set_list(cmd, opts, argc, argv, parser, streams); } else { retval = builtin_set_set(cmd, opts, argc, argv, parser, streams); } if (retval == STATUS_CMD_OK && opts.preserve_failure_exit_status) retval = incoming_exit_status; return retval; }
int main(int argc, char **argv) { int res = 1; int my_optind = 0; program_name = L"fish"; set_main_thread(); setup_fork_guards(); signal_unblock_all(); setlocale(LC_ALL, ""); fish_setlocale(); // struct stat tmp; // stat("----------FISH_HIT_MAIN----------", &tmp); if (!argv[0]) { static const char *dummy_argv[2] = {"fish", NULL}; argv = (char **)dummy_argv; //!OCLINT(parameter reassignment) argc = 1; //!OCLINT(parameter reassignment) } fish_cmd_opts_t opts; my_optind = fish_parse_opt(argc, argv, &opts); // No-exec is prohibited when in interactive mode. if (is_interactive_session && no_exec) { debug(1, _(L"Can not use the no-execute mode when running an interactive session")); no_exec = 0; } // Only save (and therefore restore) the fg process group if we are interactive. See issues // #197 and #1002. if (is_interactive_session) { save_term_foreground_process_group(); } const struct config_paths_t paths = determine_config_directory_paths(argv[0]); env_init(&paths); // Set features early in case other initialization depends on them. // Start with the ones set in the environment, then those set on the command line (so the // command line takes precedence). if (auto features_var = env_get(L"fish_features")) { for (const wcstring &s : features_var->as_list()) { mutable_fish_features().set_from_string(s); } } mutable_fish_features().set_from_string(opts.features); proc_init(); builtin_init(); misc_init(); reader_init(); parser_t &parser = parser_t::principal_parser(); const io_chain_t empty_ios; if (read_init(paths)) { // Stomp the exit status of any initialization commands (issue #635). proc_set_last_status(STATUS_CMD_OK); // Run post-config commands specified as arguments, if any. if (!opts.postconfig_cmds.empty()) { res = run_command_list(&opts.postconfig_cmds, empty_ios); } if (!opts.batch_cmds.empty()) { // Run the commands specified as arguments, if any. if (is_login) { // Do something nasty to support OpenSUSE assuming we're bash. This may modify cmds. fish_xdm_login_hack_hack_hack_hack(&opts.batch_cmds, argc - my_optind, argv + my_optind); } res = run_command_list(&opts.batch_cmds, empty_ios); reader_exit(0, 0); } else if (my_optind == argc) { // Implicitly interactive mode. res = reader_read(STDIN_FILENO, empty_ios); } else { char *file = *(argv + (my_optind++)); int fd = open(file, O_RDONLY); if (fd == -1) { perror(file); } else { // OK to not do this atomically since we cannot have gone multithreaded yet. set_cloexec(fd); wcstring_list_t list; for (char **ptr = argv + my_optind; *ptr; ptr++) { list.push_back(str2wcstring(*ptr)); } env_set(L"argv", ENV_DEFAULT, list); const wcstring rel_filename = str2wcstring(file); reader_push_current_filename(rel_filename.c_str()); res = reader_read(fd, empty_ios); if (res) { debug(1, _(L"Error while reading file %ls\n"), reader_current_filename() ? reader_current_filename() : _(L"Standard input")); } reader_pop_current_filename(); } } } int exit_status = res ? STATUS_CMD_UNKNOWN : proc_get_last_status(); // TODO: The generic process-exit event is useless and unused. // Remove this in future. proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, getpid(), exit_status); event_fire_generic(L"fish_exit"); restore_term_mode(); restore_term_foreground_process_group(); if (g_profiling_active) { parser.emit_profiling(s_profiling_output_filename); } history_save_all(); proc_destroy(); exit_without_destructors(exit_status); return EXIT_FAILURE; // above line should always exit }
int main(int argc, char **argv) { int res=1; int my_optind=0; set_main_thread(); setup_fork_guards(); wsetlocale(LC_ALL, L""); is_interactive_session=1; program_name=L"fish"; //struct stat tmp; //stat("----------FISH_HIT_MAIN----------", &tmp); std::vector<std::string> cmds; my_optind = fish_parse_opt(argc, argv, &cmds); /* No-exec is prohibited when in interactive mode */ if (is_interactive_session && no_exec) { debug(1, _(L"Can not use the no-execute mode when running an interactive session")); no_exec = 0; } /* Only save (and therefore restore) the fg process group if we are interactive. See #197, #1002 */ if (is_interactive_session) { save_term_foreground_process_group(); } const struct config_paths_t paths = determine_config_directory_paths(argv[0]); proc_init(); event_init(); wutil_init(); builtin_init(); function_init(); env_init(&paths); reader_init(); history_init(); /* For setcolor to support term256 in config.fish (#1022) */ update_fish_term256(); parser_t &parser = parser_t::principal_parser(); if (g_log_forks) printf("%d: g_fork_count: %d\n", __LINE__, g_fork_count); const io_chain_t empty_ios; if (read_init(paths)) { /* Stop the exit status of any initialization commands (#635) */ proc_set_last_status(STATUS_BUILTIN_OK); /* Run the commands specified as arguments, if any */ if (! cmds.empty()) { /* Do something nasty to support OpenSUSE assuming we're bash. This may modify cmds. */ if (is_login) { fish_xdm_login_hack_hack_hack_hack(&cmds, argc - my_optind, argv + my_optind); } for (size_t i=0; i < cmds.size(); i++) { const wcstring cmd_wcs = str2wcstring(cmds.at(i)); res = parser.eval(cmd_wcs, empty_ios, TOP); } reader_exit(0, 0); } else { if (my_optind == argc) { res = reader_read(STDIN_FILENO, empty_ios); } else { char **ptr; char *file = *(argv+(my_optind++)); int i; int fd; if ((fd = open(file, O_RDONLY)) == -1) { wperror(L"open"); return 1; } // OK to not do this atomically since we cannot have gone multithreaded yet set_cloexec(fd); if (*(argv+my_optind)) { wcstring sb; for (i=1,ptr = argv+my_optind; *ptr; i++, ptr++) { if (i != 1) sb.append(ARRAY_SEP_STR); sb.append(str2wcstring(*ptr)); } env_set(L"argv", sb.c_str(), 0); } const wcstring rel_filename = str2wcstring(file); const wchar_t *abs_filename = wrealpath(rel_filename, NULL); if (!abs_filename) { abs_filename = wcsdup(rel_filename.c_str()); } reader_push_current_filename(intern(abs_filename)); free((void *)abs_filename); res = reader_read(fd, empty_ios); if (res) { debug(1, _(L"Error while reading file %ls\n"), reader_current_filename()?reader_current_filename(): _(L"Standard input")); } reader_pop_current_filename(); } } } proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, getpid(), res); restore_term_mode(); restore_term_foreground_process_group(); history_destroy(); proc_destroy(); builtin_destroy(); reader_destroy(); parser.destroy(); wutil_destroy(); event_destroy(); env_destroy(); if (g_log_forks) printf("%d: g_fork_count: %d\n", __LINE__, g_fork_count); exit_without_destructors(res ? STATUS_UNKNOWN_COMMAND : proc_get_last_status()); return EXIT_FAILURE; //above line should always exit }
int main( int argc, char **argv ) { struct stat tmp; int res=1; const char *cmd=0; int my_optind=0; set_main_thread(); setup_fork_guards(); wsetlocale( LC_ALL, L"" ); is_interactive_session=1; program_name=L"fish"; stat("----------FISH_HIT_MAIN----------", &tmp); my_optind = fish_parse_opt( argc, argv, &cmd ); /* No-exec is prohibited when in interactive mode */ if( is_interactive_session && no_exec) { debug( 1, _(L"Can not use the no-execute mode when running an interactive session") ); no_exec = 0; } const struct config_paths_t paths = determine_config_directory_paths(argv[0]); proc_init(); event_init(); wutil_init(); //parser_init(); builtin_init(); function_init(); env_init(&paths); reader_init(); history_init(); parser_t &parser = parser_t::principal_parser(); if (g_log_forks) printf("%d: g_fork_count: %d\n", __LINE__, g_fork_count); if( read_init(paths) ) { if( cmd != 0 ) { wchar_t *cmd_wcs = str2wcs( cmd ); res = parser.eval( cmd_wcs, 0, TOP ); free(cmd_wcs); reader_exit(0, 0); } else { if( my_optind == argc ) { res = reader_read( STDIN_FILENO, 0 ); } else { char **ptr; char *file = *(argv+(my_optind++)); int i; int fd; wchar_t *rel_filename, *abs_filename; if( ( fd = open(file, O_RDONLY) ) == -1 ) { wperror( L"open" ); return 1; } // OK to not do this atomically since we cannot have gone multithreaded yet set_cloexec(fd); if( *(argv+my_optind)) { wcstring sb; for( i=1,ptr = argv+my_optind; *ptr; i++, ptr++ ) { if( i != 1 ) sb.append( ARRAY_SEP_STR ); sb.append( str2wcstring( *ptr )); } env_set( L"argv", sb.c_str(), 0 ); } rel_filename = str2wcs( file ); abs_filename = wrealpath( rel_filename, 0 ); if( !abs_filename ) { abs_filename = wcsdup(rel_filename); } reader_push_current_filename( intern( abs_filename ) ); free( rel_filename ); free( abs_filename ); res = reader_read( fd, 0 ); if( res ) { debug( 1, _(L"Error while reading file %ls\n"), reader_current_filename()?reader_current_filename(): _(L"Standard input") ); } reader_pop_current_filename(); } } } proc_fire_event( L"PROCESS_EXIT", EVENT_EXIT, getpid(), res ); history_destroy(); proc_destroy(); builtin_destroy(); reader_destroy(); parser.destroy(); wutil_destroy(); event_destroy(); env_destroy(); if (g_log_forks) printf("%d: g_fork_count: %d\n", __LINE__, g_fork_count); return res?STATUS_UNKNOWN_COMMAND:proc_get_last_status(); }
env_var_t env_get_string(const wcstring &key, env_mode_flags_t mode) { const bool has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL); const bool search_local = !has_scope || (mode & ENV_LOCAL); const bool search_global = !has_scope || (mode & ENV_GLOBAL); const bool search_universal = !has_scope || (mode & ENV_UNIVERSAL); const bool search_exported = (mode & ENV_EXPORT) || !(mode & ENV_UNEXPORT); const bool search_unexported = (mode & ENV_UNEXPORT) || !(mode & ENV_EXPORT); /* Make the assumption that electric keys can't be shadowed elsewhere, since we currently block that in env_set() */ if (is_electric(key)) { if (!search_global) return env_var_t::missing_var(); /* Big hack...we only allow getting the history on the main thread. Note that history_t may ask for an environment variable, so don't take the lock here (we don't need it) */ if (key == L"history" && is_main_thread()) { env_var_t result; history_t *history = reader_get_history(); if (! history) { history = &history_t::history_with_name(L"fish"); } if (history) history->get_string_representation(&result, ARRAY_SEP_STR); return result; } else if (key == L"COLUMNS") { return to_string(common_get_width()); } else if (key == L"LINES") { return to_string(common_get_height()); } else if (key == L"status") { return to_string(proc_get_last_status()); } else if (key == L"umask") { return format_string(L"0%0.3o", get_umask()); } // we should never get here unless the electric var list is out of sync } if (search_local || search_global) { /* Lock around a local region */ scoped_lock lock(env_lock); env_node_t *env = search_local ? top : global_env; while (env != NULL) { const var_entry_t *entry = env->find_entry(key); if (entry != NULL && (entry->exportv ? search_exported : search_unexported)) { if (entry->val == ENV_NULL) { return env_var_t::missing_var(); } else { return entry->val; } } if (has_scope) { if (!search_global || env == global_env) break; env = global_env; } else { env = env->next_scope_to_search(); } } } if (!search_universal) return env_var_t::missing_var(); /* Another big hack - only do a universal barrier on the main thread (since it can change variable values) Make sure we do this outside the env_lock because it may itself call env_get_string */ if (is_main_thread() && ! get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if (uvars()) { env_var_t env_var = uvars()->get(key); if (env_var == ENV_NULL || !(uvars()->get_export(key) ? search_exported : search_unexported)) { env_var = env_var_t::missing_var(); } return env_var; } return env_var_t::missing_var(); }
/// The source builtin, sometimes called `.`. Evaluates the contents of a file in the current /// context. int builtin_source(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); help_only_cmd_opts_t opts; int optind; int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } int fd; struct stat buf; const wchar_t *fn, *fn_intern; if (argc == optind || wcscmp(argv[optind], L"-") == 0) { // Either a bare `source` which means to implicitly read from stdin or an explicit `-`. if (argc == optind && !streams.stdin_is_directly_redirected) { // Don't implicitly read from the terminal. return STATUS_CMD_ERROR; } fn = L"-"; fn_intern = fn; fd = dup(streams.stdin_fd); } else { if ((fd = wopen_cloexec(argv[optind], O_RDONLY)) == -1) { streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), cmd, argv[optind]); builtin_wperror(cmd, streams); return STATUS_CMD_ERROR; } if (fstat(fd, &buf) == -1) { close(fd); streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), cmd, argv[optind]); builtin_wperror(L"source", streams); return STATUS_CMD_ERROR; } if (!S_ISREG(buf.st_mode)) { close(fd); streams.err.append_format(_(L"%ls: '%ls' is not a file\n"), cmd, argv[optind]); return STATUS_CMD_ERROR; } fn_intern = intern(argv[optind]); } const source_block_t *sb = parser.push_block<source_block_t>(fn_intern); reader_push_current_filename(fn_intern); // This is slightly subtle. If this is a bare `source` with no args then `argv + optind` already // points to the end of argv. Otherwise we want to skip the file name to get to the args if any. env_set_argv(argv + optind + (argc == optind ? 0 : 1)); retval = reader_read(fd, streams.io_chain ? *streams.io_chain : io_chain_t()); parser.pop_block(sb); if (retval != STATUS_CMD_OK) { streams.err.append_format(_(L"%ls: Error while reading file '%ls'\n"), cmd, fn_intern == intern_static(L"-") ? L"<stdin>" : fn_intern); } else { retval = proc_get_last_status(); } // Do not close fd after calling reader_read. reader_read automatically closes it before calling // eval. reader_pop_current_filename(); return retval; }
/// Execute a block node or function "process". /// \p user_ios contains the list of user-specified ios, used so we can avoid stomping on them with /// our pipes. \return true on success, false on error. static bool exec_block_or_func_process(parser_t &parser, job_t *j, process_t *p, const io_chain_t &user_ios, io_chain_t io_chain) { assert((p->type == INTERNAL_FUNCTION || p->type == INTERNAL_BLOCK_NODE) && "Unexpected process type"); // Create an output buffer if we're piping to another process. shared_ptr<io_buffer_t> block_output_io_buffer{}; if (!p->is_last_in_job) { // Be careful to handle failure, e.g. too many open fds. block_output_io_buffer = io_buffer_t::create(STDOUT_FILENO, user_ios); if (!block_output_io_buffer) { job_mark_process_as_failed(j, p); return false; } else { // This looks sketchy, because we're adding this io buffer locally - they // aren't in the process or job redirection list. Therefore select_try won't // be able to read them. However we call block_output_io_buffer->read() // below, which reads until EOF. So there's no need to select on this. io_chain.push_back(block_output_io_buffer); } } if (p->type == INTERNAL_FUNCTION) { const wcstring func_name = p->argv0(); auto props = function_get_properties(func_name); if (!props) { debug(0, _(L"Unknown function '%ls'"), p->argv0()); return false; } const std::map<wcstring, env_var_t> inherit_vars = function_get_inherit_vars(func_name); function_block_t *fb = parser.push_block<function_block_t>(p, func_name, props->shadow_scope); function_prepare_environment(func_name, p->get_argv() + 1, inherit_vars); parser.forbid_function(func_name); internal_exec_helper(parser, props->parsed_source, props->body_node, io_chain); parser.allow_function(); parser.pop_block(fb); } else { assert(p->type == INTERNAL_BLOCK_NODE); assert(p->block_node_source && p->internal_block_node && "Process is missing node info"); internal_exec_helper(parser, p->block_node_source, p->internal_block_node, io_chain); } int status = proc_get_last_status(); // Handle output from a block or function. This usually means do nothing, but in the // case of pipes, we have to buffer such io, since otherwise the internal pipe // buffer might overflow. if (!block_output_io_buffer.get()) { // No buffer, so we exit directly. This means we have to manually set the exit // status. if (p->is_last_in_job) { proc_set_last_status(j->get_flag(job_flag_t::NEGATE) ? (!status) : status); } p->completed = 1; return true; } // Here we must have a non-NULL block_output_io_buffer. assert(block_output_io_buffer.get() != NULL); io_chain.remove(block_output_io_buffer); block_output_io_buffer->read(); const std::string buffer_contents = block_output_io_buffer->buffer().newline_serialized(); const char *buffer = buffer_contents.data(); size_t count = buffer_contents.size(); if (count > 0) { // We don't have to drain threads here because our child process is simple. const char *fork_reason = p->type == INTERNAL_BLOCK_NODE ? "internal block io" : "internal function io"; if (!fork_child_for_process(j, p, io_chain, false, fork_reason, [&] { exec_write_and_exit(block_output_io_buffer->fd, buffer, count, status); })) { return false; } } else { if (p->is_last_in_job) { proc_set_last_status(j->get_flag(job_flag_t::NEGATE) ? (!status) : status); } p->completed = 1; } return true; }
static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst, bool apply_exit_status, bool is_subcmd) { ASSERT_IS_MAIN_THREAD(); bool prev_subshell = is_subshell; const int prev_status = proc_get_last_status(); bool split_output = false; const auto ifs = env_get(L"IFS"); if (!ifs.missing_or_empty()) { split_output = true; } is_subshell = true; int subcommand_status = -1; // assume the worst // IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may // be null. const shared_ptr<io_buffer_t> io_buffer( io_buffer_t::create(STDOUT_FILENO, io_chain_t(), is_subcmd ? read_byte_limit : 0)); if (io_buffer.get() != NULL) { parser_t &parser = parser_t::principal_parser(); if (parser.eval(cmd, io_chain_t(io_buffer), SUBST) == 0) { subcommand_status = proc_get_last_status(); } io_buffer->read(); } if (io_buffer->buffer().discarded()) subcommand_status = STATUS_READ_TOO_MUCH; // If the caller asked us to preserve the exit status, restore the old status. Otherwise set the // status of the subcommand. proc_set_last_status(apply_exit_status ? subcommand_status : prev_status); is_subshell = prev_subshell; if (lst == NULL || io_buffer.get() == NULL) { return subcommand_status; } // Walk over all the elements. for (const auto &elem : io_buffer->buffer().elements()) { if (elem.is_explicitly_separated()) { // Just append this one. lst->push_back(str2wcstring(elem.contents)); continue; } // Not explicitly separated. We have to split it explicitly. assert(!elem.is_explicitly_separated() && "should not be explicitly separated"); const char *begin = elem.contents.data(); const char *end = begin + elem.contents.size(); if (split_output) { const char *cursor = begin; while (cursor < end) { // Look for the next separator. const char *stop = (const char *)memchr(cursor, '\n', end - cursor); const bool hit_separator = (stop != NULL); if (!hit_separator) { // If it's not found, just use the end. stop = end; } // Stop now points at the first character we do not want to copy. lst->push_back(str2wcstring(cursor, stop - cursor)); // If we hit a separator, skip over it; otherwise we're at the end. cursor = stop + (hit_separator ? 1 : 0); } } else { // We're not splitting output, but we still want to trim off a trailing newline. if (end != begin && end[-1] == '\n') { --end; } lst->push_back(str2wcstring(begin, end - begin)); } } return subcommand_status; }
int job_reap(bool interactive) { ASSERT_IS_MAIN_THREAD(); job_t *jnext; int found=0; /* job_reap may fire an event handler, we do not want to call ourselves recursively (to avoid infinite recursion). */ static bool locked = false; if (locked) { return 0; } locked = true; process_mark_finished_children(false); /* Preserve the exit status */ const int saved_status = proc_get_last_status(); job_iterator_t jobs; const size_t job_count = jobs.count(); jnext = jobs.next(); while (jnext) { job_t *j = jnext; jnext = jobs.next(); /* If we are reaping only jobs who do not need status messages sent to the console, do not consider reaping jobs that need status messages */ if ((!job_get_flag(j, JOB_SKIP_NOTIFICATION)) && (!interactive) && (!job_get_flag(j, JOB_FOREGROUND))) { continue; } for (process_t *p = j->first_process; p; p=p->next) { int s; if (!p->completed) continue; if (!p->pid) continue; s = p->status; proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, p->pid, (WIFSIGNALED(s)?-1:WEXITSTATUS(s))); if (WIFSIGNALED(s)) { /* Ignore signal SIGPIPE.We issue it ourselves to the pipe writer when the pipe reader dies. */ if (WTERMSIG(s) != SIGPIPE) { int proc_is_job = ((p==j->first_process) && (p->next == 0)); if (proc_is_job) job_set_flag(j, JOB_NOTIFIED, 1); if (!job_get_flag(j, JOB_SKIP_NOTIFICATION)) { /* Print nothing if we get SIGINT in the foreground process group, to avoid spamming obvious stuff on the console (#1119). If we get SIGINT for the foreground process, assume the user typed ^C and can see it working. It's possible they didn't, and the signal was delivered via pkill, etc., but the SIGINT/SIGTERM distinction is precisely to allow INT to be from a UI and TERM to be programmatic, so this assumption is keeping with the design of signals. If echoctl is on, then the terminal will have written ^C to the console. If off, it won't have. We don't echo ^C either way, so as to respect the user's preference. */ if (WTERMSIG(p->status) != SIGINT || ! job_get_flag(j, JOB_FOREGROUND)) { if (proc_is_job) { // We want to report the job number, unless it's the only job, in which case we don't need to const wcstring job_number_desc = (job_count == 1) ? wcstring() : format_string(L"Job %d, ", j->job_id); fwprintf(stdout, _(L"%ls: %ls\'%ls\' terminated by signal %ls (%ls)"), program_name, job_number_desc.c_str(), truncate_command(j->command()).c_str(), sig2wcs(WTERMSIG(p->status)), signal_get_desc(WTERMSIG(p->status))); } else { const wcstring job_number_desc = (job_count == 1) ? wcstring() : format_string(L"from job %d, ", j->job_id); fwprintf(stdout, _(L"%ls: Process %d, \'%ls\' %ls\'%ls\' terminated by signal %ls (%ls)"), program_name, p->pid, p->argv0(), job_number_desc.c_str(), truncate_command(j->command()).c_str(), sig2wcs(WTERMSIG(p->status)), signal_get_desc(WTERMSIG(p->status))); } tputs(clr_eol,1,&writeb); fwprintf(stdout, L"\n"); } found=1; } /* Clear status so it is not reported more than once */ p->status = 0; } } } /* If all processes have completed, tell the user the job has completed and delete it from the active job list. */ if (job_is_completed(j)) { if (!job_get_flag(j, JOB_FOREGROUND) && !job_get_flag(j, JOB_NOTIFIED) && !job_get_flag(j, JOB_SKIP_NOTIFICATION)) { format_job_info(j, _(L"ended"), job_count); found=1; } proc_fire_event(L"JOB_EXIT", EVENT_EXIT, -j->pgid, 0); proc_fire_event(L"JOB_EXIT", EVENT_JOB_ID, j->job_id, 0); job_free(j); } else if (job_is_stopped(j) && !job_get_flag(j, JOB_NOTIFIED)) { /* Notify the user about newly stopped jobs. */ if (!job_get_flag(j, JOB_SKIP_NOTIFICATION)) { format_job_info(j, _(L"stopped"), job_count); found=1; } job_set_flag(j, JOB_NOTIFIED, 1); } } if (found) fflush(stdout); /* Restore the exit status. */ proc_set_last_status(saved_status); locked = false; return found; }
int main(int argc, char **argv) { int res = 1; int my_optind = 0; program_name = L"fish"; set_main_thread(); setup_fork_guards(); setlocale(LC_ALL, ""); fish_setlocale(); // struct stat tmp; // stat("----------FISH_HIT_MAIN----------", &tmp); if (!argv[0]) { static const char *dummy_argv[2] = {"fish", NULL}; argv = (char **)dummy_argv; //!OCLINT(parameter reassignment) argc = 1; //!OCLINT(parameter reassignment) } std::vector<std::string> cmds; my_optind = fish_parse_opt(argc, argv, &cmds); // No-exec is prohibited when in interactive mode. if (is_interactive_session && no_exec) { debug(1, _(L"Can not use the no-execute mode when running an interactive session")); no_exec = 0; } // Only save (and therefore restore) the fg process group if we are interactive. See issues // #197 and #1002. if (is_interactive_session) { save_term_foreground_process_group(); } const struct config_paths_t paths = determine_config_directory_paths(argv[0]); proc_init(); event_init(); builtin_init(); function_init(); env_init(&paths); reader_init(); history_init(); // For set_color to support term256 in config.fish (issue #1022). update_fish_color_support(); misc_init(); parser_t &parser = parser_t::principal_parser(); const io_chain_t empty_ios; if (read_init(paths)) { // Stomp the exit status of any initialization commands (issue #635). proc_set_last_status(STATUS_BUILTIN_OK); // Run the commands specified as arguments, if any. if (!cmds.empty()) { // Do something nasty to support OpenSUSE assuming we're bash. This may modify cmds. if (is_login) { fish_xdm_login_hack_hack_hack_hack(&cmds, argc - my_optind, argv + my_optind); } for (size_t i = 0; i < cmds.size(); i++) { const wcstring cmd_wcs = str2wcstring(cmds.at(i)); res = parser.eval(cmd_wcs, empty_ios, TOP); } reader_exit(0, 0); } else if (my_optind == argc) { // Interactive mode check_running_fishd(); res = reader_read(STDIN_FILENO, empty_ios); } else { char *file = *(argv + (my_optind++)); int fd = open(file, O_RDONLY); if (fd == -1) { perror(file); } else { // OK to not do this atomically since we cannot have gone multithreaded yet. set_cloexec(fd); if (*(argv + my_optind)) { wcstring sb; char **ptr; int i; for (i = 1, ptr = argv + my_optind; *ptr; i++, ptr++) { if (i != 1) sb.append(ARRAY_SEP_STR); sb.append(str2wcstring(*ptr)); } env_set(L"argv", sb.c_str(), 0); } const wcstring rel_filename = str2wcstring(file); reader_push_current_filename(rel_filename.c_str()); res = reader_read(fd, empty_ios); if (res) { debug(1, _(L"Error while reading file %ls\n"), reader_current_filename() ? reader_current_filename() : _(L"Standard input")); } reader_pop_current_filename(); } } } int exit_status = res ? STATUS_UNKNOWN_COMMAND : proc_get_last_status(); proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, getpid(), exit_status); restore_term_mode(); restore_term_foreground_process_group(); if (g_profiling_active) { parser.emit_profiling(s_profiling_output_filename); } history_destroy(); proc_destroy(); builtin_destroy(); reader_destroy(); event_destroy(); exit_without_destructors(exit_status); return EXIT_FAILURE; // above line should always exit }
/** Perform the specified event. Since almost all event firings will not be matched by even a single event handler, we make sure to optimize the 'no matches' path. This means that nothing is allocated/initialized unless needed. */ static void event_fire_internal(const event_t *event) { size_t i, j; event_list_t fire; /* First we free all events that have been removed */ event_free_kills(); if (events.empty()) return; /* Then we iterate over all events, adding events that should be fired to a second list. We need to do this in a separate step since an event handler might call event_remove or event_add_handler, which will change the contents of the \c events list. */ for (i=0; i<events.size(); i++) { event_t *criterion = events.at(i); /* Check if this event is a match */ if (event_match(criterion, event)) { fire.push_back(criterion); } } /* No matches. Time to return. */ if (fire.empty()) return; /* Iterate over our list of matching events */ for (i=0; i<fire.size(); i++) { event_t *criterion = fire.at(i); int prev_status; /* Check if this event has been removed, if so, dont fire it */ if (event_is_killed(criterion)) continue; /* Fire event */ wcstring buffer = criterion->function_name; if (event->arguments.get()) { for (j=0; j< event->arguments->size(); j++) { wcstring arg_esc = escape_string(event->arguments->at(j), 1); buffer += L" "; buffer += arg_esc; } } // debug( 1, L"Event handler fires command '%ls'", buffer.c_str() ); /* Event handlers are not part of the main flow of code, so they are marked as non-interactive */ proc_push_interactive(0); prev_status = proc_get_last_status(); parser_t &parser = parser_t::principal_parser(); block_t *block = new event_block_t(event); parser.push_block(block); parser.eval(buffer, io_chain_t(), TOP); parser.pop_block(); proc_pop_interactive(); proc_set_last_status(prev_status); } /* Free killed events */ event_free_kills(); }
env_var_t env_get_string(const wcstring &key) { /* Big hack...we only allow getting the history on the main thread. Note that history_t may ask for an environment variable, so don't take the lock here (we don't need it) */ const bool is_main = is_main_thread(); if (key == L"history" && is_main) { env_var_t result; history_t *history = reader_get_history(); if (! history) { history = &history_t::history_with_name(L"fish"); } if (history) history->get_string_representation(result, ARRAY_SEP_STR); return result; } else if (key == L"COLUMNS") { return to_string(common_get_width()); } else if (key == L"LINES") { return to_string(common_get_height()); } else if (key == L"status") { return to_string(proc_get_last_status()); } else if (key == L"umask") { return format_string(L"0%0.3o", get_umask()); } else { { /* Lock around a local region */ scoped_lock lock(env_lock); env_node_t *env = top; wcstring result; while (env != NULL) { const var_entry_t *entry = env->find_entry(key); if (entry != NULL) { if (entry->val == ENV_NULL) { return env_var_t::missing_var(); } else { return entry->val; } } env = env->next_scope_to_search(); } } /* Another big hack - only do a universal barrier on the main thread (since it can change variable values) Make sure we do this outside the env_lock because it may itself call env_get_string */ if (is_main && ! get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } const wchar_t *item = env_universal_get(key); if (!item || (wcscmp(item, ENV_NULL)==0)) { return env_var_t::missing_var(); } else { return item; } } }
int job_reap(bool interactive) { ASSERT_IS_MAIN_THREAD(); job_t *jnext; int found=0; static int locked = 0; locked++; /* Preserve the exit status */ const int saved_status = proc_get_last_status(); /* job_read may fire an event handler, we do not want to call ourselves recursively (to avoid infinite recursion). */ if (locked>1) return 0; job_iterator_t jobs; jnext = jobs.next(); while (jnext) { job_t *j = jnext; jnext = jobs.next(); /* If we are reaping only jobs who do not need status messages sent to the console, do not consider reaping jobs that need status messages */ if ((!job_get_flag(j, JOB_SKIP_NOTIFICATION)) && (!interactive) && (!job_get_flag(j, JOB_FOREGROUND))) { continue; } for (process_t *p = j->first_process; p; p=p->next) { int s; if (!p->completed) continue; if (!p->pid) continue; s = p->status; proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, p->pid, (WIFSIGNALED(s)?-1:WEXITSTATUS(s))); if (WIFSIGNALED(s)) { /* Ignore signal SIGPIPE.We issue it ourselves to the pipe writer when the pipe reader dies. */ if (WTERMSIG(s) != SIGPIPE) { int proc_is_job = ((p==j->first_process) && (p->next == 0)); if (proc_is_job) job_set_flag(j, JOB_NOTIFIED, 1); if (!job_get_flag(j, JOB_SKIP_NOTIFICATION)) { if (proc_is_job) fwprintf(stdout, _(L"%ls: Job %d, \'%ls\' terminated by signal %ls (%ls)"), program_name, j->job_id, j->command_wcstr(), sig2wcs(WTERMSIG(p->status)), signal_get_desc(WTERMSIG(p->status))); else fwprintf(stdout, _(L"%ls: Process %d, \'%ls\' from job %d, \'%ls\' terminated by signal %ls (%ls)"), program_name, p->pid, p->argv0(), j->job_id, j->command_wcstr(), sig2wcs(WTERMSIG(p->status)), signal_get_desc(WTERMSIG(p->status))); tputs(clr_eol,1,&writeb); fwprintf(stdout, L"\n"); found=1; } /* Clear status so it is not reported more than once */ p->status = 0; } } } /* If all processes have completed, tell the user the job has completed and delete it from the active job list. */ if (job_is_completed(j)) { if (!job_get_flag(j, JOB_FOREGROUND) && !job_get_flag(j, JOB_NOTIFIED) && !job_get_flag(j, JOB_SKIP_NOTIFICATION)) { format_job_info(j, _(L"ended")); found=1; } proc_fire_event(L"JOB_EXIT", EVENT_EXIT, -j->pgid, 0); proc_fire_event(L"JOB_EXIT", EVENT_JOB_ID, j->job_id, 0); job_free(j); } else if (job_is_stopped(j) && !job_get_flag(j, JOB_NOTIFIED)) { /* Notify the user about newly stopped jobs. */ if (!job_get_flag(j, JOB_SKIP_NOTIFICATION)) { format_job_info(j, _(L"stopped")); found=1; } job_set_flag(j, JOB_NOTIFIED, 1); } } if (found) fflush(stdout); /* Restore the exit status. */ proc_set_last_status(saved_status); locked = 0; return found; }
int exec_subshell( const wchar_t *cmd, array_list_t *lst ) { char *begin, *end; char z=0; int prev_subshell = is_subshell; int status, prev_status; io_data_t *io_buffer; const wchar_t *ifs; char sep=0; CHECK( cmd, -1 ); ifs = env_get(L"IFS"); if( ifs && ifs[0] ) { if( ifs[0] < 128 ) { sep = '\n';//ifs[0]; } else { sep = 0; debug( 0, L"Warning - invalid command substitution separator '%lc'. Please change the firsta character of IFS", ifs[0] ); } } is_subshell=1; io_buffer= io_buffer_create( 0 ); prev_status = proc_get_last_status(); if( eval( cmd, io_buffer, SUBST ) ) { status = -1; } else { status = proc_get_last_status(); } io_buffer_read( io_buffer ); proc_set_last_status( prev_status ); is_subshell = prev_subshell; b_append( io_buffer->param2.out_buffer, &z, 1 ); begin=end=io_buffer->param2.out_buffer->buff; if( lst ) { while( 1 ) { if( *end == 0 ) { if( begin != end ) { wchar_t *el = str2wcs( begin ); if( el ) { al_push( lst, el ); } else { debug( 2, L"Got null string on line %d of file %s", __LINE__, __FILE__ ); } } io_buffer_destroy( io_buffer ); return status; } else if( *end == sep ) { wchar_t *el; *end=0; el = str2wcs( begin ); if( el ) { al_push( lst, el ); } else { debug( 2, L"Got null string on line %d of file %s", __LINE__, __FILE__ ); } begin = end+1; } end++; } } io_buffer_destroy( io_buffer ); return status; }
/** The set builtin. Creates, updates and erases environment variables and environemnt variable arrays. */ static int builtin_set(parser_t &parser, wchar_t **argv) { /** Variables used for parsing the argument list */ const struct woption long_options[] = { { L"export", no_argument, 0, 'x' }, { L"global", no_argument, 0, 'g' }, { L"local", no_argument, 0, 'l' }, { L"erase", no_argument, 0, 'e' }, { L"names", no_argument, 0, 'n' }, { L"unexport", no_argument, 0, 'u' }, { L"universal", no_argument, 0, 'U' }, { L"long", no_argument, 0, 'L' }, { L"query", no_argument, 0, 'q' }, { L"help", no_argument, 0, 'h' }, { 0, 0, 0, 0 } } ; const wchar_t *short_options = L"+xglenuULqh"; int argc = builtin_count_args(argv); /* Flags to set the work mode */ int local = 0, global = 0, exportv = 0; int erase = 0, list = 0, unexport=0; int universal = 0, query=0; bool shorten_ok = true; bool preserve_incoming_failure_exit_status = true; const int incoming_exit_status = proc_get_last_status(); /* Variables used for performing the actual work */ wchar_t *dest = 0; int retcode=0; int scope; int slice=0; int i; const wchar_t *bad_char = NULL; /* Parse options to obtain the requested operation and the modifiers */ woptind = 0; while (1) { int c = wgetopt_long(argc, argv, short_options, long_options, 0); if (c == -1) { break; } switch (c) { case 0: break; case 'e': erase = 1; preserve_incoming_failure_exit_status = false; break; case 'n': list = 1; preserve_incoming_failure_exit_status = false; break; case 'x': exportv = 1; break; case 'l': local = 1; break; case 'g': global = 1; break; case 'u': unexport = 1; break; case 'U': universal = 1; break; case 'L': shorten_ok = false; break; case 'q': query = 1; preserve_incoming_failure_exit_status = false; break; case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return 0; case '?': builtin_unknown_option(parser, argv[0], argv[woptind-1]); return 1; default: break; } } /* Ok, all arguments have been parsed, let's validate them */ /* If we are checking the existance of a variable (-q) we can not also specify scope */ if (query && (erase || list)) { append_format(stderr_buffer, BUILTIN_ERR_COMBO, argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); return 1; } /* We can't both list and erase variables */ if (erase && list) { append_format(stderr_buffer, BUILTIN_ERR_COMBO, argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); return 1; } /* Variables can only have one scope */ if (local + global + universal > 1) { append_format(stderr_buffer, BUILTIN_ERR_GLOCAL, argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); return 1; } /* Variables can only have one export status */ if (exportv && unexport) { append_format(stderr_buffer, BUILTIN_ERR_EXPUNEXP, argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); return 1; } /* Calculate the scope value for variable assignement */ scope = (local ? ENV_LOCAL : 0) | (global ? ENV_GLOBAL : 0) | (exportv ? ENV_EXPORT : 0) | (unexport ? ENV_UNEXPORT : 0) | (universal ? ENV_UNIVERSAL:0) | ENV_USER; if (query) { /* Query mode. Return the number of variables that do not exist out of the specified variables. */ int i; for (i=woptind; i<argc; i++) { wchar_t *arg = argv[i]; int slice=0; if (!(dest = wcsdup(arg))) { DIE_MEM(); } if (wcschr(dest, L'[')) { slice = 1; *wcschr(dest, L'[')=0; } if (slice) { std::vector<long> indexes; wcstring_list_t result; size_t j; env_var_t dest_str = env_get_string(dest, scope); if (! dest_str.missing()) tokenize_variable_array(dest_str, result); if (!parse_index(indexes, arg, dest, result.size())) { builtin_print_help(parser, argv[0], stderr_buffer); retcode = 1; break; } for (j=0; j < indexes.size() ; j++) { long idx = indexes[j]; if (idx < 1 || (size_t)idx > result.size()) { retcode++; } } } else { if (!env_exist(arg, scope)) { retcode++; } } free(dest); } return retcode; } if (list) { /* Maybe we should issue an error if there are any other arguments? */ print_variables(0, 0, shorten_ok, scope); return 0; } if (woptind == argc) { /* Print values of variables */ if (erase) { append_format(stderr_buffer, _(L"%ls: Erase needs a variable name\n"), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); retcode = 1; } else { print_variables(1, 1, shorten_ok, scope); } return retcode; } if (!(dest = wcsdup(argv[woptind]))) { DIE_MEM(); } if (wcschr(dest, L'[')) { slice = 1; *wcschr(dest, L'[')=0; } if (!wcslen(dest)) { free(dest); append_format(stderr_buffer, BUILTIN_ERR_VARNAME_ZERO, argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); return 1; } if ((bad_char = wcsvarname(dest))) { append_format(stderr_buffer, BUILTIN_ERR_VARCHAR, argv[0], *bad_char); builtin_print_help(parser, argv[0], stderr_buffer); free(dest); return 1; } /* set assignment can work in two modes, either using slices or using the whole array. We detect which mode is used here. */ if (slice) { /* Slice mode */ std::vector<long> indexes; wcstring_list_t result; const env_var_t dest_str = env_get_string(dest, scope); if (! dest_str.missing()) { tokenize_variable_array(dest_str, result); } else if (erase) { retcode = 1; } if (!retcode) { for (; woptind<argc; woptind++) { if (!parse_index(indexes, argv[woptind], dest, result.size())) { builtin_print_help(parser, argv[0], stderr_buffer); retcode = 1; break; } size_t idx_count = indexes.size(); size_t val_count = argc-woptind-1; if (!erase) { if (val_count < idx_count) { append_format(stderr_buffer, _(BUILTIN_SET_ARG_COUNT), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); retcode=1; break; } if (val_count == idx_count) { woptind++; break; } } } } if (!retcode) { /* Slice indexes have been calculated, do the actual work */ if (erase) { erase_values(result, indexes); my_env_set(dest, result, scope); } else { wcstring_list_t value; while (woptind < argc) { value.push_back(argv[woptind++]); } if (update_values(result, indexes, value)) { append_format(stderr_buffer, L"%ls: ", argv[0]); append_format(stderr_buffer, ARRAY_BOUNDS_ERR); stderr_buffer.push_back(L'\n'); } my_env_set(dest, result, scope); } } } else { woptind++; /* No slicing */ if (erase) { if (woptind != argc) { append_format(stderr_buffer, _(L"%ls: Values cannot be specfied with erase\n"), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); retcode=1; } else { retcode = env_remove(dest, scope); } } else { wcstring_list_t val; for (i=woptind; i<argc; i++) val.push_back(argv[i]); retcode = my_env_set(dest, val, scope); } } free(dest); if (retcode == STATUS_BUILTIN_OK && preserve_incoming_failure_exit_status) retcode = incoming_exit_status; return retcode; }
void exec( job_t *j ) { process_t *p; pid_t pid; int mypipe[2]; sigset_t chldset; int skip_fork; io_data_t pipe_read, pipe_write; io_data_t *tmp; io_data_t *io_buffer =0; /* Set to 1 if something goes wrong while exec:ing the job, in which case the cleanup code will kick in. */ int exec_error=0; int needs_keepalive = 0; process_t keepalive; CHECK( j, ); CHECK_BLOCK(); if( no_exec ) return; sigemptyset( &chldset ); sigaddset( &chldset, SIGCHLD ); debug( 4, L"Exec job '%ls' with id %d", j->command, j->job_id ); if( block_io ) { if( j->io ) { j->io = io_add( io_duplicate( j, block_io), j->io ); } else { j->io=io_duplicate( j, block_io); } } io_data_t *input_redirect; for( input_redirect = j->io; input_redirect; input_redirect = input_redirect->next ) { if( (input_redirect->io_mode == IO_BUFFER) && input_redirect->is_input ) { /* Input redirection - create a new gobetween process to take care of buffering */ process_t *fake = halloc( j, sizeof(process_t) ); fake->type = INTERNAL_BUFFER; fake->pipe_write_fd = 1; j->first_process->pipe_read_fd = input_redirect->fd; fake->next = j->first_process; j->first_process = fake; break; } } if( j->first_process->type==INTERNAL_EXEC ) { /* Do a regular launch - but without forking first... */ signal_block(); /* setup_child_process makes sure signals are properly set up. It will also call signal_unblock */ if( !setup_child_process( j, 0 ) ) { /* launch_process _never_ returns */ launch_process( j->first_process ); } else { job_set_flag( j, JOB_CONSTRUCTED, 1 ); j->first_process->completed=1; return; } } pipe_read.fd=0; pipe_write.fd=1; pipe_read.io_mode=IO_PIPE; pipe_read.param1.pipe_fd[0] = -1; pipe_read.param1.pipe_fd[1] = -1; pipe_read.is_input = 1; pipe_write.io_mode=IO_PIPE; pipe_write.is_input = 0; pipe_read.next=0; pipe_write.next=0; pipe_write.param1.pipe_fd[0]=pipe_write.param1.pipe_fd[1]=-1; j->io = io_add( j->io, &pipe_write ); signal_block(); /* See if we need to create a group keepalive process. This is a process that we create to make sure that the process group doesn't die accidentally, and is often needed when a builtin/block/function is inside a pipeline, since that usually means we have to wait for one program to exit before continuing in the pipeline, causing the group leader to exit. */ if( job_get_flag( j, JOB_CONTROL ) ) { for( p=j->first_process; p; p = p->next ) { if( p->type != EXTERNAL ) { if( p->next ) { needs_keepalive = 1; break; } if( p != j->first_process ) { needs_keepalive = 1; break; } } } } if( needs_keepalive ) { keepalive.pid = exec_fork(); if( keepalive.pid == 0 ) { keepalive.pid = getpid(); set_child_group( j, &keepalive, 1 ); pause(); exit(0); } else { set_child_group( j, &keepalive, 0 ); } } /* This loop loops over every process_t in the job, starting it as appropriate. This turns out to be rather complex, since a process_t can be one of many rather different things. The loop also has to handle pipelining between the jobs. */ for( p=j->first_process; p; p = p->next ) { mypipe[1]=-1; skip_fork=0; pipe_write.fd = p->pipe_write_fd; pipe_read.fd = p->pipe_read_fd; // debug( 0, L"Pipe created from fd %d to fd %d", pipe_write.fd, pipe_read.fd ); /* This call is used so the global environment variable array is regenerated, if needed, before the fork. That way, we avoid a lot of duplicate work where EVERY child would need to generate it, since that result would not get written back to the parent. This call could be safely removed, but it would result in slightly lower performance - at least on uniprocessor systems. */ if( p->type == EXTERNAL ) env_export_arr( 1 ); /* Set up fd:s that will be used in the pipe */ if( p == j->first_process->next ) { j->io = io_add( j->io, &pipe_read ); } if( p->next ) { // debug( 1, L"%ls|%ls" , p->argv[0], p->next->argv[0]); if( exec_pipe( mypipe ) == -1 ) { debug( 1, PIPE_ERROR ); wperror (L"pipe"); exec_error=1; break; } memcpy( pipe_write.param1.pipe_fd, mypipe, sizeof(int)*2); } else { /* This is the last element of the pipeline. Remove the io redirection for pipe output. */ j->io = io_remove( j->io, &pipe_write ); } switch( p->type ) { case INTERNAL_FUNCTION: { const wchar_t * orig_def; wchar_t * def=0; array_list_t *named_arguments; int shadows; /* Calls to function_get_definition might need to source a file as a part of autoloading, hence there must be no blocks. */ signal_unblock(); orig_def = function_get_definition( p->argv[0] ); named_arguments = function_get_named_arguments( p->argv[0] ); shadows = function_get_shadows( p->argv[0] ); signal_block(); if( orig_def ) { def = halloc_register( j, wcsdup(orig_def) ); } if( def == 0 ) { debug( 0, _( L"Unknown function '%ls'" ), p->argv[0] ); break; } parser_push_block( shadows?FUNCTION_CALL:FUNCTION_CALL_NO_SHADOW ); current_block->param2.function_call_process = p; current_block->param1.function_call_name = halloc_register( current_block, wcsdup( p->argv[0] ) ); /* set_argv might trigger an event handler, hence we need to unblock signals. */ signal_unblock(); parse_util_set_argv( p->argv+1, named_arguments ); signal_block(); parser_forbid_function( p->argv[0] ); if( p->next ) { io_buffer = io_buffer_create( 0 ); j->io = io_add( j->io, io_buffer ); } internal_exec_helper( def, TOP, j->io ); parser_allow_function(); parser_pop_block(); break; } case INTERNAL_BLOCK: { if( p->next ) { io_buffer = io_buffer_create( 0 ); j->io = io_add( j->io, io_buffer ); } internal_exec_helper( p->argv[0], TOP, j->io ); break; } case INTERNAL_BUILTIN: { int builtin_stdin=0; int fg; int close_stdin=0; /* If this is the first process, check the io redirections and see where we should be reading from. */ if( p == j->first_process ) { io_data_t *in = io_get( j->io, 0 ); if( in ) { switch( in->io_mode ) { case IO_FD: { builtin_stdin = in->param1.old_fd; break; } case IO_PIPE: { builtin_stdin = in->param1.pipe_fd[0]; break; } case IO_FILE: { builtin_stdin=wopen( in->param1.filename, in->param2.flags, OPEN_MASK ); if( builtin_stdin == -1 ) { debug( 1, FILE_ERROR, in->param1.filename ); wperror( L"open" ); } else { close_stdin = 1; } break; } case IO_CLOSE: { /* FIXME: When requesting that stdin be closed, we really don't do anything. How should this be handled? */ builtin_stdin = -1; break; } default: { builtin_stdin=-1; debug( 1, _( L"Unknown input redirection type %d" ), in->io_mode); break; } } } } else { builtin_stdin = pipe_read.param1.pipe_fd[0]; } if( builtin_stdin == -1 ) { exec_error=1; break; } else { int old_out = builtin_out_redirect; int old_err = builtin_err_redirect; /* Since this may be the foreground job, and since a builtin may execute another foreground job, we need to pretend to suspend this job while running the builtin, in order to avoid a situation where two jobs are running at once. The reason this is done here, and not by the relevant builtins, is that this way, the builtin does not need to know what job it is part of. It could probably figure that out by walking the job list, but it seems more robust to make exec handle things. */ builtin_push_io( builtin_stdin ); builtin_out_redirect = has_fd( j->io, 1 ); builtin_err_redirect = has_fd( j->io, 2 ); fg = job_get_flag( j, JOB_FOREGROUND ); job_set_flag( j, JOB_FOREGROUND, 0 ); signal_unblock(); p->status = builtin_run( p->argv, j->io ); builtin_out_redirect=old_out; builtin_err_redirect=old_err; signal_block(); /* Restore the fg flag, which is temporarily set to false during builtin execution so as not to confuse some job-handling builtins. */ job_set_flag( j, JOB_FOREGROUND, fg ); } /* If stdin has been redirected, close the redirection stream. */ if( close_stdin ) { exec_close( builtin_stdin ); } break; } } if( exec_error ) { break; } switch( p->type ) { case INTERNAL_BLOCK: case INTERNAL_FUNCTION: { int status = proc_get_last_status(); /* Handle output from a block or function. This usually means do nothing, but in the case of pipes, we have to buffer such io, since otherwise the internal pipe buffer might overflow. */ if( !io_buffer ) { /* No buffer, so we exit directly. This means we have to manually set the exit status. */ if( p->next == 0 ) { proc_set_last_status( job_get_flag( j, JOB_NEGATE )?(!status):status); } p->completed = 1; break; } j->io = io_remove( j->io, io_buffer ); io_buffer_read( io_buffer ); if( io_buffer->param2.out_buffer->used != 0 ) { pid = exec_fork(); if( pid == 0 ) { /* This is the child process. Write out the contents of the pipeline. */ p->pid = getpid(); setup_child_process( j, p ); exec_write_and_exit(io_buffer->fd, io_buffer->param2.out_buffer->buff, io_buffer->param2.out_buffer->used, status); } else { /* This is the parent process. Store away information on the child, and possibly give it control over the terminal. */ p->pid = pid; set_child_group( j, p, 0 ); } } else { if( p->next == 0 ) { proc_set_last_status( job_get_flag( j, JOB_NEGATE )?(!status):status); } p->completed = 1; } io_buffer_destroy( io_buffer ); io_buffer=0; break; } case INTERNAL_BUFFER: { pid = exec_fork(); if( pid == 0 ) { /* This is the child process. Write out the contents of the pipeline. */ p->pid = getpid(); setup_child_process( j, p ); exec_write_and_exit( 1, input_redirect->param2.out_buffer->buff, input_redirect->param2.out_buffer->used, 0); } else { /* This is the parent process. Store away information on the child, and possibly give it control over the terminal. */ p->pid = pid; set_child_group( j, p, 0 ); } break; } case INTERNAL_BUILTIN: { int skip_fork; /* Handle output from builtin commands. In the general case, this means forking of a worker process, that will write out the contents of the stdout and stderr buffers to the correct file descriptor. Since forking is expensive, fish tries to avoid it wehn possible. */ /* If a builtin didn't produce any output, and it is not inside a pipeline, there is no need to fork */ skip_fork = ( !sb_out->used ) && ( !sb_err->used ) && ( !p->next ); /* If the output of a builtin is to be sent to an internal buffer, there is no need to fork. This helps out the performance quite a bit in complex completion code. */ io_data_t *io = io_get( j->io, 1 ); int buffer_stdout = io && io->io_mode == IO_BUFFER; if( ( !sb_err->used ) && ( !p->next ) && ( sb_out->used ) && ( buffer_stdout ) ) { char *res = wcs2str( (wchar_t *)sb_out->buff ); b_append( io->param2.out_buffer, res, strlen( res ) ); skip_fork = 1; free( res ); } for( io = j->io; io; io=io->next ) { if( io->io_mode == IO_FILE && wcscmp(io->param1.filename, L"/dev/null" )) { skip_fork = 0; } } if( skip_fork ) { p->completed=1; if( p->next == 0 ) { debug( 3, L"Set status of %ls to %d using short circut", j->command, p->status ); int status = proc_format_status(p->status); proc_set_last_status( job_get_flag( j, JOB_NEGATE )?(!status):status ); } break; } /* Ok, unfortunatly, we have to do a real fork. Bummer. */ pid = exec_fork(); if( pid == 0 ) { /* This is the child process. Setup redirections, print correct output to stdout and stderr, and then exit. */ p->pid = getpid(); setup_child_process( j, p ); do_builtin_io( sb_out->used ? (wchar_t *)sb_out->buff : 0, sb_err->used ? (wchar_t *)sb_err->buff : 0 ); exit( p->status ); } else { /* This is the parent process. Store away information on the child, and possibly give it control over the terminal. */ p->pid = pid; set_child_group( j, p, 0 ); } break; } case EXTERNAL: { pid = exec_fork(); if( pid == 0 ) { /* This is the child process. */ p->pid = getpid(); setup_child_process( j, p ); launch_process( p ); /* launch_process _never_ returns... */ } else { /* This is the parent process. Store away information on the child, and possibly fice it control over the terminal. */ p->pid = pid; set_child_group( j, p, 0 ); } break; } } if( p->type == INTERNAL_BUILTIN ) builtin_pop_io(); /* Close the pipe the current process uses to read from the previous process_t */ if( pipe_read.param1.pipe_fd[0] >= 0 ) exec_close( pipe_read.param1.pipe_fd[0] ); /* Set up the pipe the next process uses to read from the current process_t */ if( p->next ) pipe_read.param1.pipe_fd[0] = mypipe[0]; /* If there is a next process in the pipeline, close the output end of the current pipe (the surrent child subprocess already has a copy of the pipe - this makes sure we don't leak file descriptors either in the shell or in the children). */ if( p->next ) { exec_close(mypipe[1]); } } /* The keepalive process is no longer needed, so we terminate it with extreme prejudice */ if( needs_keepalive ) { kill( keepalive.pid, SIGKILL ); } signal_unblock(); debug( 3, L"Job is constructed" ); j->io = io_remove( j->io, &pipe_read ); for( tmp = block_io; tmp; tmp=tmp->next ) j->io = io_remove( j->io, tmp ); job_set_flag( j, JOB_CONSTRUCTED, 1 ); if( !job_get_flag( j, JOB_FOREGROUND ) ) { proc_last_bg_pid = j->pgid; } if( !exec_error ) { job_continue (j, 0); } }
/** Perform the specified event. Since almost all event firings will not be matched by even a single event handler, we make sure to optimize the 'no matches' path. This means that nothing is allocated/initialized unless needed. */ static void event_fire_internal(const event_t &event) { event_list_t fire; /* First we free all events that have been removed, but only if this invocation of event_fire_internal is not a recursive call. */ if (is_event <= 1) event_free_kills(); if (events.empty()) return; /* Then we iterate over all events, adding events that should be fired to a second list. We need to do this in a separate step since an event handler might call event_remove or event_add_handler, which will change the contents of the \c events list. */ for (size_t i=0; i<events.size(); i++) { event_t *criterion = events.at(i); /* Check if this event is a match */ if (event_match(*criterion, event)) { fire.push_back(criterion); } } /* No matches. Time to return. */ if (fire.empty()) return; if (signal_is_blocked()) { /* Fix for https://github.com/fish-shell/fish-shell/issues/608. Don't run event handlers while signals are blocked. */ event_t *heap_event = new event_t(event); input_common_add_callback(fire_event_callback, heap_event); return; } /* Iterate over our list of matching events */ for (size_t i=0; i<fire.size(); i++) { event_t *criterion = fire.at(i); int prev_status; /* Check if this event has been removed, if so, dont fire it */ if (event_is_killed(*criterion)) continue; /* Fire event */ wcstring buffer = criterion->function_name; for (size_t j=0; j < event.arguments.size(); j++) { wcstring arg_esc = escape_string(event.arguments.at(j), 1); buffer += L" "; buffer += arg_esc; } // debug( 1, L"Event handler fires command '%ls'", buffer.c_str() ); /* Event handlers are not part of the main flow of code, so they are marked as non-interactive */ proc_push_interactive(0); prev_status = proc_get_last_status(); parser_t &parser = parser_t::principal_parser(); block_t *block = new event_block_t(event); parser.push_block(block); parser.eval(buffer, io_chain_t(), TOP); parser.pop_block(); proc_pop_interactive(); proc_set_last_status(prev_status); } /* Free killed events */ if (is_event <= 1) event_free_kills(); }
/// The set builtin creates, updates, and erases (removes, deletes) variables. int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); // Flags to set the work mode. int local = 0, global = 0, exportv = 0; int erase = 0, list = 0, unexport = 0; int universal = 0, query = 0; bool shorten_ok = true; bool preserve_failure_exit_status = true; const int incoming_exit_status = proc_get_last_status(); // Variables used for performing the actual work. wchar_t *dest = NULL; int retcode = STATUS_CMD_OK; int scope; int slice = 0; // Variables used for parsing the argument list. This command is atypical in using the "+" // (REQUIRE_ORDER) option for flag parsing. This is not typical of most fish commands. It means // we stop scanning for flags when the first non-flag argument is seen. static const wchar_t *short_options = L"+LUeghlnqux"; static const struct woption long_options[] = {{L"export", no_argument, NULL, 'x'}, {L"global", no_argument, NULL, 'g'}, {L"local", no_argument, NULL, 'l'}, {L"erase", no_argument, NULL, 'e'}, {L"names", no_argument, NULL, 'n'}, {L"unexport", no_argument, NULL, 'u'}, {L"universal", no_argument, NULL, 'U'}, {L"long", no_argument, NULL, 'L'}, {L"query", no_argument, NULL, 'q'}, {L"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; // Parse options to obtain the requested operation and the modifiers. int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'e': { erase = 1; preserve_failure_exit_status = false; break; } case 'n': { list = 1; preserve_failure_exit_status = false; break; } case 'x': { exportv = 1; break; } case 'l': { local = 1; break; } case 'g': { global = 1; break; } case 'u': { unexport = 1; break; } case 'U': { universal = 1; break; } case 'L': { shorten_ok = false; break; } case 'q': { query = 1; preserve_failure_exit_status = false; break; } case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } // Ok, all arguments have been parsed, let's validate them. If we are checking the existance of // a variable (-q) we can not also specify scope. if (query && (erase || list)) { streams.err.append_format(BUILTIN_ERR_COMBO, cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } // We can't both list and erase variables. if (erase && list) { streams.err.append_format(BUILTIN_ERR_COMBO, cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } // Variables can only have one scope. if (local + global + universal > 1) { streams.err.append_format(BUILTIN_ERR_GLOCAL, cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } // Variables can only have one export status. if (exportv && unexport) { streams.err.append_format(BUILTIN_ERR_EXPUNEXP, argv[0]); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } // Calculate the scope value for variable assignement. scope = (local ? ENV_LOCAL : 0) | (global ? ENV_GLOBAL : 0) | (exportv ? ENV_EXPORT : 0) | (unexport ? ENV_UNEXPORT : 0) | (universal ? ENV_UNIVERSAL : 0) | ENV_USER; if (query) { // Query mode. Return the number of variables that do not exist out of the specified // variables. int i; for (i = w.woptind; i < argc; i++) { wchar_t *arg = argv[i]; int slice = 0; dest = wcsdup(arg); assert(dest); if (wcschr(dest, L'[')) { slice = 1; *wcschr(dest, L'[') = 0; } if (slice) { std::vector<long> indexes; wcstring_list_t result; size_t j; env_var_t dest_str = env_get_string(dest, scope); if (!dest_str.missing()) tokenize_variable_array(dest_str, result); if (!parse_index(indexes, arg, dest, result.size(), streams)) { builtin_print_help(parser, streams, cmd, streams.err); retcode = 1; break; } for (j = 0; j < indexes.size(); j++) { long idx = indexes[j]; if (idx < 1 || (size_t)idx > result.size()) { retcode++; } } } else { if (!env_exist(arg, scope)) { retcode++; } } free(dest); } return retcode; } if (list) { // Maybe we should issue an error if there are any other arguments? print_variables(0, 0, shorten_ok, scope, streams); return STATUS_CMD_OK; } if (w.woptind == argc) { // Print values of variables. if (erase) { streams.err.append_format(_(L"%ls: Erase needs a variable name\n"), cmd); builtin_print_help(parser, streams, cmd, streams.err); retcode = STATUS_INVALID_ARGS; } else { print_variables(1, 1, shorten_ok, scope, streams); } return retcode; } dest = wcsdup(argv[w.woptind]); assert(dest); if (wcschr(dest, L'[')) { slice = 1; *wcschr(dest, L'[') = 0; } if (!valid_var_name(dest)) { streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, dest); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_INVALID_ARGS; } // Set assignment can work in two modes, either using slices or using the whole array. We detect // which mode is used here. if (slice) { // Slice mode. std::vector<long> indexes; wcstring_list_t result; const env_var_t dest_str = env_get_string(dest, scope); if (!dest_str.missing()) { tokenize_variable_array(dest_str, result); } else if (erase) { retcode = 1; } if (!retcode) { for (; w.woptind < argc; w.woptind++) { if (!parse_index(indexes, argv[w.woptind], dest, result.size(), streams)) { builtin_print_help(parser, streams, argv[0], streams.err); retcode = 1; break; } size_t idx_count = indexes.size(); size_t val_count = argc - w.woptind - 1; if (!erase) { if (val_count < idx_count) { streams.err.append_format(_(BUILTIN_SET_ARG_COUNT), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); retcode = 1; break; } if (val_count == idx_count) { w.woptind++; break; } } } } if (!retcode) { // Slice indexes have been calculated, do the actual work. if (erase) { erase_values(result, indexes); my_env_set(dest, result, scope, streams); } else { wcstring_list_t value; while (w.woptind < argc) { value.push_back(argv[w.woptind++]); } if (update_values(result, indexes, value)) { streams.err.append_format(L"%ls: ", argv[0]); streams.err.append_format(ARRAY_BOUNDS_ERR); streams.err.push_back(L'\n'); } my_env_set(dest, result, scope, streams); } } } else { w.woptind++; // No slicing. if (erase) { if (w.woptind != argc) { streams.err.append_format(_(L"%ls: Values cannot be specfied with erase\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); retcode = 1; } else { retcode = env_remove(dest, scope); } } else { wcstring_list_t val; for (int i = w.woptind; i < argc; i++) val.push_back(argv[i]); retcode = my_env_set(dest, val, scope, streams); } } // Check if we are setting variables above the effective scope. See // https://github.com/fish-shell/fish-shell/issues/806 env_var_t global_dest = env_get_string(dest, ENV_GLOBAL); if (universal && !global_dest.missing()) { streams.err.append_format( _(L"%ls: Warning: universal scope selected, but a global variable '%ls' exists.\n"), L"set", dest); } free(dest); if (retcode == STATUS_CMD_OK && preserve_failure_exit_status) retcode = incoming_exit_status; return retcode; }