Category : Batch File Utilities - mostly for DOS
Archive   : RBSETENV.ZIP
Filename : POPEN.C
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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;
}
Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!
This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.
But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/