/* pr.c -- Process creation and tracking support * Created: Sun Jan 7 13:34:08 1996 by faith@dict.org * Revised: Sat Mar 30 12:06:35 2002 by faith@dict.org * Copyright 1996, 2002 Rickard E. Faith (faith@dict.org) * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: pr.c,v 1.11 2002/08/02 19:43:15 faith Exp $ * * \section{Process Management Routines} * * \intro The process management routines are designed to facilitate the * creation and management of child processes, coprocesses, and associated * pipelines and I/O. Some support for daemons and socket connections is * also provided. * */ #include "maaP.h" #include #include #include #if HAVE_SYS_PARAM_H # include #endif #if HAVE_SYS_WAIT_H # include #endif #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif #ifndef WIFEXITED # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif typedef struct _pr_Obj { int pid; } *_pr_Obj; static _pr_Obj _pr_objects = NULL; #if 0 static void _pr_check( pr_Object object, const char *function ) { Obj o = (Obj)object; if (!o) err_internal( function, "object is null\n" ); #if MAA_MAGIC if (o->magic != PR_MAGIC) err_internal( function, "Magic match failed: 0x%08x (should be 0x%08x)\n", o->magic, PR_MAGIC ); #endif } #endif /* The idea for the max_fd call is from W. Richard Stevens; Advanced Programming in the UNIX Environment (Addison-Wesley Publishing Co., 1992); page 43. The implementation here, however, is different from that provided by Stevens for his open_max routine. */ static int max_fd( void ) { static int maxFd = 0; if (maxFd) return maxFd; #if HAVE_SYSCONF if ((maxFd = sysconf( _SC_OPEN_MAX )) > 0) return maxFd; #endif #ifdef NOFILE return maxFd = NOFILE; /* Usually in sys/params.h */ #else # ifdef _NFILE return maxFd = _NFILE; # else return maxFd = 256; /* best guess */ # endif #endif } static void _pr_init( void ) { if (!_pr_objects) _pr_objects = xcalloc( max_fd(), sizeof( struct _pr_Obj ) ); } void _pr_shutdown( void ) { int i; if (_pr_objects) { for (i = 0; i < max_fd(); i++) { /* FIXME: blah */; if (_pr_objects[i].pid) { kill( _pr_objects[i].pid, SIGKILL ); /* FIXME! be gentler. */ pr_wait( _pr_objects[i].pid ); _pr_objects[i].pid = 0; } } xfree( _pr_objects ); _pr_objects = NULL; } } int pr_open( const char *command, int flags, int *infd, int *outfd, int *errfd) { int pid; int fdin[2]; int fdout[2]; int fderr[2]; arg_List list; int argc; char **argv; int null; _pr_init(); if (flags & ~(PR_USE_STDIN | PR_USE_STDOUT | PR_USE_STDERR | PR_CREATE_STDIN | PR_CREATE_STDOUT | PR_CREATE_STDERR | PR_STDERR_TO_STDOUT)) err_internal( __FUNCTION__, "Illegal flags: 0x%08x\n", flags ); if ((flags & PR_USE_STDIN) && (flags & PR_CREATE_STDIN)) err_internal( __FUNCTION__, "Cannot both use and create stdin\n" ); if ((flags & PR_USE_STDOUT) && (flags & PR_CREATE_STDOUT)) err_internal( __FUNCTION__, "Cannot both use and create stdout\n" ); if ((flags & PR_USE_STDERR) && (flags & PR_CREATE_STDERR)) err_internal( __FUNCTION__, "Cannot both use and create stderr\n" ); if ((flags & PR_STDERR_TO_STDOUT) && ((flags & PR_USE_STDERR) || (flags & PR_CREATE_STDERR))) err_internal( __FUNCTION__, "Cannot use/create stderr when duping to stdout\n" ); list = arg_argify( command, 0 ); arg_get_vector( list, &argc, &argv ); PRINTF(MAA_PR,("Execing %s with \"%s\"\n", argv[0], command )); if ((flags & PR_CREATE_STDIN) && pipe( fdin ) < 0) err_fatal_errno( __FUNCTION__, "Cannot create pipe for stdin\n" ); if ((flags & PR_CREATE_STDOUT) && pipe( fdout ) < 0) err_fatal_errno( __FUNCTION__, "Cannot create pipe for stdout\n" ); if ((flags & PR_CREATE_STDERR) && pipe( fderr ) < 0) err_fatal_errno( __FUNCTION__, "Cannot create pipe for stderr\n" ); if ((pid = fork()) < 0) err_fatal_errno( __FUNCTION__, "Cannot fork\n" ); if (pid == 0) { /* child */ int i; #define CHILD(CREATE,USE,fds,writefd,readfd,fd,FILENO,flag) \ if (flags & CREATE) { \ close( fds[writefd] ); \ dup2( fds[readfd], FILENO ); \ close( fds[readfd] ); \ } else if (flags & USE) { \ if (fd && *fd) { \ dup2( *fd, FILENO ); \ close( *fd ); \ } else { \ if ((null = open( "/dev/null", flag )) >= 0) { \ dup2( null, FILENO ); \ close( null ); \ } \ } \ } CHILD( PR_CREATE_STDIN, PR_USE_STDIN, fdin, 1, 0, infd, STDIN_FILENO, O_RDONLY ); CHILD( PR_CREATE_STDOUT, PR_USE_STDOUT, fdout, 0, 1, outfd, STDOUT_FILENO, O_WRONLY ); CHILD( PR_CREATE_STDERR, PR_USE_STDERR, fderr, 0, 1, errfd, STDERR_FILENO, O_WRONLY ); #undef CHILD if (flags & PR_STDERR_TO_STDOUT) dup2( STDOUT_FILENO, STDERR_FILENO ); for (i = 0; i < max_fd(); i++) if (_pr_objects[i].pid > 0) close( i ); execvp( argv[0], argv ); _exit(127); } /* parent */ #define PARENT(CREATE,USE,fds,readfd,writefd,fd,flag,name) \ if (flags & CREATE) { \ close( fds[ readfd ] ); \ *fd = fds[ writefd ]; \ _pr_objects[ *fd ].pid = pid; \ PRINTF(MAA_PR,(name " = %d; ",*fd)); \ } else if (flags & USE) { \ if (fd && *fd) { \ PRINTF(MAA_PR,(name " = %d*; ",*fd)); \ _pr_objects[ *fd ].pid =0; \ close( *fd ); \ } \ } PARENT( PR_CREATE_STDIN, PR_USE_STDIN, fdin, 0, 1, infd, "w", "stdin" ); PARENT( PR_CREATE_STDOUT, PR_USE_STDOUT, fdout, 1, 0, outfd, "r", "stdout" ); PARENT( PR_CREATE_STDERR, PR_USE_STDERR, fderr, 1, 0, errfd, "r", "stderr" ); #undef PARENT PRINTF(MAA_PR,("child pid = %d\n",pid)); arg_destroy( list ); return pid; } int pr_wait( int pid ) { int exitStatus = 0; int status; PRINTF(MAA_PR,("waiting on pid %d\n",pid)); while (waitpid( pid, &status, 0 ) < 0) { if (errno != EINTR) { if (errno == ECHILD) return 0; /* We've already waited */ /* This is really bad... */ PRINTF(MAA_PR,("waitpid() < 0, errno = %d\n", errno )); perror( __FUNCTION__ ); return -1; } } if (WIFEXITED( status )) exitStatus |= WEXITSTATUS( status ); /* SIGPIPE is ok here, since tar may shutdown early. Anything else is a problem. */ if (WIFSIGNALED( status ) && WTERMSIG( status ) != SIGPIPE) exitStatus |= 128 + WTERMSIG( status ); /* like bash :-) */ PRINTF(MAA_PR,("Child %d exited with status 0x%04x\n",pid,exitStatus)); return exitStatus; } int pr_close_nowait( int fd ) { int pid; if (!_pr_objects) err_internal( __FUNCTION__, "No previous call to pr_open()\n" ); if (!(pid = _pr_objects[ fd ].pid)) err_internal( __FUNCTION__, "File (%d) not created by pr_open()\n", fd ); _pr_objects[ fd ].pid = 0; close( fd ); return pid; } int pr_close( int fd ) { int pid = pr_close_nowait( fd ); return pr_wait( pid ); } int pr_readwrite( int in, int out, const char *inBuffer, int inLen, char *outBuffer, int outMaxLen ) { long flags; const char *inPt = inBuffer; char *outPt = outBuffer; int outLen = 0; fd_set rfds, wfds, efds; struct timeval tv; int n; int count; int retval; int status; if ((flags = fcntl( in, F_GETFL )) < 0) err_fatal_errno( __FUNCTION__, "Can't get flags for output stream\n" ); #ifdef O_NONBLOCK flags |= O_NONBLOCK; #else flags |= FNDELAY; #endif fcntl( in, F_SETFL, flags ); if ((flags = fcntl( out, F_GETFL )) < 0) err_fatal_errno( __FUNCTION__, "Can't get flags for input stream\n" ); #ifdef O_NONBLOCK flags |= O_NONBLOCK; #else flags |= FNDELAY; #endif fcntl( out, F_SETFL, flags ); n = max( in, out ) + 1; for (;;) { tv.tv_sec = 5; tv.tv_usec = 0; FD_ZERO( &rfds ); FD_ZERO( &wfds ); FD_ZERO( &efds ); FD_SET( out, &rfds ); FD_SET( out, &efds ); if (inLen) { FD_SET( in, &wfds ); FD_SET( in, &efds ); } switch ((retval = select( n, &rfds, &wfds, &efds, &tv ))) { case -1: err_fatal_errno( __FUNCTION__, "Filter failed\n" ); break; /* case 0: err_fatal( __FUNCTION__, "Filter hung\n" ); break; */ default: if (dbg_test(MAA_PR)) { printf( "select(2) returns %d," " inLen = %d, outLen = %d, outMaxLen = %d\n", retval, inLen, outLen, outMaxLen ); if (FD_ISSET( in, &rfds )) printf( " in/read\n" ); if (FD_ISSET( out, &rfds )) printf( " out/read\n" ); if (FD_ISSET( in, &wfds )) printf( " in/write\n" ); if (FD_ISSET( out, &wfds )) printf( " out/write\n" ); if (FD_ISSET( in, &efds )) printf( " in/error\n" ); if (FD_ISSET( out, &efds )) printf( " out/error\n" ); } if (inLen) { if ((count = write( in, inPt, inLen )) <= 0) { if (errno != EAGAIN) err_fatal_errno( __FUNCTION__, "Error writing to filter\n" ); } else { PRINTF(MAA_PR,(" wrote %d\n",count)); inLen -= count; inPt += count; if (!inLen) { pr_close_nowait( in ); n = out + 1; } } } if ((count = read( out, outPt, outMaxLen )) <= 0) { if (!count) { if (inLen) err_fatal( __FUNCTION__, "End of output, but input not flushed\n" ); if ((status = pr_close( out ))) err_warning( __FUNCTION__, "Filter had non-zero exit status: 0x%x\n", status ); return outLen; } else if (errno != EAGAIN) err_fatal_errno( __FUNCTION__, "Error reading from filter\n" ); } else { PRINTF(MAA_PR,(" read %d\n",count)); outLen += count; outPt += count; if ((outMaxLen -= count) < 0) err_fatal( __FUNCTION__, "Output buffer overflow\n" ); } break; } } } int pr_filter( const char *command, const char *inBuffer, int inLen, char *outBuffer, int outMaxLen ) { int pid; int in, out; pid = pr_open( command, PR_CREATE_STDIN | PR_CREATE_STDOUT, &in, &out, NULL ); return pr_readwrite( in, out, inBuffer, inLen, outBuffer, outMaxLen ); } int pr_spawn( const char *command ) { arg_List list; int argc; char **argv; int pid; int status; int exitStatus = 0; _pr_init(); list = arg_argify( command, 0 ); arg_get_vector( list, &argc, &argv ); PRINTF(MAA_PR,("Execing %s with \"%s\"\n", argv[0], command )); if ((pid = fork()) < 0) err_fatal_errno( __FUNCTION__, "Cannot fork\n" ); if (pid == 0) { /* child */ execvp( argv[0], argv ); _exit(127); } /* parent */ PRINTF(MAA_PR,("child pid = %d\n",pid)); arg_destroy( list ); PRINTF(MAA_PR,("waiting on pid %d\n",pid)); while (waitpid( pid, &status, 0 ) < 0) { if (errno != EINTR) { if (errno == ECHILD) return 0; /* We've already waited */ /* This is really bad... */ PRINTF(MAA_PR,("waitpid() < 0, errno = %d\n", errno )); perror( __FUNCTION__ ); return -1; } } if (WIFEXITED( status )) exitStatus |= WEXITSTATUS( status ); /* SIGPIPE is ok here, since tar may shutdown early. Anything else is a problem. */ if (WIFSIGNALED( status ) && WTERMSIG( status ) != SIGPIPE) exitStatus |= 128 + WTERMSIG( status ); /* like bash :-) */ PRINTF(MAA_PR,("Child %d exited with status 0x%04x\n",pid,exitStatus)); return exitStatus; }