void exec_cmd ( string const * cmd_orig, ExecCmdCallback func, void * closure, LIST * shell ) { int const slot = get_free_cmdtab_slot(); int const is_raw_cmd = is_raw_command_request( shell ); string cmd_local[ 1 ]; /* Initialize default shell - anything more than /Q/C is non-portable. */ static LIST * default_shell; if ( !default_shell ) default_shell = list_new( object_new( "cmd.exe /Q/C" ) ); /* Specifying no shell means requesting the default shell. */ if ( list_empty( shell ) ) shell = default_shell; if ( DEBUG_EXECCMD ) if ( is_raw_cmd ) out_printf( "Executing raw command directly\n" ); else { out_printf( "Executing using a command file and the shell: " ); list_print( shell ); out_printf( "\n" ); } /* If we are running a raw command directly - trim its leading whitespaces * as well as any trailing all-whitespace lines but keep any trailing * whitespace in the final/only line containing something other than * whitespace). */ if ( is_raw_cmd ) { char const * start = cmd_orig->value; char const * p = cmd_orig->value + cmd_orig->size; char const * end = p; while ( isspace( *start ) ) ++start; while ( p > start && isspace( p[ -1 ] ) ) if ( *--p == '\n' ) end = p; string_new( cmd_local ); string_append_range( cmd_local, start, end ); assert( cmd_local->size == raw_command_length( cmd_orig->value ) ); } /* If we are not running a raw command directly, prepare a command file to * be executed using an external shell and the actual command string using * that command file. */ else { char const * const cmd_file = prepare_command_file( cmd_orig, slot ); char const * argv[ MAXARGC + 1 ]; /* +1 for NULL */ argv_from_shell( argv, shell, cmd_file, slot ); string_new_from_argv( cmd_local, argv ); } /* Catch interrupts whenever commands are running. */ if ( !intr_installed ) { intr_installed = 1; signal( SIGINT, onintr ); } /* Save input data into the selected running commands table slot. */ cmdtab[ slot ].func = func; cmdtab[ slot ].closure = closure; /* Invoke the actual external process using the constructed command line. */ invoke_cmd( cmd_local->value, slot ); /* Free our local command string copy. */ string_free( cmd_local ); }
void exec_cmd ( string const * command, int flags, ExecCmdCallback func, void * closure, LIST * shell ) { struct sigaction ignore, saveintr, savequit; sigset_t chldmask, savemask; int const slot = get_free_cmdtab_slot(); int out[ 2 ]; int err[ 2 ]; int len; char const * argv[ MAXARGC + 1 ]; /* +1 for NULL */ /* Initialize default shell. */ static LIST * default_shell; if ( !default_shell ) default_shell = list_push_back( list_new( object_new( "/bin/sh" ) ), object_new( "-c" ) ); if ( list_empty( shell ) ) shell = default_shell; /* Forumulate argv. If shell was defined, be prepared for % and ! subs. * Otherwise, use stock /bin/sh. */ argv_from_shell( argv, shell, command->value, slot ); if ( DEBUG_EXECCMD ) { int i; out_printf( "Using shell: " ); list_print( shell ); out_printf( "\n" ); for ( i = 0; argv[ i ]; ++i ) out_printf( " argv[%d] = '%s'\n", i, argv[ i ] ); } /* Create pipes for collecting child output. */ if ( pipe( out ) < 0 || ( globs.pipe_action && pipe( err ) < 0 ) ) { perror( "pipe" ); exit( EXITBAD ); } /* Start the command */ timestamp_current( &cmdtab[ slot ].start_dt ); if ( 0 < globs.timeout ) { /* Handle hung processes by manually tracking elapsed time and signal * process when time limit expires. */ struct tms buf; cmdtab[ slot ].start_time = times( &buf ); /* Make a global, only do this once. */ if ( !tps ) tps = sysconf( _SC_CLK_TCK ); } /* Child does not need the read pipe ends used by the parent. */ fcntl( out[ EXECCMD_PIPE_READ ], F_SETFD, FD_CLOEXEC ); if ( globs.pipe_action ) fcntl( err[ EXECCMD_PIPE_READ ], F_SETFD, FD_CLOEXEC ); /* ignore SIGINT and SIGQUIT */ ignore.sa_handler = SIG_IGN; sigemptyset(&ignore.sa_mask); ignore.sa_flags = 0; if (sigaction(SIGINT, &ignore, &saveintr) < 0) return; if (sigaction(SIGQUIT, &ignore, &savequit) < 0) return; /* block SIGCHLD */ sigemptyset(&chldmask); sigaddset(&chldmask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0) return; if ( ( cmdtab[ slot ].pid = vfork() ) == -1 ) { perror( "vfork" ); exit( EXITBAD ); } if ( cmdtab[ slot ].pid == 0 ) { /*****************/ /* Child process */ /*****************/ int const pid = getpid(); /* restore previous signals */ sigaction(SIGINT, &saveintr, NULL); sigaction(SIGQUIT, &savequit, NULL); sigprocmask(SIG_SETMASK, &savemask, NULL); /* Redirect stdout and stderr to pipes inherited from the parent. */ dup2( out[ EXECCMD_PIPE_WRITE ], STDOUT_FILENO ); dup2( globs.pipe_action ? err[ EXECCMD_PIPE_WRITE ] : out[ EXECCMD_PIPE_WRITE ], STDERR_FILENO ); close( out[ EXECCMD_PIPE_WRITE ] ); if ( globs.pipe_action ) close( err[ EXECCMD_PIPE_WRITE ] ); /* Make this process a process group leader so that when we kill it, all * child processes of this process are terminated as well. We use * killpg( pid, SIGKILL ) to kill the process group leader and all its * children. */ if ( 0 < globs.timeout ) { struct rlimit r_limit; r_limit.rlim_cur = globs.timeout; r_limit.rlim_max = globs.timeout; setrlimit( RLIMIT_CPU, &r_limit ); } if (0 != setpgid( pid, pid )) { perror("setpgid(child)"); /* exit( EXITBAD ); */ } execvp( argv[ 0 ], (char * *)argv ); perror( "execvp" ); _exit( 127 ); } /******************/ /* Parent process */ /******************/ /* redundant call, ignore return value */ setpgid(cmdtab[ slot ].pid, cmdtab[ slot ].pid); /* Parent not need the write pipe ends used by the child. */ close( out[ EXECCMD_PIPE_WRITE ] ); if ( globs.pipe_action ) close( err[ EXECCMD_PIPE_WRITE ] ); /* Set both pipe read file descriptors to non-blocking. */ fcntl( out[ EXECCMD_PIPE_READ ], F_SETFL, O_NONBLOCK ); if ( globs.pipe_action ) fcntl( err[ EXECCMD_PIPE_READ ], F_SETFL, O_NONBLOCK ); /* Parent reads from out[ EXECCMD_PIPE_READ ]. */ cmdtab[ slot ].fd[ OUT ] = out[ EXECCMD_PIPE_READ ]; cmdtab[ slot ].stream[ OUT ] = fdopen( cmdtab[ slot ].fd[ OUT ], "rb" ); if ( !cmdtab[ slot ].stream[ OUT ] ) { perror( "fdopen" ); exit( EXITBAD ); } /* Parent reads from err[ EXECCMD_PIPE_READ ]. */ if ( globs.pipe_action ) { cmdtab[ slot ].fd[ ERR ] = err[ EXECCMD_PIPE_READ ]; cmdtab[ slot ].stream[ ERR ] = fdopen( cmdtab[ slot ].fd[ ERR ], "rb" ); if ( !cmdtab[ slot ].stream[ ERR ] ) { perror( "fdopen" ); exit( EXITBAD ); } } GET_WAIT_FD( slot )[ OUT ].fd = out[ EXECCMD_PIPE_READ ]; if ( globs.pipe_action ) GET_WAIT_FD( slot )[ ERR ].fd = err[ EXECCMD_PIPE_READ ]; cmdtab[ slot ].flags = flags; /* Save input data into the selected running commands table slot. */ cmdtab[ slot ].func = func; cmdtab[ slot ].closure = closure; /* restore previous signals */ sigaction(SIGINT, &saveintr, NULL); sigaction(SIGQUIT, &savequit, NULL); sigprocmask(SIG_SETMASK, &savemask, NULL); }