aboutsummaryrefslogtreecommitdiffhomepage
path: root/proc.c
diff options
context:
space:
mode:
Diffstat (limited to 'proc.c')
-rw-r--r--proc.c1268
1 files changed, 1268 insertions, 0 deletions
diff --git a/proc.c b/proc.c
new file mode 100644
index 00000000..53512fe6
--- /dev/null
+++ b/proc.c
@@ -0,0 +1,1268 @@
+/** \file proc.c
+
+Utilities for keeping track of jobs, processes and subshells, as
+well as signal handling functions for tracking children. These
+functions do not themselves launch new processes, the exec library
+will call proc to create representations of the running jobs as
+needed.
+
+Some of the code in this file is based on code from the Glibc manual.
+
+*/
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <wchar.h>
+#include <string.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <dirent.h>
+#include <sys/time.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+
+#include "util.h"
+#include "wutil.h"
+#include "proc.h"
+#include "common.h"
+#include "reader.h"
+#include "sanity.h"
+#include "env.h"
+
+/**
+ Size of message buffer
+*/
+#define MESS_SIZE 256
+
+/**
+ Size of buffer for reading buffered output
+*/
+#define BUFFER_SIZE 4096
+
+/** Status of last process to exit */
+static int last_status=0;
+
+/** Signal flag */
+static sig_atomic_t got_signal=0;
+
+job_t *first_job=0;
+int is_interactive=0;
+int is_interactive_session=0;
+int is_subshell=0;
+int is_block=0;
+int is_login=0;
+
+pid_t proc_last_bg_pid = 0;
+
+io_data_t *io_add( io_data_t *list, io_data_t *element )
+{
+ io_data_t *curr = list;
+ if( curr == 0 )
+ return element;
+ while( curr->next != 0 )
+ curr = curr->next;
+ curr->next = element;
+ return list;
+}
+
+io_data_t *io_remove( io_data_t *list, io_data_t *element )
+{
+ io_data_t *curr, *prev=0;
+ for( curr=list; curr; curr = curr->next )
+ {
+ if( element == curr )
+ {
+ if( prev == 0 )
+ {
+ io_data_t *tmp = element->next;
+ element->next = 0;
+ return tmp;
+ }
+ else
+ {
+ prev->next = element->next;
+ element->next = 0;
+ return list;
+ }
+ }
+ prev = curr;
+ }
+ return list;
+}
+
+io_data_t *io_duplicate( io_data_t *l )
+{
+ io_data_t *res;
+
+ if( l == 0 )
+ return 0;
+
+ res = malloc( sizeof( io_data_t) );
+
+ if( !res )
+ {
+ die_mem();
+
+ }
+
+ memcpy( res, l, sizeof(io_data_t ));
+ res->next=io_duplicate( l->next );
+ return res;
+}
+
+io_data_t *io_get( io_data_t *io, int fd )
+{
+ if( io == 0 )
+ return 0;
+
+ io_data_t *res = io_get( io->next, fd );
+ if( res )
+ return res;
+
+ if( io->fd == fd )
+ return io;
+
+ return 0;
+}
+
+
+/**
+ Recursively free a process and those following it
+*/
+static void free_process( process_t *p )
+{
+ wchar_t **arg;
+
+ if( p==0 )
+ return;
+
+ free_process( p->next );
+ free( p->actual_cmd );
+ if( p->argv != 0 )
+ {
+ for( arg=p->argv; *arg; arg++ )
+ free( *arg );
+ free(p->argv );
+ }
+ free( p );
+}
+
+/** Remove job from list of jobs */
+
+static int job_remove( job_t *j )
+{
+ job_t *prev=0, *curr=first_job;
+ while( (curr != 0) && (curr != j) )
+ {
+ prev = curr;
+ curr = curr->next;
+ }
+
+ if( j != curr )
+ {
+ debug( 1, L"Job inconsistency" );
+ sanity_lose();
+ return 0;
+ }
+
+ if( prev == 0 )
+ first_job = j->next;
+ else
+ prev->next = j->next;
+ return 1;
+}
+
+
+/*
+ Remove job from the job list and free all memory associated with
+ it.
+*/
+void job_free( job_t * j )
+{
+ io_data_t *io, *ionext;
+
+// fwprintf( stderr, L"Remove job %d (%ls)\n", j->job_id, j->command );
+
+ job_remove( j );
+
+ /* Then free ourselves */
+ free_process( j->first_process);
+
+ if( j->command != 0 )
+ free( j->command );
+
+ for( io=j->io; io; io=ionext )
+ {
+ ionext = io->next;
+// fwprintf( stderr, L"Kill redirect %d of type %d\n", ionext, io->io_mode );
+ if( io->io_mode == IO_FILE )
+ {
+ free( io->filename );
+ }
+ free( io );
+ }
+
+ free( j );
+}
+
+void proc_destroy()
+{
+ while( first_job )
+ {
+ debug( 2, L"freeing leaked job %ls", first_job->command );
+ job_free( first_job );
+ }
+}
+
+void proc_set_last_status( int s )
+{
+ last_status = s;
+ wchar_t stat[16];
+ swprintf( stat, 16, L"%d", s );
+ env_set( L"status", stat, ENV_GLOBAL );
+ // fwprintf( stderr, L"Set last status to %d\n", s );
+}
+
+int proc_get_last_status()
+{
+ return last_status;
+}
+
+job_t *job_create()
+{
+ int free_id=0;
+ job_t *res;
+
+ while( job_get( free_id ) != 0 )
+ free_id++;
+ res = calloc( 1, sizeof(job_t) );
+ res->next = first_job;
+ res->job_id = free_id;
+ first_job = res;
+// if( res->job_id > 2 )
+// fwprintf( stderr, L"Create job %d\n", res->job_id );
+ return res;
+}
+
+
+job_t *job_get( int id )
+{
+ job_t *res = first_job;
+ if( id == -1 )
+ {
+ return res;
+ }
+
+ while( res != 0 )
+ {
+ if( res->job_id == id )
+ return res;
+ res = res->next;
+ }
+ return 0;
+}
+
+job_t *job_get_from_pid( int pid )
+{
+ job_t *res = first_job;
+
+ while( res != 0 )
+ {
+ if( res->pgid == pid )
+ return res;
+ res = res->next;
+ }
+ return 0;
+}
+
+
+/*
+ Return true if all processes in the job have stopped or completed.
+*/
+int job_is_stopped( const job_t *j )
+{
+ process_t *p;
+
+ for (p = j->first_process; p; p = p->next)
+ {
+ if (!p->completed && !p->stopped)
+ {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/*
+ Return true if all processes in the job have completed.
+*/
+int job_is_completed( const job_t *j )
+{
+ process_t *p;
+
+ for (p = j->first_process; p; p = p->next)
+ {
+ if (!p->completed)
+ {
+// fwprintf( stderr, L"Process %ls not finished\n", p->argv[0] );
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/**
+ Return true if all processes in the job have completed.
+*/
+static int job_last_is_completed( const job_t *j )
+{
+ process_t *p;
+
+ for (p = j->first_process; p->next; p = p->next)
+ ;
+ return p->completed;
+}
+
+/**
+ Get string representation of a signal
+*/
+static wchar_t *sig2wcs( int sig )
+{
+ switch( sig )
+ {
+ case SIGHUP:
+ return L"SIGHUP";
+ case SIGINT:
+ return L"SIGINT";
+ case SIGQUIT:
+ return L"SIGQUIT";
+ case SIGILL:
+ return L"SIGILL";
+ case SIGTRAP:
+ return L"SIGTRAP";
+ case SIGABRT:
+ return L"SIGABRT";
+ case SIGBUS:
+ return L"SIGBUS";
+ case SIGFPE:
+ return L"SIGFPE";
+ case SIGKILL:
+ return L"SIGKILL";
+ case SIGUSR1:
+ return L"SIGUSR1";
+ case SIGSEGV:
+ return L"SIGSEGV";
+ case SIGUSR2:
+ return L"SIGUSR2";
+ case SIGPIPE:
+ return L"SIGPIPE";
+ case SIGALRM:
+ return L"SIGALRM";
+ case SIGTERM:
+ return L"SIGTERM";
+ case SIGCHLD:
+ return L"SIGCHLD";
+ case SIGCONT:
+ return L"SIGCONT";
+ case SIGSTOP:
+ return L"SIGSTOP";
+ case SIGTSTP:
+ return L"SIGTSTP";
+ case SIGTTIN:
+ return L"SIGTTIN";
+ case SIGTTOU:
+ return L"SIGTTOU";
+ case SIGURG:
+ return L"SIGURG";
+ case SIGXCPU:
+ return L"SIGXCPU";
+ case SIGXFSZ:
+ return L"SIGFXSZ";
+ case SIGVTALRM:
+ return L"SIGVTALRM";
+ case SIGPROF:
+ return L"SIGPROF";
+ case SIGWINCH:
+ return L"SIGWINCH";
+ case SIGIO:
+ return L"SIGIO";
+#ifdef SIGPWR
+ case SIGPWR:
+ return L"SIGPWR";
+#endif
+ case SIGSYS:
+ return L"SIGSYS";
+ default:
+ return L"Unknown";
+ }
+
+}
+
+/**
+ Returns a description of the specified signal.
+*/
+static wchar_t *sig_description( int sig )
+{
+ switch( sig )
+ {
+ case SIGHUP:
+ return L"Terminal hung up";
+ case SIGINT:
+ return L"Quit request from job control (^C)";
+ case SIGQUIT:
+ return L"Quit request from job control with core dump (^\\)";
+ case SIGILL:
+ return L"Illegal instruction";
+ case SIGTRAP:
+ return L"Trace or breakpoint trap";
+ case SIGABRT:
+ return L"Abort";
+ case SIGBUS:
+ return L"Misaligned address error";
+ case SIGFPE:
+ return L"Floating point exception";
+ case SIGKILL:
+ return L"Forced quit";
+ case SIGUSR1:
+ return L"User defined signal 1";
+ case SIGUSR2:
+ return L"User defined signal 2";
+ case SIGSEGV:
+ return L"Address boundary error";
+ case SIGPIPE:
+ return L"Broken pipe";
+ case SIGALRM:
+ return L"Timer expired";
+ case SIGTERM:
+ return L"Polite quit request";
+ case SIGCHLD:
+ return L"Child process status changed";
+ case SIGCONT:
+ return L"Continue previously stopped process";
+ case SIGSTOP:
+ return L"Forced stop";
+ case SIGTSTP:
+ return L"Stop request from job control (^Z)";
+ case SIGTTIN:
+ return L"Stop from terminal input";
+ case SIGTTOU:
+ return L"Stop from terminal output";
+ case SIGURG:
+ return L"Urgent socket condition";
+ case SIGXCPU:
+ return L"CPU time limit exceeded";
+ case SIGXFSZ:
+ return L"File size limit exceeded";
+ case SIGVTALRM:
+ return L"Virtual timer expired";
+ case SIGPROF:
+ return L"Profiling timer expired";
+ case SIGWINCH:
+ return L"Window size change";
+ case SIGIO:
+ return L"Asynchronous IO event";
+#ifdef SIGPWR
+ case SIGPWR:
+ return L"Power failure";
+#endif
+ case SIGSYS:
+ return L"Bad system call";
+ default:
+ return L"Unknown";
+ }
+
+}
+
+
+/**
+ Store the status of the process pid that was returned by waitpid.
+ Return 0 if all went well, nonzero otherwise.
+*/
+static void mark_process_status( job_t *j,
+ process_t *p,
+ int status )
+{
+ p->status = status;
+ if (WIFSTOPPED (status))
+ {
+ p->stopped = 1;
+// fwprintf( stderr, L"Proc %d (%ls) stopped\n", p->pid, p->actual_cmd );
+
+/* sprintf( mess,
+ "%ls (%d): Process stopped\n",
+ j->command,
+ (int) p->pid );
+ write( 2, mess, strlen(mess) );*/
+ }
+ else
+ {
+ p->completed = 1;
+// fwprintf( stderr, L"Proc %d (%ls) exited\n", p->pid, p->actual_cmd );
+
+ if (( !WIFEXITED( status ) ) &&
+ (! WIFSIGNALED( status )) )
+ {
+ /* This should never be reached */
+ char mess[128];
+ sprintf( mess,
+ "Process %d exited abnormally\n",
+ (int) p->pid );
+
+ write( 2, mess, strlen(mess) );
+ }
+ }
+}
+
+/**
+ Handle status update for child \c pid. This function is called by
+ the signal handler, so it mustn't use malloc or any such nonsense.
+*/
+static void handle_child_status( pid_t pid, int status )
+{
+ int found_proc = 0;
+ job_t *j;
+ process_t *p;
+// char mess[MESS_SIZE];
+ found_proc = 0;
+ /*
+ snprintf( mess,
+ MESS_SIZE,
+ "Process %d\n",
+ (int) pid );
+ write( 2, mess, strlen(mess ));
+ */
+
+ for( j=first_job; j && !found_proc; j=j->next )
+ {
+ process_t *prev=0;
+ for( p=j->first_process; p; p=p->next )
+ {
+ if( pid == p->pid )
+ {
+/* snprintf( mess,
+ MESS_SIZE,
+ "Process %d is %ls from job %ls\n",
+ (int) pid, p->actual_cmd, j->command );
+ write( 2, mess, strlen(mess ));
+*/
+
+ mark_process_status ( j, p, status);
+ if( p->completed && prev != 0 )
+ {
+ if( !prev->completed && prev->pid)
+ {
+ /* snprintf( mess,
+ MESS_SIZE,
+ "Kill previously uncompleted process %ls (%d)\n",
+ prev->actual_cmd,
+ prev->pid );
+ write( 2, mess, strlen(mess ));
+ */
+ kill(prev->pid,SIGPIPE);
+ }
+ }
+ found_proc = 1;
+ break;
+ }
+ prev = p;
+ }
+ }
+
+
+ if( !is_interactive )
+ {
+
+ if( WIFSIGNALED( status ) &&
+ ( WTERMSIG(status)==SIGINT ||
+ WTERMSIG(status)==SIGQUIT ) )
+ {
+ struct sigaction act;
+ sigemptyset( & act.sa_mask );
+ act.sa_flags=0;
+ act.sa_handler=SIG_DFL;
+ sigaction( SIGINT, &act, 0 );
+ sigaction( SIGQUIT, &act, 0 );
+ kill( getpid(), WTERMSIG(status) );
+ }
+ }
+
+ if( !found_proc )
+ {
+ /*
+ A child we lost track of?
+
+ There have been bugs in both subshell handling and in
+ builtin handling that have caused this previously...
+ */
+/* snprintf( mess,
+ MESS_SIZE,
+ "Process %d not found by %d\n",
+ (int) pid, (int)getpid() );
+
+ write( 2, mess, strlen(mess ));
+*/
+ }
+ return;
+}
+
+
+void job_handle_signal ( int signal, siginfo_t *info, void *con )
+{
+
+ int status;
+ pid_t pid;
+ int errno_old = errno;
+
+ got_signal = 1;
+
+// write( 2, "got signal\n", 11 );
+
+
+ while(1)
+ {
+ switch(pid=waitpid( -1,&status,WUNTRACED|WNOHANG ))
+ {
+ case 0:
+ case -1:
+ {
+ errno=errno_old;
+ return;
+ }
+ default:
+
+ handle_child_status( pid, status );
+ break;
+ }
+ }
+ kill( 0, SIGIO );
+ errno=errno_old;
+}
+
+/**
+ Format information about job status for the user to look at.
+*/
+static void format_job_info( const job_t *j, const wchar_t *status )
+{
+ fwprintf (stdout, L"\rJob %d, \'%ls\' has %ls", j->job_id, j->command, status);
+ fflush( stdout );
+ tputs(clr_eol,1,&writeb);
+ fwprintf (stdout, L"\n" );
+}
+
+int job_do_notification()
+{
+ job_t *j, *jnext;
+ int found=0;
+
+ for( j=first_job; j; j=jnext)
+ {
+ process_t *p;
+ jnext = j->next;
+
+
+ for( p=j->first_process; p; p=p->next )
+ {
+ if( !p->completed )
+ continue;
+
+ if( p->type )
+ continue;
+
+
+ if( WIFSIGNALED(p->status) )
+ {
+ /*
+ Ignore signal SIGPIPE.We issue it ourselves to the pipe
+ writer when the pipe reader dies.
+ */
+ if( WTERMSIG(p->status) != SIGPIPE )
+ {
+ int proc_is_job = ((p==j->first_process) && (p->next == 0));
+ if( proc_is_job )
+ j->notified = 1;
+ if( !j->skip_notification )
+ {
+ fwprintf( stdout,
+ L"fish: %ls %d, \'%ls\' terminated by signal %ls (%ls)",
+ proc_is_job?L"Job":L"Process",
+ proc_is_job?j->job_id:p->pid,
+ j->command,
+ sig2wcs(WTERMSIG(p->status)),
+ sig_description( 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( !j->fg && !j->notified )
+ {
+ if( !j->skip_notification )
+ {
+ format_job_info (j, L"ended");
+ found=1;
+ }
+ }
+ job_free(j);
+ }
+ else if(job_is_stopped (j) && !j->notified) {
+ /*
+ Notify the user about newly stopped jobs.
+ */
+ if( !j->skip_notification )
+ {
+ format_job_info(j, L"stopped");
+ found=1;
+ }
+ j->notified = 1;
+ }
+ }
+ if( found )
+ fflush( stdout );
+ return found;
+
+}
+
+
+#ifdef HAVE__PROC_SELF_STAT
+/**
+ Get the CPU time for the specified process
+*/
+unsigned long proc_get_jiffies( process_t *p )
+{
+ wchar_t fn[256];
+ //char stat_line[1024];
+
+ char state;
+ int pid, ppid, pgrp,
+ session, tty_nr, tpgid,
+ exit_signal, processor;
+
+ long int cutime, cstime, priority,
+ nice, placeholder, itrealvalue,
+ rss;
+ unsigned long int flags, minflt, cminflt,
+ majflt, cmajflt, utime,
+ stime, starttime, vsize,
+ rlim, startcode, endcode,
+ startstack, kstkesp, kstkeip,
+ signal, blocked, sigignore,
+ sigcatch, wchan, nswap, cnswap;
+ char comm[1024];
+
+ if( p->pid <= 0 )
+ return 0;
+
+ swprintf( fn, 512, L"/proc/%d/stat", p->pid );
+
+ FILE *f = wfopen( fn, "r" );
+ if( !f )
+ return 0;
+
+ int count = fscanf( f,
+ "%d %s %c "
+ "%d %d %d "
+ "%d %d %lu "
+
+ "%lu %lu %lu "
+ "%lu %lu %lu "
+ "%ld %ld %ld "
+
+ "%ld %ld %ld "
+ "%lu %lu %ld "
+ "%lu %lu %lu "
+
+ "%lu %lu %lu "
+ "%lu %lu %lu "
+ "%lu %lu %lu "
+
+ "%lu %d %d ",
+
+ &pid, comm, &state,
+ &ppid, &pgrp, &session,
+ &tty_nr, &tpgid, &flags,
+
+ &minflt, &cminflt, &majflt,
+ &cmajflt, &utime, &stime,
+ &cutime, &cstime, &priority,
+
+ &nice, &placeholder, &itrealvalue,
+ &starttime, &vsize, &rss,
+ &rlim, &startcode, &endcode,
+
+ &startstack, &kstkesp, &kstkeip,
+ &signal, &blocked, &sigignore,
+ &sigcatch, &wchan, &nswap,
+
+ &cnswap, &exit_signal, &processor
+ );
+
+ if( count < 17 )
+ {
+ return 0;
+ }
+ fclose( f );
+ return utime+stime+cutime+cstime;
+
+}
+
+/**
+ Update the CPU time for all jobs
+*/
+void proc_update_jiffies()
+{
+ job_t *j;
+ process_t *p;
+
+ for( j=first_job; j; j=j->next )
+ {
+ for( p=j->first_process; p; p=p->next )
+ {
+ gettimeofday( &p->last_time, 0 );
+ p->last_jiffies = proc_get_jiffies( p );
+ }
+ }
+}
+
+
+#endif
+
+/**
+ Check if there are buffers associated with the job, and select on
+ them for a while if available.
+
+ \return 1 if buffers where avaialble, zero otherwise
+*/
+static int select_try( job_t *j )
+{
+ fd_set fds;
+ int maxfd=-1;
+ io_data_t *d;
+
+
+
+/* if( j->stop_reading )
+ {
+ sleep(1);
+ return;
+ }
+*/
+
+ FD_ZERO(&fds);
+
+ for( d = j->io; d; d=d->next )
+ {
+ if( d->io_mode == IO_BUFFER )
+ {
+ int fd = d->pipe_fd[0];
+// fwprintf( stderr, L"fd %d on job %ls\n", fd, j->command );
+ FD_SET( fd, &fds );
+ maxfd=maxi( maxfd, d->pipe_fd[0] );
+ debug( 3, L"select_try on %d\n", fd );
+ }
+ }
+
+ if( maxfd >= 0 )
+ {
+ int retval;
+ struct timeval tv;
+
+ tv.tv_sec=5;
+ tv.tv_usec=0;
+
+ retval =select( maxfd+1, &fds, 0, 0, &tv );
+ return retval > 0;
+ }
+
+ return -1;
+}
+
+/**
+ Read from descriptors until they are empty.
+*/
+static void read_try( job_t *j )
+{
+ io_data_t *d, *buff=0;
+
+ /*
+ Find the last buffer, which is the one we want to read from
+ */
+ for( d = j->io; d; d=d->next )
+ {
+
+ if( d->io_mode == IO_BUFFER )
+ {
+ buff=d;
+
+ }
+ }
+
+ if( buff )
+ {
+ // fwprintf( stderr, L"proc::read_try('%ls')\n", j->command );
+ while(1)
+ {
+ char b[BUFFER_SIZE];
+ int l;
+ //fwprintf( stderr, L"read...\n");
+ l=read_blocked( buff->pipe_fd[0], b, BUFFER_SIZE );
+ if( l==0 )
+ {
+ break;
+ }
+ else if( l<0 )
+ {
+ if( errno != EAGAIN )
+ {
+ debug( 1,
+ L"An error occured while reading output from code block" );
+ wperror( L"read_try" );
+ }
+
+ break;
+ }
+ else
+ {
+ b_append( buff->out_buffer, b, l );
+ }
+
+ }
+ }
+}
+
+void job_continue (job_t *j, int cont)
+{
+ /*
+ Put job first in the job list
+ */
+ job_remove( j );
+ j->next = first_job;
+ first_job = j;
+ j->notified = 0;
+
+// if( is_interactive )
+ debug( 3,
+ L"Continue on job %d (%ls), %ls, %ls",
+ j->job_id,
+ j->command,
+ job_is_completed( j )?L"COMPLETED":L"UNCOMPLETED",
+ is_interactive?L"INTERACTIVE":L"NON-INTERACTIVE" );
+
+ if( !job_is_completed( j ) )
+ {
+ if( !is_subshell && is_interactive && !is_block)
+ {
+
+ /* Put the job into the foreground. */
+ if( j->fg )
+ {
+ while( 1 )
+ {
+ if( tcsetpgrp (0, j->pgid) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1,
+ L"Could not send job %d ('%ls') to foreground",
+ j->job_id,
+ j->command );
+ wperror( L"tcsetpgrp" );
+ return;
+ }
+ }
+ else
+ break;
+ }
+
+ if( cont )
+ {
+// fwprintf( stderr, L"tcsetattr\n" );
+ while( 1 )
+ {
+ if( tcsetattr (0, TCSADRAIN, &j->tmodes))
+ {
+ if( errno != EINTR )
+ {
+ debug( 1,
+ L"Could not send job %d ('%ls') to foreground",
+ j->job_id,
+ j->command );
+ wperror( L"tcsetattr" );
+ return;
+ }
+ }
+ else
+ break;
+ }
+
+ }
+ }
+ }
+ }
+
+ /*
+ Send the job a continue signal, if necessary.
+ */
+ if( cont )
+ {
+ process_t *p;
+ for( p=j->first_process; p; p=p->next )
+ p->stopped=0;
+ for( p=j->first_process; p; p=p->next )
+ {
+ if (kill ( p->pid, SIGCONT) < 0)
+ {
+ wperror (L"kill (SIGCONT)");
+ return;
+ }
+ }
+ }
+
+ if( j->fg )
+ {
+ int quit = 0;
+
+ /*
+ Wait for job to report. Looks a bit ugly because it has to
+ handle the possibility that a signal is dispatched while
+ running job_is_stopped().
+ */
+ /*
+ fwprintf( stderr, L"Wait for %ls (%d)\n", j->command, j->pgid );
+ */
+ while( !quit )
+ {
+ do
+ {
+ got_signal = 0;
+ quit = job_is_stopped( j ) || job_last_is_completed( j );
+ }
+ while( got_signal && !quit );
+ if( !quit )
+ {
+
+ debug( 3, L"select_try()" );
+ switch( select_try(j) )
+ {
+ case 1:
+ {
+ debug( 3, L"1" );
+ read_try( j );
+ break;
+ }
+
+ case -1:
+ {
+ /*
+ If there is no funky IO magic, we can use
+ waitpid instead of handling child deaths
+ through signals. This gives a rather large
+ speed boost (A factor 3 startup time
+ improvement on my 300 MHz machine) on
+ short-lived jobs.
+ */
+ debug( 3, L"-1" );
+ int status;
+ pid_t pid = waitpid(-1, &status, WUNTRACED );
+ if( pid > 0 )
+ handle_child_status( pid, status );
+ break;
+ }
+
+ }
+ }
+ }
+ }
+
+ if( j->fg )
+ {
+
+ if( job_is_completed( j ))
+ {
+ process_t *p = j->first_process;
+ while( p->next )
+ p = p->next;
+
+ if( WIFEXITED( p->status ) )
+ {
+ /*
+ Mark process status only if we are in the foreground
+ and the last process in a pipe, and it is not a short circuted builtin
+ */
+ if( p->pid )
+ {
+ debug( 3, L"Set status of %ls to %d", j->command, WEXITSTATUS(p->status) );
+ proc_set_last_status( j->negate?(WEXITSTATUS(p->status)?0:1):WEXITSTATUS(p->status) );
+ }
+ }
+
+ }
+ /*
+ Put the shell back in the foreground.
+ */
+ if( !is_subshell && is_interactive && !is_block)
+ {
+
+ while( 1 )
+ {
+
+ if( tcsetpgrp (0, getpid()) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not return shell to foreground" );
+ wperror( L"tcsetpgrp" );
+ return;
+ }
+ }
+ else break;
+ }
+
+ /*
+ Save jobs terminal modes.
+ */
+ while( 1 )
+ {
+ if( tcgetattr (0, &j->tmodes) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not return shell to foreground" );
+ wperror( L"tcgetattr" );
+ return;
+ }
+ }
+ else
+ break;
+ }
+
+ /*
+ Restore the shell's terminal modes.
+ */
+ while( 1 )
+ {
+ if( tcsetattr (0, TCSADRAIN, &shell_modes))
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not return shell to foreground" );
+ wperror( L"tcsetattr" );
+ return;
+ }
+ }
+ else
+ break;
+ }
+ }
+ }
+// fwprintf( stderr, L"Job_continue end\n" );
+}
+
+void proc_sanity_check()
+{
+ job_t *j;
+ job_t *fg_job=0;
+
+ for( j = first_job; j ; j=j->next )
+ {
+ process_t *p;
+
+ if( !j->constructed )
+ continue;
+
+
+ validate_pointer( j->command,
+ L"Job command",
+ 0 );
+ validate_pointer( j->first_process,
+ L"Process list pointer",
+ 0 );
+ validate_pointer( j->next,
+ L"Job list pointer",
+ 1 );
+ validate_pointer( j->command,
+ L"Job command",
+ 0 );
+ /*
+ More than one foreground job?
+ */
+ if( j->fg && !(job_is_stopped(j) || job_last_is_completed(j) ) )
+ {
+ if( fg_job != 0 )
+ {
+ debug( 0,
+ L"More than one job in foreground:\n"
+ L"job 1: %ls\njob 2: %ls",
+ fg_job->command,
+ j->command );
+ sanity_lose();
+ }
+ fg_job = j;
+ }
+
+ p = j->first_process;
+ while( p )
+ {
+ validate_pointer( p->argv, L"Process argument list", 0 );
+ validate_pointer( p->argv[0], L"Process name", 0 );
+ validate_pointer( p->next, L"Process list pointer", 1 );
+ validate_pointer( p->actual_cmd, L"Process command", 1 );
+
+ if ( (p->stopped & (~0x00000001)) != 0 )
+ {
+ debug( 0,
+ L"Job %ls, process %ls "
+ L"has inconsistent state \'stopped\'=%d",
+ j->command,
+ p->argv[0],
+ p->stopped );
+ sanity_lose();
+ }
+
+ if ( (p->completed & (~0x00000001)) != 0 )
+ {
+ debug( 0,
+ L"Job %ls, process %ls "
+ L"has inconsistent state \'completed\'=%d",
+ j->command,
+ p->argv[0],
+ p->completed );
+ sanity_lose();
+ }
+
+ p=p->next;
+ }
+
+ }
+}
+