void do_job_notification(pid_t pid) { process *p, *q; /* Update status information for child processes. */ update_status(); for(p = first_process; p; p = q) { q = p->next; if(pid == 0 || pid == p->pid) { /* if all processes have completed, tell the user * the job has completed and delete it from the list of active jobs. */ if(p->completed) { format_job_info(p, "completed"); remove_process(p); free(p); } else if(p->stopped) { format_job_info(p, "stopped"); } /* Don't day anything about jobs that are still running. */ else { format_job_info(p, "running"); } } } }
/* Notify the user about stopped or terminated jobs. Delete terminated jobs from the active job list. */ void do_job_notification (void) { job *j, *jlast, *jnext; process *p; /* Update status information for child processes. */ update_status (); jlast = NULL; for (j = first_job; j; j = jnext) { jnext = j->next; /* If all processes have completed, tell the user the job has completed and delete it from the list of active jobs. */ if (job_is_completed (j)) { format_job_info (j, "completed"); if (jlast) jlast->next = jnext; else first_job = jnext; free_job (j); } /* Notify the user about stopped jobs, marking them so that we won’t do this more than once. */ else if (job_is_stopped (j) && !j->notified) { format_job_info (j, "stopped"); j->notified = 1; jlast = j; } /* Don’t say anything about jobs that are still running. */ else jlast = j; } }
void launch_job (job *j, int foreground) { process *p; pid_t pid; int mypipe[2], infile, outfile; infile = j->stdin; for (p = j->first_process; p; p = p->next) { /* Set up pipes, if necessary. */ if (p->next) { if (pipe (mypipe) < 0) { perror ("pipe"); exit (1); } outfile = mypipe[1]; } else outfile = j->stdout; /* Fork the child processes. */ pid = fork (); if (pid == 0) /* This is the child process. */ launch_process (p, j->pgid, infile, outfile, j->stderr, foreground); else if (pid < 0) { /* The fork failed. */ perror ("fork"); exit (1); } else { /* This is the parent process. */ p->pid = pid; if (shell_is_interactive) { if (!j->pgid) j->pgid = pid; setpgid (pid, j->pgid); } } /* Clean up after pipes. */ if (infile != j->stdin) close (infile); if (outfile != j->stdout) close (outfile); infile = mypipe[0]; } format_job_info (j, "launched"); if (!shell_is_interactive) wait_for_job (j); else if (foreground) put_job_in_foreground (j, 0); else put_job_in_background (j, 0); }
int job_reap( bool interactive ) { ASSERT_IS_MAIN_THREAD(); job_t *jnext; int found=0; static int locked = 0; locked++; /* 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(); process_t *p; /* 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( 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 ); locked = 0; return found; }
void sh_launch_job(job_t *job, int foreground) { process_t *p; /* Process iteration */ pid_t pid; /* PID for the newly spawned process */ int output; /* Output file descriptor */ int input = job->stdin; /* Input file descriptor */ int iopipe[2] = {STDIN_FILENO, STDOUT_FILENO}; /* Inter-process comms pipe */ for (p = job->first_process; p != NULL; p = p->next) { /* Set up pipes if applicable, set output */ if (p->next != NULL) { check(pipe(iopipe) >= 0); output = iopipe[1]; } else { output = job->stdout; } /* Fork child process and launch */ check((pid = fork()) >= 0); if (pid == 0) { /* I am the fork */ sh_launch_process(p, job->pgid, input, output, job->stderr, foreground); } else { /* I am parent */ p->pid = pid; if (shell_interactive) { /* * if (job->pgid == 0) * job->pgid = pid; * * MAN specifies that if pgid is 0, pid of * indicated process (pid) shall be used. */ setpgid(pid, job->pgid); } } /* Clean up the pipes, set input */ if (input != job->stdin) close(input); if (output != job->stdout) close(output); input = iopipe[0]; } format_job_info(job, "launched"); /* Either wait until job is finished, or engage new */ if (!shell_interactive) job_wait_blocked(job); else if (foreground) put_in_foreground(job, 0); else put_in_background(job, 0); }
static int process_clean_after_marking(bool allow_interactive) { ASSERT_IS_MAIN_THREAD(); job_t *jnext; int found = 0; // this function 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; // this may be invoked in an exit handler, after the TERM has been torn down // don't try to print in that case (#3222) const bool interactive = allow_interactive && cur_term != NULL; 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 ((!j->get_flag(JOB_SKIP_NOTIFICATION)) && (!interactive) && (!j->get_flag(JOB_FOREGROUND))) { continue; } for (const process_ptr_t &p : j->processes) { int s; if (!p->completed) continue; if (!p->pid) continue; s = p->status; // TODO: The generic process-exit event is useless and unused. // Remove this in future. proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, p->pid, (WIFSIGNALED(s) ? -1 : WEXITSTATUS(s))); // Ignore signal SIGPIPE.We issue it ourselves to the pipe writer when the pipe reader // dies. if (!WIFSIGNALED(s) || WTERMSIG(s) == SIGPIPE) { continue; } // Handle signals other than SIGPIPE. int proc_is_job = (p->is_first_in_job && p->is_last_in_job); if (proc_is_job) j->set_flag(JOB_NOTIFIED, true); if (j->get_flag(JOB_SKIP_NOTIFICATION)) { continue; } // 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 || !j->get_flag(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); const wchar_t *fmt = _(L"%ls: Process %d, \'%ls\' %ls\'%ls\' terminated by signal %ls (%ls)"); fwprintf(stdout, fmt, 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))); } if (cur_term != NULL) { tputs(clr_eol, 1, &writeb); } else { fwprintf(stdout, L"\e[K"); // no term set up - do clr_eol manually } fwprintf(stdout, L"\n"); } found = 1; p->status = 0; // clear status so it is not reported more than once } // 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 (!j->get_flag(JOB_FOREGROUND) && !j->get_flag(JOB_NOTIFIED) && !j->get_flag(JOB_SKIP_NOTIFICATION)) { format_job_info(j, JOB_ENDED); found = 1; } // TODO: The generic process-exit event is useless and unused. // Remove this in future. // Don't fire the exit-event for jobs with pgid -2. // That's our "sentinel" pgid, for jobs that don't (yet) have a pgid, // or jobs that consist entirely of builtins (and hence don't have a process). // This causes issues if fish is PID 2, which is quite common on WSL. See #4582. if (j->pgid != -2) { 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_remove(j); } else if (job_is_stopped(j) && !j->get_flag(JOB_NOTIFIED)) { // Notify the user about newly stopped jobs. if (!j->get_flag(JOB_SKIP_NOTIFICATION)) { format_job_info(j, JOB_STOPPED); found = 1; } j->set_flag(JOB_NOTIFIED, true); } } if (found) fflush(stdout); locked = false; return found; }
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; }