home *** CD-ROM | disk | FTP | other *** search
- /*
- * popen/pclose: simple MS-DOS piping scheme to imitate UNIX pipes
- *
- * [Obtained from SIMTEL20 as popen.arc - no author name on it
- * Contains a lot of debugging code, which I left in.]
- * #define DEBUG to get chatty output.
- *
- * Revision record:
- * 08/10/90 RB
- * - Added checks on length of generated command line before attempting to run
- * - Set O_DENYNONE mode on stdin/stdout before running command
- * - Modified naming the pipe file to include PID to guard against nested execution
- *
- * 03/12/90 RB
- * - Changed strsave/strfree to standard strdup/free
- * - Added #ifdef SWITCH to force switchar to / before running shell
- * - Added set_popen_shell() and set_popen_exec() to select method
- *
- * 10/5/89 RB
- * - Changed check for MKS ksh into explicit check for ksh, as opposed to
- * anything other than command.com (I use 4dos)
- * - Check for trailing "/" or "\" on $TMP - do not add another
- * - Added a simpler version of run() that does not need COMSPEC or getswitch()
- * and executes the program directly via spawnvp(). It will work for all but
- * .BAT files or internal commands
- * - Added my own versions of dup() and dup2()
- * - Changed function declarations to prototypes
- * - Added close ostdout/ostdin (original left them open, using up handles)
- */
-
- #include <stdio.h>
- #include <ctype.h>
- #include <alloc.h>
- #include <string.h>
- #include <errno.h>
- #include <setjmp.h>
- #include <process.h>
- #include <io.h>
- #include <dos.h>
- #include <fcntl.h>
-
- #include "popen.h"
-
- extern char *getenv( char * );
- extern int getswitch(void);
- extern int setswitch(char);
-
- #ifndef _NFILE
- #define _NFILE OPEN_MAX /* Number of open files */
- #endif _NFILE
-
- #define READIT 1 /* Read pipe */
- #define WRITEIT 2 /* Write pipe */
- #define MAXARGLINE 127 /* max length of an msdos command line */
- #define MAX_ARGS 64 /* max number of separate arguments */
- #define TRUE 1
- #define FALSE 0
-
- static char *prgname[ _NFILE ]; /* program name if write pipe */
- static int pipetype[ _NFILE ]; /* 1=read 2=write */
- static char *pipename[ _NFILE ]; /* pipe file name */
- static int use_shell = TRUE; /* flag for type of exec */
-
- /*
- *------------------------------------------------------------------------
- * run: Execute command via SHELL or COMSPEC. This version will run .EXE
- * .COM .BAT or internal commands of the shell program, also pipelines.
- *------------------------------------------------------------------------
- */
- static int
- runshell( char *command )
- {
- jmp_buf panic; /* How to recover from errors */
- int lineno; /* Line number where panic happened */
- char *shell; /* Command processor */
- char *s = (char *) NULL; /* Holds the command */
- int s_is_malloced = 0; /* True if need to free 's' */
- static char *command_com = "COMMAND.COM";
- int status; /* Return codes */
- char *shellpath; /* Full command processor path */
- char *bp; /* Generic string pointer */
- char saveswitch; /* Save the switch char */
- static char dash_c[ 3 ] = { '?', 'c', '\0' };
- if( (lineno = setjmp( panic )) != 0 ) {
- int E = errno;
- #ifdef DEBUG
- fprintf( stderr, "RUN panic on line %d: %d\n", lineno, E );
- #endif DEBUG
- if( s_is_malloced && (s != (char *) NULL) ) free( s );
- errno = E;
- return( -1 );
- }
- if( (s = strdup( command )) == (char *) NULL ) longjmp( panic, __LINE__ );
-
- /* Determine the command processor - use SHELL and then COMSPEC */
- if( ((shell = getenv( "SHELL" )) == (char *) NULL) &&
- ((shell = getenv( "COMSPEC" )) == (char *) NULL) ) shell = command_com;
- strupr( shell );
- shellpath = shell;
-
- /* Strip off any leading backslash directories */
- shell = strrchr( shellpath, '\\' );
- if( shell != (char *) NULL ) ++shell;
- else shell = shellpath;
-
- /* Strip off any leading slash directories */
- bp = strrchr( shell, '/' );
- if( bp != (char *) NULL ) shell = ++bp;
- if( strstr( shell, "KSH" ) != NULL ) {
- /* MKS Shell needs quoted argument */
- char *bp;
- if( (bp = s = malloc( strlen( command ) + 3 )) == (char *) NULL )
- longjmp( panic, __LINE__ );
- *bp++ = '\'';
- while( (*bp++ = *command++) != '\0' );
- *(bp - 1) = '\'';
- *bp = '\0';
- s_is_malloced = 1;
- } else s = command;
- saveswitch = dash_c[ 0 ] = (char) getswitch();
- #ifdef SWITCH /* some shells will not work without '/' so force it here */
- setswitch('/');
- dash_c[ 0 ] = '/';
- #endif
- /* Test the length of the generated command line - if too long, fail */
- if (strlen(s) + strlen(shell) + 4 > MAXARGLINE) {
- #ifdef DEBUG
- fprintf(stderr,"popen: command line too long\n");
- #endif DEBUG
- errno = E2BIG;
- return(-1);
- }
- /* Run the program */
- #ifdef DEBUG
- fprintf( stderr, "Running: (%s) %s %s %s\n", shellpath, shell, dash_c, s );
- #endif DEBUG
- status = spawnl( P_WAIT, shellpath, shell, dash_c, s, (char *) NULL );
- if( s_is_malloced ) free( s );
- #ifdef SWITCH /* restore switchar if we changed it */
- setswitch(saveswitch);
- #endif
- return( status );
- }
-
-
- /*
- *------------------------------------------------------------------------
- * run: Execute command directly with spawnvp. This version will only run
- * single .EXE or .COM files but is much faster than using COMSPEC
- *------------------------------------------------------------------------
- */
- /*
- * If we know something about the program to be run, we can execute it
- * directly instead of calling the shell, thereby saving some time and memory
- * This routine added by R. Brittain
- */
- static int
- runexec( char *command )
- {
- char *vals[MAX_ARGS]; /* array of pointers to the arguments */
- char cmd[MAXARGLINE+1], *s;
- int cnt=0, totlen=0, status, i;
-
- /* Copy command and parse argument list. No support for \" in this version */
- s = cmd;
- strcpy(s,command); /* copy the argument - we will modify this */
-
- while (TRUE) {
- s += strspn(s, " \t\n"); /* skip leading whitespace */
- if (*s == '\0')
- break;
-
- if (cnt >= MAX_ARGS) {
- #ifdef DEBUG
- fprintf(stderr,"popen: too many arguments in call to exec\n");
- #endif DEBUG
- return(-1);
- }
- if (*s == '"') { /* open quote - look for closing quote */
- vals[cnt] = ++s; /* quotes are not included in argument */
- if ((s = strchr(s, '"')) == NULL)
- break;
- ++cnt;
- *s++ = '\0'; /* terminate this argument */
- } else {
- vals[cnt++] = s; /* start of a new argument */
- s += strcspn(s, " \n\t");
- if (*s == '\0')
- break;
- else
- *s++ = '\0';
- }
- }
- vals[cnt] = NULL; /* terminate array with a null pointer */
- /*
- * Test the final parsed arguments against maximum allowable command line
- * before executing. If too long, fail.
- */
- for (i=0; vals[i] != NULL; i++) {
- totlen += strlen(vals[i]) + 1;
- if (totlen > MAXARGLINE) {
- #ifdef DEBUG
- fprintf(stderr,"popen: command line too long\n");
- #endif DEBUG
- errno = E2BIG;
- return(-1);
- }
- }
- #ifdef DEBUG
- fprintf( stderr, "Running: %s\n", command );
- #endif DEBUG
- status = spawnvp( P_WAIT, vals[0], vals);
- return( status );
- }
-
-
- /*
- *------------------------------------------------------------------------
- * uniquepipe: returns a unique file name to use as a pipe
- *------------------------------------------------------------------------
- */
- static char *
- uniquepipe(void)
- {
- static char name[ 14 ];
- static short int num = 0;
- (void) sprintf( name, "p%04x%03.3d.tmp", getpid(), num++ );
- return( name );
- }
-
- /*
- *------------------------------------------------------------------------
- * resetpipe: Private routine to cancel a pipe
- *------------------------------------------------------------------------
- */
- static void
- resetpipe( int fd )
- {
- char *bp;
- if( (fd >= 0) && (fd < _NFILE) ) {
- pipetype[ fd ] = 0;
- if( (bp = pipename[ fd ]) != (char *) NULL ) {
- (void) unlink( bp );
- free( bp );
- pipename[ fd ] = (char *) NULL;
- }
- if( (bp = prgname[ fd ]) != (char *) NULL ) {
- free( bp );
- prgname[ fd ] = (char *) NULL;
- }
- }
- }
-
- /*
- *------------------------------------------------------------------------
- * popen: open a pipe
- *------------------------------------------------------------------------
- */
- FILE *popen( char *prg, char *type )
- /* char *prg; The command to be run */
- /* char *type; "w" or "r" */
- {
- FILE *p = (FILE *) NULL; /* Where we open the pipe */
- int pipefd = -1; /* fileno( p ) -- for convenience */
- char tmpfile[ BUFSIZ ]; /* Holds name of pipe file */
- char *tmpdir; /* Points to directory prefix of pipe */
- jmp_buf panic; /* Where to go if there's an error */
- int lineno; /* Line number where panic happened */
-
- /* test first for a null argument */
- if (prg == (char *)NULL || *prg == '\0') {
- errno = ENOENT;
- return((FILE *)NULL);
- }
-
- /* Find out where we should put temporary files */
- if( (tmpdir = getenv( "TMPDIR" )) == (char *) NULL )
- tmpdir = getenv( "TMP" );
- if( tmpdir != (char *) NULL ) {
- char c, *tmp;
- /* Use temporary directory if available */
- (void) strcpy( tmpfile, tmpdir );
- /* if there is not a / or a \, add one */
- for (tmp = tmpdir; *tmp != NULL; tmp++) ;
- c = *(--tmp);
- if (c != '/' && c != '\\') (void) strcat( tmpfile, "\\" );
- } else *tmpfile = '\0';
-
- /* Get a unique pipe file name */
- (void) strcat( tmpfile, uniquepipe() );
- if( (lineno = setjmp( panic )) != 0 ) {
- /* An error has occurred, so clean up */
- int E = errno;
- #ifdef DEBUG
- fprintf( stderr, "POPEN panic on line %d: %d\n", lineno, E );
- #endif DEBUG
- if( p != (FILE *) NULL ) (void) fclose( p );
- resetpipe( pipefd );
- errno = E;
- return( (FILE *) NULL );
- }
- if( strcmp( type, "w" ) == 0 ) {
- /* for write style pipe, pclose handles program execution */
- if( (p = fopen( tmpfile, "w" )) != (FILE *) NULL ) {
- pipefd = fileno( p );
- pipetype[ pipefd ] = WRITEIT;
- pipename[ pipefd ] = strdup( tmpfile );
- prgname[ pipefd ] = strdup( prg );
- if( !pipename[ pipefd ] || !prgname[ pipefd ] ) longjmp( panic, __LINE__ );
- #ifdef DEBUG
- fprintf( stderr, "Popen: create file %s for writing\n", tmpfile);
- #endif DEBUG
- }
- } else if( strcmp( type, "r" ) == 0 ) {
- /*
- * read pipe must create tmp file, set up stdout to point to the temp
- * file, and run the program. note that if the pipe file cannot be
- * opened, it'll return a condition indicating pipe failure, which is
- * fine.
- */
- if( (p = fopen( tmpfile, "w" )) != (FILE *) NULL ) {
- int ostdout;
- pipefd = fileno( p );
- pipetype[ pipefd ]= READIT;
- if( (pipename[ pipefd ] = strdup( tmpfile )) == (char *) NULL )
- longjmp( panic, __LINE__ );
-
- /* Redirect stdin for the new command */
- ostdout = dup( fileno( stdout ) );
- if( dup2( fileno( stdout ), pipefd ) < 0 ) {
- int E = errno;
- (void) dup2( fileno( stdout ), ostdout );
- errno = E;
- longjmp( panic, __LINE__ );
- }
- #ifdef DEBUG
- fprintf( stderr, "Popen: create file %s for reading\n", tmpfile);
- #endif DEBUG
- if (_osmajor > 2) {
- /* set the write and sharing mode on stdout */
- setmode( fileno( stdout ), O_WRONLY | O_DENYNONE);
- setmode( pipefd, O_NOINHERIT);
- }
- if (use_shell) {
- if (runshell( prg ) != 0 ) longjmp( panic, __LINE__ );
- } else {
- if (runexec( prg ) != 0 ) longjmp( panic, __LINE__ );
- }
- #ifdef DEBUG
- fprintf( stderr, "Popen: child process \"%s\" complete\n", prg);
- #endif DEBUG
- if( dup2( fileno( stdout ), ostdout ) < 0 ) longjmp( panic, __LINE__ );
- if( fclose( p ) < 0 ) longjmp( panic, __LINE__ );
- if( close( ostdout ) < 0 ) longjmp( panic, __LINE__ );
- if( (p = fopen( tmpfile, "r" )) == (FILE *) NULL ) longjmp( panic, __LINE__ );
- }
- } else {
- /* screwy call or unsupported type */
- errno = EINVFNC;
- longjmp( panic, __LINE__ );
- }
- return( p );
- }
-
- /* close a pipe */
- int
- pclose( FILE *p )
- {
- int pipefd = -1; /* Fildes where pipe is opened */
- int ostdin; /* Where our stdin points now */
- jmp_buf panic; /* Context to return to if error */
- int lineno; /* Line number where panic happened */
-
- if( (lineno = setjmp( panic )) != 0 ) {
- /* An error has occurred, so clean up and return */
- int E = errno;
- #ifdef DEBUG
- fprintf( stderr, "POPEN panic on line %d: %d\n", lineno, E );
- #endif DEBUG
- if( p != (FILE *) NULL ) (void) fclose( p );
- resetpipe( pipefd );
- errno = E;
- return( -1 );
- }
- pipefd = fileno( p );
- if( fclose( p ) < 0 ) longjmp( panic, __LINE__ );
- switch( pipetype[ pipefd ] ) {
- case WRITEIT:
- /*
- * open the temp file again as read, redirect stdin from that
- * file, run the program, then clean up.
- */
- if ( (p = fopen( pipename[ pipefd ],"r" )) == (FILE *) NULL )
- longjmp( panic,__LINE__);
- ostdin = dup( fileno( stdin ));
- if (dup2( fileno( stdin ), fileno( p )) < 0 ) longjmp( panic,__LINE__);
- if( fclose( p ) < 0 ) longjmp( panic, __LINE__ );
- if (_osmajor > 2) {
- /* set the write and sharing mode on stdout */
- setmode( fileno( stdin ), O_RDONLY | O_DENYNONE);
- setmode( pipefd, O_NOINHERIT);
- }
-
- if (use_shell) {
- if( runshell( prgname[ pipefd ] ) != 0 ) longjmp( panic, __LINE__ );
- } else {
- if( runexec( prgname[ pipefd ] ) != 0 ) longjmp( panic, __LINE__ );
- }
- #ifdef DEBUG
- fprintf( stderr, "Popen: child process \"%s\" complete\n", prgname[pipefd]);
- #endif DEBUG
- if( dup2( fileno( stdin ), ostdin ) < 0 ) longjmp( panic, __LINE__ );
- if( close( ostdin ) < 0 ) longjmp( panic, __LINE__ );
- resetpipe( pipefd );
- break;
- case READIT:
- /* close the temp file and remove it */
- resetpipe( pipefd );
- break;
- default:
- errno = EINVFNC;
- longjmp( panic, __LINE__ );
- /*NOTREACHED*/
- }
- return( 0 );
- }
-
- void set_popen_shell()
- {
- use_shell = TRUE;
- }
-
- void set_popen_exec()
- {
- use_shell = FALSE;
- }