/// 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; }
bool process_iterator_t::next_process(wcstring *out_str, pid_t *out_pid) { wcstring cmd; pid_t pid = 0; while (cmd.empty()) { wcstring name; if (! dir || ! wreaddir(dir, name)) break; if (!iswnumeric(name.c_str())) continue; wcstring path = wcstring(L"/proc/") + name; struct stat buf; if (wstat(path, &buf)) continue; if (buf.st_uid != getuid()) continue; /* remember the pid */ pid = fish_wcstoi(name.c_str(), NULL, 10); /* the 'cmdline' file exists, it should contain the commandline */ FILE *cmdfile; if ((cmdfile=wfopen(path + L"/cmdline", "r"))) { wcstring full_command_line; signal_block(); fgetws2(&full_command_line, cmdfile); signal_unblock(); /* The command line needs to be escaped */ cmd = tok_first(full_command_line.c_str()); } #ifdef SunOS else if ((cmdfile=wfopen(path + L"/psinfo", "r"))) { psinfo_t info; if (fread(&info, sizeof(info), 1, cmdfile)) { /* The filename is unescaped */ cmd = str2wcstring(info.pr_fname); } } #endif if (cmdfile) fclose(cmdfile); } bool result = ! cmd.empty(); if (result) { *out_str = cmd; *out_pid = pid; } return result; }
int wcs2sig(const wchar_t *str) { for (const auto &data : signal_table) { if (match_signal_name(data.name, str)) { return data.signal; } } int res = fish_wcstoi(str); if (errno || res < 0) return -1; return res; }
int wcs2sig(const wchar_t *str) { int i; wchar_t *end = 0; for (i = 0; lookup[i].desc; i++) { if (match_signal_name(lookup[i].name, str)) { return lookup[i].signal; } } errno = 0; int res = fish_wcstoi(str, &end, 10); if (!errno && res >= 0 && !*end) return res; return -1; }
static int parse_cmd_opts(math_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method) int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { const wchar_t *cmd = L"math"; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 's': { opts.scale = fish_wcstoi(w.woptarg); if (errno || opts.scale < 0 || opts.scale > 15) { streams.err.append_format(_(L"%ls: '%ls' is not a valid scale value\n"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } break; } case 'h': { opts.print_help = true; break; } case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { // For most commands this is an error. We ignore it because a math expression // can begin with a minus sign. *optind = w.woptind - 1; return STATUS_CMD_OK; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } *optind = w.woptind; return STATUS_CMD_OK; }
static int find_process(const wchar_t *proc, expand_flags_t flags, std::vector<completion_t> &out) { int found = 0; if (!(flags & EXPAND_SKIP_JOBS)) { ASSERT_IS_MAIN_THREAD(); const job_t *j; if (iswnumeric(proc) || (wcslen(proc)==0)) { /* This is a numeric job string, like '%2' */ if (flags & ACCEPT_INCOMPLETE) { job_iterator_t jobs; while ((j = jobs.next())) { wchar_t jid[16]; if (j->command_is_empty()) continue; swprintf(jid, 16, L"%d", j->job_id); if (wcsncmp(proc, jid, wcslen(proc))==0) { wcstring desc_buff = format_string(COMPLETE_JOB_DESC_VAL, j->command_wcstr()); append_completion(out, jid+wcslen(proc), desc_buff, 0); } } } else { int jid; wchar_t *end; errno = 0; jid = fish_wcstoi(proc, &end, 10); if (jid > 0 && !errno && !*end) { j = job_get(jid); if ((j != 0) && (j->command_wcstr() != 0)) { { append_completion(out, to_string<long>(j->pgid)); found = 1; } } } } } if (found) return 1; job_iterator_t jobs; while ((j = jobs.next())) { if (j->command_is_empty()) continue; size_t offset; if (match_pid(j->command(), proc, flags, &offset)) { if (flags & ACCEPT_INCOMPLETE) { append_completion(out, j->command_wcstr() + offset + wcslen(proc), COMPLETE_JOB_DESC, 0); } else { append_completion(out, to_string<long>(j->pgid)); found = 1; } } } if (found) { return 1; } jobs.reset(); while ((j = jobs.next())) { process_t *p; if (j->command_is_empty()) continue; for (p=j->first_process; p; p=p->next) { if (p->actual_cmd.empty()) continue; size_t offset; if (match_pid(p->actual_cmd, proc, flags, &offset)) { if (flags & ACCEPT_INCOMPLETE) { append_completion(out, wcstring(p->actual_cmd, offset + wcslen(proc)), COMPLETE_CHILD_PROCESS_DESC, 0); } else { append_completion(out, to_string<long>(p->pid), L"", 0); found = 1; } } } } if (found) { return 1; } } /* Iterate over all processes */ wcstring process_name; pid_t process_pid; process_iterator_t iterator; while (iterator.next_process(&process_name, &process_pid)) { size_t offset; if (match_pid(process_name, proc, flags, &offset)) { if (flags & ACCEPT_INCOMPLETE) { append_completion(out, process_name.c_str() + offset + wcslen(proc), COMPLETE_PROCESS_DESC, 0); } else { append_completion(out, to_string<long>(process_pid)); } } } return 1; }
/// The following function is invoked on the main thread, because the job operation is not thread /// safe. It waits for child jobs, not for child processes individually. int builtin_wait(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); int retval = STATUS_CMD_OK; const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); bool any_flag = false; // flag for -n option static const wchar_t *const short_options = L":n"; static const struct woption long_options[] = {{L"any", no_argument, NULL, 'n'}, {NULL, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'n': any_flag = true; break; case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (w.woptind == argc) { // no jobs specified retval = wait_for_backgrounds(parser, any_flag); } else { // jobs specified std::vector<job_id_t> waited_job_ids; for (int i = w.woptind; i < argc; i++) { if (iswnumeric(argv[i])) { // argument is pid pid_t pid = fish_wcstoi(argv[i]); if (errno || pid <= 0) { streams.err.append_format(_(L"%ls: '%ls' is not a valid process id\n"), cmd, argv[i]); continue; } if (job_id_t id = get_job_id_from_pid(pid, parser)) { waited_job_ids.push_back(id); } else { streams.err.append_format( _(L"%ls: Could not find a job with process id '%d'\n"), cmd, pid); } } else { // argument is process name if (!find_job_by_name(argv[i], waited_job_ids, parser)) { streams.err.append_format( _(L"%ls: Could not find child processes with the name '%ls'\n"), cmd, argv[i]); } } } if (waited_job_ids.empty()) return STATUS_INVALID_ARGS; retval = wait_for_backgrounds_specified(parser, waited_job_ids, any_flag); } return retval; }
/// The jobs builtin. Used for printing running jobs. Defined in builtin_jobs.c. int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); int found = 0; int mode = JOBS_DEFAULT; int print_last = 0; static const wchar_t *const short_options = L":cghlpq"; static const struct woption long_options[] = { {L"command", no_argument, NULL, 'c'}, {L"group", no_argument, NULL, 'g'}, {L"help", no_argument, NULL, 'h'}, {L"last", no_argument, NULL, 'l'}, {L"pid", no_argument, NULL, 'p'}, {L"quiet", no_argument, NULL, 'q'}, {nullptr, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'p': { mode = JOBS_PRINT_PID; break; } case 'q': { mode = JOBS_PRINT_NOTHING; break; } case 'c': { mode = JOBS_PRINT_COMMAND; break; } case 'g': { mode = JOBS_PRINT_GROUP; break; } case 'l': { print_last = 1; break; } case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (print_last) { // Ignore unconstructed jobs, i.e. ourself. job_iterator_t jobs; const job_t *j; while ((j = jobs.next())) { if (j->is_constructed() && !j->is_completed()) { builtin_jobs_print(j, mode, !streams.out_is_redirected, streams); return STATUS_CMD_ERROR; } } } else { if (w.woptind < argc) { int i; for (i = w.woptind; i < argc; i++) { const job_t *j = nullptr; if (argv[i][0] == L'%') { int jobId = -1; jobId = fish_wcstoi(argv[i] + 1); if (errno || jobId < -1) { streams.err.append_format(_(L"%ls: '%ls' is not a valid job id"), cmd, argv[i]); return STATUS_INVALID_ARGS; } j = job_t::from_job_id(jobId); } else { int pid = fish_wcstoi(argv[i]); if (errno || pid < 0) { streams.err.append_format(_(L"%ls: '%ls' is not a valid process id\n"), cmd, argv[i]); return STATUS_INVALID_ARGS; } j = job_t::from_pid(pid); } if (j && !j->is_completed() && j->is_constructed()) { builtin_jobs_print(j, mode, false, streams); found = 1; } else { streams.err.append_format(_(L"%ls: No suitable job: %ls\n"), cmd, argv[i]); return STATUS_CMD_ERROR; } } } else { job_iterator_t jobs; const job_t *j; while ((j = jobs.next())) { // Ignore unconstructed jobs, i.e. ourself. if (j->is_constructed() && !j->is_completed()) { builtin_jobs_print(j, mode, !found && !streams.out_is_redirected, streams); found = 1; } } } } if (!found) { // Do not babble if not interactive. if (!streams.out_is_redirected && mode != JOBS_PRINT_NOTHING) { streams.out.append_format(_(L"%ls: There are no jobs\n"), argv[0]); } return STATUS_CMD_ERROR; } return STATUS_CMD_OK; }
static int parse_cmd_opts(status_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method) int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { wchar_t *cmd = argv[0]; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case STATUS_IS_FULL_JOB_CTRL: { if (!set_status_cmd(cmd, opts, STATUS_IS_FULL_JOB_CTRL, streams)) { return STATUS_CMD_ERROR; } break; } case STATUS_IS_INTERACTIVE_JOB_CTRL: { if (!set_status_cmd(cmd, opts, STATUS_IS_INTERACTIVE_JOB_CTRL, streams)) { return STATUS_CMD_ERROR; } break; } case STATUS_IS_NO_JOB_CTRL: { if (!set_status_cmd(cmd, opts, STATUS_IS_NO_JOB_CTRL, streams)) { return STATUS_CMD_ERROR; } break; } case STATUS_FISH_PATH: { if (!set_status_cmd(cmd, opts, STATUS_FISH_PATH, streams)) { return STATUS_CMD_ERROR; } break; } case 'L': { opts.level = fish_wcstoi(w.woptarg); if (opts.level < 0 || errno == ERANGE) { streams.err.append_format(_(L"%ls: Invalid level value '%ls'\n"), argv[0], w.woptarg); return STATUS_INVALID_ARGS; } else if (errno) { streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg); return STATUS_INVALID_ARGS; } break; } case 'c': { if (!set_status_cmd(cmd, opts, STATUS_IS_COMMAND_SUB, streams)) { return STATUS_CMD_ERROR; } break; } case 'b': { if (!set_status_cmd(cmd, opts, STATUS_IS_BLOCK, streams)) { return STATUS_CMD_ERROR; } break; } case 'i': { if (!set_status_cmd(cmd, opts, STATUS_IS_INTERACTIVE, streams)) { return STATUS_CMD_ERROR; } break; } case 'l': { if (!set_status_cmd(cmd, opts, STATUS_IS_LOGIN, streams)) { return STATUS_CMD_ERROR; } break; } case 'f': { if (!set_status_cmd(cmd, opts, STATUS_FILENAME, streams)) { return STATUS_CMD_ERROR; } break; } case 'n': { if (!set_status_cmd(cmd, opts, STATUS_LINE_NUMBER, streams)) { return STATUS_CMD_ERROR; } break; } case 'j': { if (!set_status_cmd(cmd, opts, STATUS_SET_JOB_CONTROL, streams)) { return STATUS_CMD_ERROR; } opts.new_job_control_mode = job_control_str_to_mode(w.woptarg, cmd, streams); if (opts.new_job_control_mode == -1) { return STATUS_CMD_ERROR; } break; } case 't': { if (!set_status_cmd(cmd, opts, STATUS_STACK_TRACE, streams)) { return STATUS_CMD_ERROR; } break; } case 'h': { opts.print_help = true; break; } case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } *optind = w.woptind; return STATUS_CMD_OK; }
static int parse_cmd_opts(function_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method) int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { const wchar_t *cmd = L"function"; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'd': { opts.description = w.woptarg; break; } case 's': { int sig = wcs2sig(w.woptarg); if (sig == -1) { streams.err.append_format(_(L"%ls: Unknown signal '%ls'"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } opts.events.push_back(event_t::signal_event(sig)); break; } case 'v': { if (!valid_var_name(w.woptarg)) { streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, w.woptarg); return STATUS_INVALID_ARGS; } opts.events.push_back(event_t::variable_event(w.woptarg)); break; } case 'e': { opts.events.push_back(event_t::generic_event(w.woptarg)); break; } case 'j': case 'p': { pid_t pid; event_t e(EVENT_ANY); if ((opt == 'j') && (wcscasecmp(w.woptarg, L"caller") == 0)) { job_id_t job_id = -1; if (is_subshell) { size_t block_idx = 0; // Find the outermost substitution block. for (block_idx = 0;; block_idx++) { const block_t *b = parser.block_at_index(block_idx); if (b == NULL || b->type() == SUBST) break; } // Go one step beyond that, to get to the caller. const block_t *caller_block = parser.block_at_index(block_idx + 1); if (caller_block != NULL && caller_block->job != NULL) { job_id = caller_block->job->job_id; } } if (job_id == -1) { streams.err.append_format( _(L"%ls: Cannot find calling job for event handler"), cmd); return STATUS_INVALID_ARGS; } e.type = EVENT_JOB_ID; e.param1.job_id = job_id; } else { pid = fish_wcstoi(w.woptarg); if (errno || pid < 0) { streams.err.append_format(_(L"%ls: Invalid process id '%ls'"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } e.type = EVENT_EXIT; e.param1.pid = (opt == 'j' ? -1 : 1) * abs(pid); } opts.events.push_back(e); break; } case 'a': { opts.named_arguments.push_back(w.woptarg); break; } case 'S': { opts.shadow_scope = false; break; } case 'w': { opts.wrap_targets.push_back(w.woptarg); break; } case 'V': { if (!valid_var_name(w.woptarg)) { streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, w.woptarg); return STATUS_INVALID_ARGS; } opts.inherit_vars.push_back(w.woptarg); break; } case 'h': { opts.print_help = true; break; } case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } *optind = w.woptind; return STATUS_CMD_OK; }
/// The jobs builtin. Used fopr printing running jobs. Defined in builtin_jobs.c. int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wgetopter_t w; int argc = 0; int found = 0; int mode = JOBS_DEFAULT; int print_last = 0; argc = builtin_count_args(argv); w.woptind = 0; while (1) { static const struct woption long_options[] = { {L"pid", no_argument, 0, 'p'}, {L"command", no_argument, 0, 'c'}, {L"group", no_argument, 0, 'g'}, {L"last", no_argument, 0, 'l'}, {L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"pclgh", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } case 'p': { mode = JOBS_PRINT_PID; break; } case 'c': { mode = JOBS_PRINT_COMMAND; break; } case 'g': { mode = JOBS_PRINT_GROUP; break; } case 'l': { print_last = 1; break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return 0; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return 1; } default: { DIE("unexpected opt"); break; } } } if (print_last) { // Ignore unconstructed jobs, i.e. ourself. job_iterator_t jobs; const job_t *j; while ((j = jobs.next())) { if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j)) { builtin_jobs_print(j, mode, !streams.out_is_redirected, streams); return 0; } } } else { if (w.woptind < argc) { int i; for (i = w.woptind; i < argc; i++) { int pid = fish_wcstoi(argv[i]); if (errno || pid < 0) { streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]); return 1; } const job_t *j = job_get_from_pid(pid); if (j && !job_is_completed(j)) { builtin_jobs_print(j, mode, false, streams); found = 1; } else { streams.err.append_format(_(L"%ls: No suitable job: %d\n"), argv[0], pid); return 1; } } } else { job_iterator_t jobs; const job_t *j; while ((j = jobs.next())) { // Ignore unconstructed jobs, i.e. ourself. if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j)) { builtin_jobs_print(j, mode, !found && !streams.out_is_redirected, streams); found = 1; } } } } if (!found) { // Do not babble if not interactive. if (!streams.out_is_redirected) { streams.out.append_format(_(L"%ls: There are no jobs\n"), argv[0]); } return 1; } return 0; }
/** The jobs builtin. Used fopr printing running jobs. Defined in builtin_jobs.c. */ static int builtin_jobs(parser_t &parser, wchar_t **argv) { int argc=0; int found=0; int mode=JOBS_DEFAULT; int print_last = 0; const job_t *j; argc = builtin_count_args(argv); woptind=0; while (1) { static const struct woption long_options[] = { { L"pid", no_argument, 0, 'p' } , { L"command", no_argument, 0, 'c' } , { L"group", no_argument, 0, 'g' } , { L"last", no_argument, 0, 'l' } , { L"help", no_argument, 0, 'h' } , { 0, 0, 0, 0 } } ; int opt_index = 0; int opt = wgetopt_long(argc, argv, L"pclgh", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: if (long_options[opt_index].flag != 0) break; append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, argv[0], stderr_buffer); return 1; case 'p': mode=JOBS_PRINT_PID; break; case 'c': mode=JOBS_PRINT_COMMAND; break; case 'g': mode=JOBS_PRINT_GROUP; break; case 'l': { print_last = 1; 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; } } /* Do not babble if not interactive */ if (builtin_out_redirect) { found=1; } if (print_last) { /* Ignore unconstructed jobs, i.e. ourself. */ job_iterator_t jobs; const job_t *j; while ((j = jobs.next())) { if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j)) { builtin_jobs_print(j, mode, !found); return 0; } } } else { if (woptind < argc) { int i; found = 1; for (i=woptind; i<argc; i++) { int pid; wchar_t *end; errno=0; pid=fish_wcstoi(argv[i], &end, 10); if (errno || *end) { append_format(stderr_buffer, _(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]); return 1; } j = job_get_from_pid(pid); if (j && !job_is_completed(j)) { builtin_jobs_print(j, mode, !found); } else { append_format(stderr_buffer, _(L"%ls: No suitable job: %d\n"), argv[0], pid); return 1; } } } else { job_iterator_t jobs; const job_t *j; while ((j = jobs.next())) { /* Ignore unconstructed jobs, i.e. ourself. */ if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j)) { builtin_jobs_print(j, mode, !found); found = 1; } } } } if (!found) { append_format(stdout_buffer, _(L"%ls: There are no jobs\n"), argv[0]); } return 0; }
/// Builtin for removing jobs from the job list. int builtin_disown(parser_t &parser, io_streams_t &streams, wchar_t **argv) { 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; } if (argv[1] == 0) { // Select last constructed job (ie first job in the job queue) that is possible to disown. // Stopped jobs can be disowned (they will be continued). // Foreground jobs can be disowned. // Even jobs that aren't under job control can be disowned! job_t *job = nullptr; for (const auto &j : jobs()) { if (j->is_constructed() && (!j->is_completed())) { job = j.get(); break; } } if (job) { retval = disown_job(cmd, parser, streams, job); } else { streams.err.append_format(_(L"%ls: There are no suitable jobs\n"), cmd); retval = STATUS_CMD_ERROR; } } else { std::set<job_t *> jobs; // If one argument is not a valid pid (i.e. integer >= 0), fail without disowning anything, // but still print errors for all of them. // Non-existent jobs aren't an error, but information about them is useful. // Multiple PIDs may refer to the same job; include the job only once by using a set. for (int i = 1; argv[i]; i++) { int pid = fish_wcstoi(argv[i]); if (errno || pid < 0) { streams.err.append_format(_(L"%ls: '%ls' is not a valid job specifier\n"), cmd, argv[i]); retval = STATUS_INVALID_ARGS; } else { if (job_t *j = parser.job_get_from_pid(pid)) { jobs.insert(j); } else { streams.err.append_format(_(L"%ls: Could not find job '%d'\n"), cmd, pid); } } } if (retval != STATUS_CMD_OK) { return retval; } // Disown all target jobs for (const auto &j : jobs) { retval |= disown_job(cmd, parser, streams, j); } } return retval; }