2005-09-20 21:26:39 +08:00
/** \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 .
2012-11-19 08:30:30 +08:00
2005-09-20 21:26:39 +08:00
*/
# 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>
2012-01-31 00:15:02 +08:00
# include <algorithm>
2006-08-10 06:53:38 +08:00
# ifdef HAVE_SYS_TERMIOS_H
# include <sys/termios.h>
# endif
# ifdef HAVE_SYS_IOCTL_H
2005-09-20 21:26:39 +08:00
# include <sys/ioctl.h>
2006-08-10 06:53:38 +08:00
# endif
2005-09-20 21:26:39 +08:00
# 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
2006-01-19 20:22:07 +08:00
# if HAVE_TERM_H
2005-09-20 21:26:39 +08:00
# include <term.h>
2006-01-19 20:22:07 +08:00
# elif HAVE_NCURSES_TERM_H
# include <ncurses/term.h>
# endif
2005-09-20 21:26:39 +08:00
2006-07-31 04:26:59 +08:00
# ifdef HAVE_SIGINFO_H
# include <siginfo.h>
# endif
2006-02-28 21:17:16 +08:00
2006-08-10 06:26:05 +08:00
# ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
# endif
2006-02-28 21:17:16 +08:00
# include "fallback.h"
2005-09-20 21:26:39 +08:00
# include "util.h"
2006-02-28 21:17:16 +08:00
2005-09-20 21:26:39 +08:00
# include "wutil.h"
# include "proc.h"
# include "common.h"
# include "reader.h"
# include "sanity.h"
# include "env.h"
2005-09-23 04:16:52 +08:00
# include "parser.h"
2005-10-06 06:37:08 +08:00
# include "signal.h"
2005-10-12 03:23:43 +08:00
# include "event.h"
2006-07-20 06:55:49 +08:00
2006-02-16 21:36:32 +08:00
# include "output.h"
2005-09-20 21:26:39 +08:00
/**
2012-11-19 08:30:30 +08:00
Size of message buffer
2005-09-20 21:26:39 +08:00
*/
# define MESS_SIZE 256
/**
Size of buffer for reading buffered output
*/
# define BUFFER_SIZE 4096
2012-11-19 08:30:30 +08:00
/**
Status of last process to exit
2006-01-16 08:15:56 +08:00
*/
2005-09-20 21:26:39 +08:00
static int last_status = 0 ;
2006-01-16 08:15:56 +08:00
/**
2012-11-19 08:30:30 +08:00
Signal flag
2006-01-16 08:15:56 +08:00
*/
2005-09-20 21:26:39 +08:00
static sig_atomic_t got_signal = 0 ;
2012-02-28 10:43:24 +08:00
bool job_list_is_empty ( void )
{
ASSERT_IS_MAIN_THREAD ( ) ;
return parser_t : : principal_parser ( ) . job_list ( ) . empty ( ) ;
}
void job_iterator_t : : reset ( )
{
this - > current = job_list - > begin ( ) ;
this - > end = job_list - > end ( ) ;
}
2012-01-30 08:36:21 +08:00
2012-11-19 08:30:30 +08:00
job_iterator_t : : job_iterator_t ( job_list_t & jobs ) : job_list ( & jobs )
{
2012-02-28 10:43:24 +08:00
this - > reset ( ) ;
2012-01-30 08:36:21 +08:00
}
2012-02-28 10:43:24 +08:00
2012-11-19 08:30:30 +08:00
job_iterator_t : : job_iterator_t ( ) : job_list ( & parser_t : : principal_parser ( ) . job_list ( ) )
{
2012-02-28 10:43:24 +08:00
this - > reset ( ) ;
}
2005-09-20 21:26:39 +08:00
int is_interactive_session = 0 ;
int is_subshell = 0 ;
int is_block = 0 ;
int is_login = 0 ;
2005-10-06 06:37:08 +08:00
int is_event = 0 ;
2005-09-20 21:26:39 +08:00
pid_t proc_last_bg_pid = 0 ;
2006-01-31 01:54:26 +08:00
int job_control_mode = JOB_CONTROL_INTERACTIVE ;
2006-03-18 09:04:59 +08:00
int no_exec = 0 ;
2012-02-26 10:54:49 +08:00
static int is_interactive = - 1 ;
2012-04-01 06:33:34 +08:00
static bool proc_had_barrier = false ;
2012-11-19 08:30:30 +08:00
int get_is_interactive ( void )
{
2012-02-26 10:54:49 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
return is_interactive ;
}
2005-09-20 21:26:39 +08:00
2012-11-19 08:30:30 +08:00
bool get_proc_had_barrier ( )
{
2012-04-01 06:33:34 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
return proc_had_barrier ;
}
2012-11-19 08:30:30 +08:00
void set_proc_had_barrier ( bool flag )
{
2012-04-01 06:33:34 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
proc_had_barrier = flag ;
}
2005-10-14 19:40:33 +08:00
/**
2005-12-12 06:21:01 +08:00
The event variable used to send all process event
2005-10-14 19:40:33 +08:00
*/
2012-02-09 11:02:25 +08:00
static event_t event ( 0 ) ;
2005-12-12 06:21:01 +08:00
2006-06-20 08:50:10 +08:00
/**
A stack containing the values of is_interactive . Used by proc_push_interactive and proc_pop_interactive .
*/
2012-07-18 03:47:01 +08:00
static std : : vector < int > interactive_stack ;
2005-10-14 19:40:33 +08:00
void proc_init ( )
{
2012-11-19 08:30:30 +08:00
proc_push_interactive ( 0 ) ;
2012-02-09 11:02:25 +08:00
event . arguments . reset ( new wcstring_list_t ) ;
2005-10-14 19:40:33 +08:00
}
2006-02-09 23:50:20 +08:00
2006-01-16 08:15:56 +08:00
/**
2012-11-19 08:30:30 +08:00
Remove job from list of jobs
2006-01-16 08:15:56 +08:00
*/
2012-11-19 08:30:30 +08:00
static int job_remove ( job_t * j )
2005-09-20 21:26:39 +08:00
{
2012-02-28 10:43:24 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
return parser_t : : principal_parser ( ) . job_remove ( j ) ;
2012-01-30 08:36:21 +08:00
}
void job_promote ( job_t * job )
{
2012-02-28 10:43:24 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
parser_t : : principal_parser ( ) . job_promote ( job ) ;
2005-09-20 21:26:39 +08:00
}
/*
Remove job from the job list and free all memory associated with
it .
*/
2012-11-19 08:30:30 +08:00
void job_free ( job_t * j )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
job_remove ( j ) ;
2012-02-10 11:26:44 +08:00
delete j ;
2005-09-20 21:26:39 +08:00
}
void proc_destroy ( )
{
2012-02-09 11:02:25 +08:00
event . arguments . reset ( NULL ) ;
2012-02-28 10:43:24 +08:00
job_list_t & jobs = parser_t : : principal_parser ( ) . job_list ( ) ;
2012-11-19 08:30:30 +08:00
while ( ! jobs . empty ( ) )
{
job_t * job = jobs . front ( ) ;
debug ( 2 , L " freeing leaked job %ls " , job - > command_wcstr ( ) ) ;
job_free ( job ) ;
}
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
void proc_set_last_status ( int s )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
last_status = s ;
2005-09-20 21:26:39 +08:00
}
int proc_get_last_status ( )
{
2012-11-19 08:30:30 +08:00
return last_status ;
2005-09-20 21:26:39 +08:00
}
2012-02-28 10:43:24 +08:00
/* Basic thread safe job IDs. The vector consumed_job_ids has a true value wherever the job ID corresponding to that slot is in use. The job ID corresponding to slot 0 is 1. */
static pthread_mutex_t job_id_lock = PTHREAD_MUTEX_INITIALIZER ;
static std : : vector < bool > consumed_job_ids ;
job_id_t acquire_job_id ( void )
2005-09-20 21:26:39 +08:00
{
2012-02-28 10:43:24 +08:00
scoped_lock lock ( job_id_lock ) ;
2012-11-19 08:30:30 +08:00
2012-02-28 10:43:24 +08:00
/* Find the index of the first 0 slot */
std : : vector < bool > : : iterator slot = std : : find ( consumed_job_ids . begin ( ) , consumed_job_ids . end ( ) , false ) ;
if ( slot ! = consumed_job_ids . end ( ) )
{
/* We found a slot. Note that slot 0 corresponds to job ID 1. */
* slot = true ;
2012-07-29 08:49:46 +08:00
return ( job_id_t ) ( slot - consumed_job_ids . begin ( ) + 1 ) ;
2012-02-28 10:43:24 +08:00
}
else
{
/* We did not find a slot; create a new slot. The size of the vector is now the job ID (since it is one larger than the slot). */
consumed_job_ids . push_back ( true ) ;
return ( job_id_t ) consumed_job_ids . size ( ) ;
}
}
2006-03-10 21:38:09 +08:00
2012-02-28 10:43:24 +08:00
void release_job_id ( job_id_t jid )
{
assert ( jid > 0 ) ;
scoped_lock lock ( job_id_lock ) ;
size_t slot = ( size_t ) ( jid - 1 ) , count = consumed_job_ids . size ( ) ;
2012-11-19 08:30:30 +08:00
2012-02-28 10:43:24 +08:00
/* Make sure this slot is within our vector and is currently set to consumed */
assert ( slot < count ) ;
assert ( consumed_job_ids . at ( slot ) = = true ) ;
2012-11-19 08:30:30 +08:00
2012-02-28 10:43:24 +08:00
/* Clear it and then resize the vector to eliminate unused trailing job IDs */
consumed_job_ids . at ( slot ) = false ;
2012-11-19 08:30:30 +08:00
while ( count - - )
{
2012-02-28 10:43:24 +08:00
if ( consumed_job_ids . at ( count ) )
break ;
}
consumed_job_ids . resize ( count + 1 ) ;
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
job_t * job_get ( job_id_t id )
2005-09-20 21:26:39 +08:00
{
2012-02-28 10:43:24 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
return parser_t : : principal_parser ( ) . job_get ( id ) ;
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
job_t * job_get_from_pid ( int pid )
2005-09-20 21:26:39 +08:00
{
2012-02-28 10:43:24 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
return parser_t : : principal_parser ( ) . job_get_from_pid ( pid ) ;
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
/*
Return true if all processes in the job have stopped or completed .
2006-01-16 08:15:56 +08:00
\ param j the job to test
2005-09-20 21:26:39 +08:00
*/
2012-11-19 08:30:30 +08:00
int job_is_stopped ( const job_t * j )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
process_t * p ;
for ( p = j - > first_process ; p ; p = p - > next )
{
if ( ! p - > completed & & ! p - > stopped )
{
return 0 ;
}
}
return 1 ;
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
/*
Return true if the last processes in the job has completed .
2006-01-16 08:15:56 +08:00
\ param j the job to test
2005-09-20 21:26:39 +08:00
*/
2012-11-19 08:30:30 +08:00
int job_is_completed ( const job_t * j )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
process_t * p ;
assert ( j - > first_process ! = NULL ) ;
for ( p = j - > first_process ; p - > next ; p = p - > next )
;
return p - > completed ;
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
void job_set_flag ( job_t * j , int flag , int set )
2006-10-26 04:47:59 +08:00
{
2012-11-19 08:30:30 +08:00
if ( set )
j - > flags | = flag ;
else
j - > flags = j - > flags & ( ( unsigned int ) ( - 1 ) ^ flag ) ;
2006-10-26 04:47:59 +08:00
}
2012-11-19 08:30:30 +08:00
int job_get_flag ( const job_t * j , int flag )
2006-10-26 04:47:59 +08:00
{
2012-11-19 08:30:30 +08:00
return j - > flags & flag ? 1 : 0 ;
2006-10-26 04:47:59 +08:00
}
2012-11-19 08:30:30 +08:00
int job_signal ( job_t * j , int signal )
{
pid_t my_pid = getpid ( ) ;
int res = 0 ;
if ( j - > pgid ! = my_pid )
{
res = killpg ( j - > pgid , SIGHUP ) ;
}
else
{
process_t * p ;
for ( p = j - > first_process ; p ; p = p - > next )
{
if ( ! p - > completed )
{
if ( p - > pid )
{
if ( kill ( p - > pid , SIGHUP ) )
{
res = - 1 ;
break ;
}
}
}
}
}
return res ;
2006-11-20 21:12:24 +08:00
}
2006-10-26 04:47:59 +08:00
2005-09-20 21:26:39 +08:00
/**
Store the status of the process pid that was returned by waitpid .
2012-11-19 08:30:30 +08:00
Return 0 if all went well , nonzero otherwise .
2012-11-18 18:16:14 +08:00
This is called from a signal handler .
2005-09-20 21:26:39 +08:00
*/
2012-11-19 08:30:30 +08:00
static void mark_process_status ( const job_t * j ,
process_t * p ,
int status )
2005-09-20 21:26:39 +08:00
{
2006-11-11 18:48:40 +08:00
// debug( 0, L"Process %ls %ls", p->argv[0], WIFSTOPPED (status)?L"stopped":(WIFEXITED( status )?L"exited":(WIFSIGNALED( status )?L"signaled to exit":L"BLARGH")) );
2012-11-19 08:30:30 +08:00
p - > status = status ;
if ( WIFSTOPPED ( status ) )
{
p - > stopped = 1 ;
}
else if ( WIFSIGNALED ( status ) | | WIFEXITED ( status ) )
{
p - > completed = 1 ;
}
else
{
ssize_t ignore ;
/* This should never be reached */
p - > completed = 1 ;
char mess [ MESS_SIZE ] ;
snprintf ( mess ,
MESS_SIZE ,
" Process %ld exited abnormally \n " ,
( long ) p - > pid ) ;
/*
If write fails , do nothing . We ' re in a signal handlers error
handler . If things aren ' t working properly , it ' s safer to
give up .
*/
ignore = write ( 2 , mess , strlen ( mess ) ) ;
}
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
void job_mark_process_as_failed ( const job_t * job , process_t * p )
2012-10-29 16:45:51 +08:00
{
/* The given process failed to even lift off (e.g. posix_spawn failed) and so doesn't have a valid pid. Mark it as dead. */
p - > completed = 1 ;
}
2005-09-20 21:26:39 +08:00
/**
Handle status update for child \ c pid . This function is called by
2006-01-16 08:15:56 +08:00
the signal handler , so it mustn ' t use malloc or any such hitech
nonsense .
\ param pid the pid of the process whose status changes
\ param status the status as returned by wait
2005-09-20 21:26:39 +08:00
*/
2012-11-19 08:30:30 +08:00
static void handle_child_status ( pid_t pid , int status )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
bool found_proc = false ;
const job_t * j = 0 ;
process_t * p = 0 ;
// char mess[MESS_SIZE];
/*
snprintf ( mess ,
MESS_SIZE ,
" Process %d \n " ,
( int ) pid ) ;
write ( 2 , mess , strlen ( mess ) ) ;
*/
2005-09-20 21:26:39 +08:00
2012-01-30 08:36:21 +08:00
job_iterator_t jobs ;
2012-11-19 08:30:30 +08:00
while ( ! found_proc & & ( j = jobs . 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 = true ;
break ;
}
prev = p ;
}
}
if ( WIFSIGNALED ( status ) & &
( WTERMSIG ( status ) = = SIGINT | |
WTERMSIG ( status ) = = SIGQUIT ) )
{
if ( ! is_interactive_session )
{
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 ) ) ;
}
else
{
2012-06-05 05:20:01 +08:00
/* In an interactive session, tell the principal parser to skip all blocks we're executing so control-C returns control to the user. */
2012-11-19 08:30:30 +08:00
if ( p & & found_proc )
{
2012-06-05 05:20:01 +08:00
parser_t : : skip_all_blocks ( ) ;
2012-11-19 08:30:30 +08:00
}
}
}
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 ;
2005-09-20 21:26:39 +08:00
}
2012-08-15 15:57:56 +08:00
process_t : : process_t ( ) :
argv_array ( ) ,
argv0_narrow ( ) ,
type ( 0 ) ,
actual_cmd ( ) ,
pid ( 0 ) ,
pipe_write_fd ( 0 ) ,
pipe_read_fd ( 0 ) ,
completed ( 0 ) ,
stopped ( 0 ) ,
status ( 0 ) ,
count_help_magic ( 0 ) ,
next ( NULL )
# ifdef HAVE__PROC_SELF_STAT
, last_time ( ) ,
last_jiffies ( 0 )
# endif
{
}
2012-11-19 08:30:30 +08:00
2012-08-15 15:57:56 +08:00
process_t : : ~ process_t ( )
{
if ( this - > next ! = NULL )
delete this - > next ;
}
job_t : : job_t ( job_id_t jobid ) :
2012-11-19 08:30:30 +08:00
command_str ( ) ,
command_narrow ( ) ,
first_process ( NULL ) ,
pgid ( 0 ) ,
tmodes ( ) ,
job_id ( jobid ) ,
io ( ) ,
flags ( 0 )
2012-08-15 15:57:56 +08:00
{
}
2012-11-19 08:30:30 +08:00
2012-08-15 15:57:56 +08:00
job_t : : ~ job_t ( )
{
if ( first_process ! = NULL )
delete first_process ;
io_chain_destroy ( this - > io ) ;
release_job_id ( job_id ) ;
}
2005-09-20 21:26:39 +08:00
2012-06-05 05:20:01 +08:00
/* This is called from a signal handler */
2012-11-19 08:30:30 +08:00
void job_handle_signal ( int signal , siginfo_t * info , void * con )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
int status ;
pid_t pid ;
int errno_old = errno ;
got_signal = 1 ;
2005-09-20 21:26:39 +08:00
// write( 2, "got signal\n", 11 );
2012-11-19 08:30:30 +08:00
while ( 1 )
{
switch ( pid = waitpid ( - 1 , & status , WUNTRACED | WNOHANG ) )
{
2012-11-19 16:31:03 +08:00
case 0 :
case - 1 :
{
errno = errno_old ;
return ;
}
default :
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
handle_child_status ( pid , status ) ;
break ;
2012-11-19 08:30:30 +08:00
}
}
kill ( 0 , SIGIO ) ;
errno = errno_old ;
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
/**
Format information about job status for the user to look at .
2006-01-16 08:15:56 +08:00
\ param j the job to test
\ param status a string description of the job exit type
2005-09-20 21:26:39 +08:00
*/
2012-11-19 08:30:30 +08:00
static void format_job_info ( const job_t * j , const wchar_t * status )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
fwprintf ( stdout , L " \r " ) ;
fwprintf ( stdout , _ ( L " Job %d, \' %ls \' has %ls " ) , j - > job_id , j - > command_wcstr ( ) , status ) ;
fflush ( stdout ) ;
tputs ( clr_eol , 1 , & writeb ) ;
fwprintf ( stdout , L " \n " ) ;
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
void proc_fire_event ( const wchar_t * msg , int type , pid_t pid , int status )
2005-10-12 03:23:43 +08:00
{
2012-11-19 08:30:30 +08:00
event . type = type ;
event . param1 . pid = pid ;
2011-12-27 16:06:07 +08:00
event . arguments - > push_back ( msg ) ;
2012-03-06 02:18:42 +08:00
event . arguments - > push_back ( to_string < int > ( pid ) ) ;
event . arguments - > push_back ( to_string < int > ( status ) ) ;
2012-11-19 08:30:30 +08:00
event_fire ( & event ) ;
2011-12-27 16:06:07 +08:00
event . arguments - > resize ( 0 ) ;
2012-11-19 08:30:30 +08:00
}
2005-10-12 03:23:43 +08:00
2012-11-19 08:30:30 +08:00
int job_reap ( bool interactive )
2005-09-20 21:26:39 +08:00
{
2012-02-16 16:24:27 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
2012-11-19 08:30:30 +08:00
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 ;
2012-01-30 08:36:21 +08:00
job_iterator_t jobs ;
jnext = jobs . next ( ) ;
2012-11-19 08:30:30 +08:00
while ( jnext )
2012-01-30 08:36:21 +08:00
{
job_t * j = jnext ;
jnext = jobs . next ( ) ;
2012-11-19 08:30:30 +08:00
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 ;
2005-09-20 21:26:39 +08:00
}
# ifdef HAVE__PROC_SELF_STAT
2006-01-20 22:27:21 +08:00
2006-01-24 04:40:14 +08:00
/**
Maximum length of a / proc / [ PID ] / stat filename
*/
2006-01-20 22:27:21 +08:00
# define FN_SIZE 256
2005-09-20 21:26:39 +08:00
/**
Get the CPU time for the specified process
*/
2012-11-19 08:30:30 +08:00
unsigned long proc_get_jiffies ( process_t * p )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
wchar_t fn [ FN_SIZE ] ;
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 , FN_SIZE , 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 ;
}
/*
Don ' t need to check exit status of fclose on read - only streams
*/
fclose ( f ) ;
return utime + stime + cutime + cstime ;
2005-09-20 21:26:39 +08:00
}
/**
Update the CPU time for all jobs
*/
void proc_update_jiffies ( )
{
2012-11-19 08:30:30 +08:00
job_t * job ;
process_t * p ;
job_iterator_t j ;
for ( job = j . next ( ) ; job ; job = j . next ( ) )
{
for ( p = job - > first_process ; p ; p = p - > next )
{
gettimeofday ( & p - > last_time , 0 ) ;
p - > last_jiffies = proc_get_jiffies ( p ) ;
}
}
2005-09-20 21:26:39 +08:00
}
# endif
/**
Check if there are buffers associated with the job , and select on
2012-11-19 08:30:30 +08:00
them for a while if available .
2006-01-16 08:15:56 +08:00
\ param j the job to test
2005-10-08 17:33:10 +08:00
\ return 1 if buffers were avaialble , zero otherwise
2005-09-20 21:26:39 +08:00
*/
2012-11-19 08:30:30 +08:00
static int select_try ( job_t * j )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
fd_set fds ;
int maxfd = - 1 ;
FD_ZERO ( & fds ) ;
2005-09-20 21:26:39 +08:00
2012-08-15 15:57:56 +08:00
for ( size_t idx = 0 ; idx < j - > io . size ( ) ; idx + + )
2012-11-19 08:30:30 +08:00
{
2012-08-15 15:57:56 +08:00
const io_data_t * d = j - > io . at ( idx ) ;
2012-11-19 08:30:30 +08:00
if ( d - > io_mode = = IO_BUFFER )
{
int fd = d - > param1 . pipe_fd [ 0 ] ;
2005-09-20 21:26:39 +08:00
// fwprintf( stderr, L"fd %d on job %ls\n", fd, j->command );
2012-11-19 08:30:30 +08:00
FD_SET ( fd , & fds ) ;
maxfd = maxi ( maxfd , fd ) ;
debug ( 3 , L " select_try on %d \n " , fd ) ;
}
}
if ( maxfd > = 0 )
{
int retval ;
struct timeval tv ;
tv . tv_sec = 0 ;
tv . tv_usec = 10000 ;
retval = select ( maxfd + 1 , & fds , 0 , 0 , & tv ) ;
return retval > 0 ;
}
return - 1 ;
2005-09-20 21:26:39 +08:00
}
/**
2012-11-19 08:30:30 +08:00
Read from descriptors until they are empty .
2006-01-16 08:15:56 +08:00
\ param j the job to test
2005-09-20 21:26:39 +08:00
*/
2012-11-19 08:30:30 +08:00
static void read_try ( job_t * j )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
io_data_t * buff = NULL ;
2005-09-20 21:26:39 +08:00
2012-11-19 08:30:30 +08:00
/*
Find the last buffer , which is the one we want to read from
*/
2012-08-15 15:57:56 +08:00
for ( size_t idx = 0 ; idx < j - > io . size ( ) ; idx + + )
2012-11-19 08:30:30 +08:00
{
2012-08-15 15:57:56 +08:00
io_data_t * d = j - > io . at ( idx ) ;
2012-11-19 08:30:30 +08:00
if ( d - > io_mode = = IO_BUFFER )
{
buff = d ;
}
}
if ( buff )
{
debug ( 3 , L " proc::read_try('%ls') \n " , j - > command_wcstr ( ) ) ;
while ( 1 )
{
char b [ BUFFER_SIZE ] ;
long l ;
l = read_blocked ( buff - > param1 . 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
{
2012-03-04 18:35:30 +08:00
buff - > out_buffer_append ( b , l ) ;
2012-11-19 08:30:30 +08:00
}
}
}
2005-09-20 21:26:39 +08:00
}
2006-01-16 08:15:56 +08:00
2008-01-09 09:23:38 +08:00
/**
2012-11-19 08:30:30 +08:00
Give ownership of the terminal to the specified job .
2008-01-09 09:23:38 +08:00
\ param j The job to give the terminal to .
\ param cont If this variable is set , we are giving back control to
a job that has previously been stopped . In that case , we need to
set the terminal attributes to those saved in the job .
*/
2012-11-19 08:30:30 +08:00
static int terminal_give_to_job ( job_t * j , int cont )
2006-11-11 18:48:40 +08:00
{
2012-11-19 08:30:30 +08:00
if ( tcsetpgrp ( 0 , j - > pgid ) )
{
debug ( 1 ,
_ ( L " Could not send job %d ('%ls') to foreground " ) ,
j - > job_id ,
j - > command_wcstr ( ) ) ;
wperror ( L " tcsetpgrp " ) ;
return 0 ;
}
if ( cont )
{
if ( tcsetattr ( 0 , TCSADRAIN , & j - > tmodes ) )
{
debug ( 1 ,
_ ( L " Could not send job %d ('%ls') to foreground " ) ,
j - > job_id ,
j - > command_wcstr ( ) ) ;
wperror ( L " tcsetattr " ) ;
return 0 ;
}
}
return 1 ;
2006-11-11 18:48:40 +08:00
}
/**
2008-01-09 09:23:38 +08:00
Returns contol of the terminal to the shell , and saves the terminal
attribute state to the job , so that we can restore the terminal
2012-11-19 08:30:30 +08:00
ownership to the job at a later time .
2006-11-11 18:48:40 +08:00
*/
2012-11-19 08:30:30 +08:00
static int terminal_return_from_job ( job_t * j )
2006-11-11 18:48:40 +08:00
{
2012-11-19 08:30:30 +08:00
if ( tcsetpgrp ( 0 , getpgrp ( ) ) )
{
debug ( 1 , _ ( L " Could not return shell to foreground " ) ) ;
wperror ( L " tcsetpgrp " ) ;
return 0 ;
}
/*
Save jobs terminal modes .
*/
if ( tcgetattr ( 0 , & j - > tmodes ) )
{
debug ( 1 , _ ( L " Could not return shell to foreground " ) ) ;
wperror ( L " tcgetattr " ) ;
return 0 ;
}
/* Disabling this per https://github.com/adityagodbole/fish-shell/commit/9d229cd18c3e5c25a8bd37e9ddd3b67ddc2d1b72
On Linux , ' cd . ; ftp ' prevents you from typing into the ftp prompt
See https : //github.com/fish-shell/fish-shell/issues/121
*/
2012-10-06 09:23:38 +08:00
#if 0
2012-11-19 08:30:30 +08:00
/*
Restore the shell ' s terminal modes .
*/
if ( tcsetattr ( 0 , TCSADRAIN , & shell_modes ) )
{
debug ( 1 , _ ( L " Could not return shell to foreground " ) ) ;
wperror ( L " tcsetattr " ) ;
return 0 ;
}
2012-10-06 09:23:38 +08:00
# endif
2006-11-11 18:48:40 +08:00
2012-11-19 08:30:30 +08:00
return 1 ;
2006-11-11 18:48:40 +08:00
}
2012-11-19 08:30:30 +08:00
void job_continue ( job_t * j , int cont )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
/*
Put job first in the job list
*/
2012-01-30 08:36:21 +08:00
job_promote ( j ) ;
2012-11-19 08:30:30 +08:00
job_set_flag ( j , JOB_NOTIFIED , 0 ) ;
CHECK_BLOCK ( ) ;
debug ( 4 ,
L " Continue job %d, gid %d (%ls), %ls, %ls " ,
j - > job_id ,
j - > pgid ,
j - > command_wcstr ( ) ,
job_is_completed ( j ) ? L " COMPLETED " : L " UNCOMPLETED " ,
is_interactive ? L " INTERACTIVE " : L " NON-INTERACTIVE " ) ;
if ( ! job_is_completed ( j ) )
{
if ( job_get_flag ( j , JOB_TERMINAL ) & & job_get_flag ( j , JOB_FOREGROUND ) )
{
/* Put the job into the foreground. */
int ok ;
signal_block ( ) ;
ok = terminal_give_to_job ( j , cont ) ;
signal_unblock ( ) ;
if ( ! ok )
return ;
}
/*
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 ;
if ( job_get_flag ( j , JOB_CONTROL ) )
{
if ( killpg ( j - > pgid , SIGCONT ) )
{
wperror ( L " killpg (SIGCONT) " ) ;
return ;
}
}
else
{
for ( p = j - > first_process ; p ; p = p - > next )
{
if ( kill ( p - > pid , SIGCONT ) < 0 )
{
wperror ( L " kill (SIGCONT) " ) ;
return ;
}
}
}
}
if ( job_get_flag ( j , JOB_FOREGROUND ) )
{
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 ( ) .
*/
while ( ! quit )
{
do
{
got_signal = 0 ;
quit = job_is_stopped ( j ) | | job_is_completed ( j ) ;
}
while ( got_signal & & ! quit ) ;
if ( ! quit )
{
// debug( 1, L"select_try()" );
switch ( select_try ( j ) )
{
2012-11-19 16:31:03 +08:00
case 1 :
2012-11-19 08:30:30 +08:00
{
2012-11-19 16:31:03 +08:00
read_try ( j ) ;
break ;
2012-11-19 08:30:30 +08:00
}
2012-11-19 16:31:03 +08:00
case - 1 :
2012-11-19 08:30:30 +08:00
{
/*
2012-11-19 16:31:03 +08:00
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 .
2012-11-19 08:30:30 +08:00
*/
2012-11-19 16:31:03 +08:00
int status ;
pid_t pid = waitpid ( - 1 , & status , WUNTRACED ) ;
if ( pid > 0 )
2012-11-19 08:30:30 +08:00
{
2012-11-19 16:31:03 +08:00
handle_child_status ( pid , status ) ;
2012-11-19 08:30:30 +08:00
}
2012-11-19 16:31:03 +08:00
else
{
/*
This probably means we got a
signal . A signal might mean that the
terminal emulator sent us a hup
signal to tell is to close . If so ,
we should exit .
*/
if ( reader_exit_forced ( ) )
{
quit = 1 ;
}
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
}
break ;
2012-11-19 08:30:30 +08:00
}
}
}
}
}
}
if ( job_get_flag ( j , JOB_FOREGROUND ) )
{
if ( job_is_completed ( j ) )
{
// It's possible that the job will produce output and exit before we've even read from it.
// We'll eventually read the output, but it may be after we've executed subsequent calls
// This is why my prompt colors kept getting screwed up - the builtin echo calls
// were sometimes having their output combined with the set_color calls in the wrong order!
read_try ( j ) ;
process_t * p = j - > first_process ;
while ( p - > next )
p = p - > next ;
if ( WIFEXITED ( p - > status ) | | WIFSIGNALED ( 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 )
{
int status = proc_format_status ( p - > status ) ;
//wprintf(L"setting status %d for %ls\n", job_get_flag( j, JOB_NEGATE )?!status:status, j->command);
proc_set_last_status ( job_get_flag ( j , JOB_NEGATE ) ? ! status : status ) ;
}
}
}
/*
Put the shell back in the foreground .
*/
if ( job_get_flag ( j , JOB_TERMINAL ) & & job_get_flag ( j , JOB_FOREGROUND ) )
{
int ok ;
signal_block ( ) ;
ok = terminal_return_from_job ( j ) ;
signal_unblock ( ) ;
if ( ! ok )
return ;
}
}
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
int proc_format_status ( int status )
2009-02-22 00:46:56 +08:00
{
2012-11-19 08:30:30 +08:00
if ( WIFSIGNALED ( status ) )
{
return 128 + WTERMSIG ( status ) ;
}
else if ( WIFEXITED ( status ) )
{
return WEXITSTATUS ( status ) ;
}
return status ;
2009-02-22 00:46:56 +08:00
}
2005-09-20 21:26:39 +08:00
void proc_sanity_check ( )
{
2012-11-19 08:30:30 +08:00
job_t * j ;
job_t * fg_job = 0 ;
2012-01-30 08:36:21 +08:00
job_iterator_t jobs ;
while ( ( j = jobs . next ( ) ) )
2012-11-19 08:30:30 +08:00
{
process_t * p ;
if ( ! job_get_flag ( j , JOB_CONSTRUCTED ) )
continue ;
validate_pointer ( j - > first_process ,
_ ( L " Process list pointer " ) ,
0 ) ;
/*
More than one foreground job ?
*/
if ( job_get_flag ( j , JOB_FOREGROUND ) & & ! ( job_is_stopped ( j ) | | job_is_completed ( j ) ) )
{
if ( fg_job ! = 0 )
{
debug ( 0 ,
_ ( L " More than one job in foreground: job 1: '%ls' job 2: '%ls' " ) ,
fg_job - > command_wcstr ( ) ,
j - > command_wcstr ( ) ) ;
sanity_lose ( ) ;
}
fg_job = j ;
}
p = j - > first_process ;
while ( p )
{
validate_pointer ( p - > get_argv ( ) , _ ( L " Process argument list " ) , 0 ) ;
validate_pointer ( p - > argv0 ( ) , _ ( L " Process name " ) , 0 ) ;
validate_pointer ( p - > next , _ ( L " Process list pointer " ) , 1 ) ;
if ( ( p - > stopped & ( ~ 0x00000001 ) ) ! = 0 )
{
debug ( 0 ,
_ ( L " Job '%ls', process '%ls' has inconsistent state \' stopped \' =%d " ) ,
j - > command_wcstr ( ) ,
p - > argv0 ( ) ,
p - > stopped ) ;
sanity_lose ( ) ;
}
if ( ( p - > completed & ( ~ 0x00000001 ) ) ! = 0 )
{
debug ( 0 ,
_ ( L " Job '%ls', process '%ls' has inconsistent state \' completed \' =%d " ) ,
j - > command_wcstr ( ) ,
p - > argv0 ( ) ,
p - > completed ) ;
sanity_lose ( ) ;
}
p = p - > next ;
}
}
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
void proc_push_interactive ( int value )
2006-02-16 21:36:32 +08:00
{
2012-02-26 10:54:49 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
2012-11-19 08:30:30 +08:00
int old = is_interactive ;
2012-02-10 11:26:44 +08:00
interactive_stack . push_back ( is_interactive ) ;
2012-11-19 08:30:30 +08:00
is_interactive = value ;
if ( old ! = value )
signal_set_handlers ( ) ;
2006-02-16 21:36:32 +08:00
}
void proc_pop_interactive ( )
{
2012-02-26 10:54:49 +08:00
ASSERT_IS_MAIN_THREAD ( ) ;
2012-11-19 08:30:30 +08:00
int old = is_interactive ;
is_interactive = interactive_stack . back ( ) ;
2012-02-10 11:26:44 +08:00
interactive_stack . pop_back ( ) ;
2012-11-19 08:30:30 +08:00
if ( is_interactive ! = old )
signal_set_handlers ( ) ;
2006-02-16 21:36:32 +08:00
}