/***
*popen.c - initiate a pipe and a child command
*
*	Copyright (c) 1989-1993, Microsoft Corporation. All rights reserved.
*
*Purpose:
*	Defines _popen() and _pclose().
*
*Revision History:
*	01-06-89  GJF	Initial version (I plead temporary insanity).
*	01-09-89  GJF	Fixed several bugs.
*	01-10-89  GJF	Implemented several improvements from Trapper.
*	01-12-89  GJF	Added underscores to function names. Also, in _pclose,
*			pstream must be close before the cwait call if it is
*			attached to the write handle of the pipe (otherwise,
*			may get a deadlock).
*	01-13-89  GJF	Added multi-thread/dll support.
*	02-09-89  GJF	Prevent child process from inheriting unwanted handles.
*			Also, always close pstream before doing the cwait.
*	05-10-89  GJF	Ported to 386 (OS/2 2.0)
*	08-14-89  GJF	Use DOSCALLS.H for API prototypes, fixed _rotl call
*			in _pclose (rotate 24 bits for 386!), re-tested.
*	11-16-89  GJF	Changed DOS32SETFILEHSTATE to DOS32SETFHSTATE
*	11-20-89  GJF	Added const attribute to types of _popen()'s args.
*			Also, fixed copyright.
*	03-19-90  GJF	Replaced _LOAD_DS with _CALLTYPE1 and added #include
*			<cruntime.h>.
*	03-26-90  GJF	Made ibtab() and setinherit() _CALLTYPE4.
*	07-25-90  SBM	Compiles cleanly with -W3 (removed unreferenced
*			variables), removed '32' from API names
*	08-13-90  SBM	Compiles cleanly with -W3 with new build of compiler
*	10-03-90  GJF	New-style function declarators.
*	12-04-90  SRW	Changed to include <oscalls.h> instead of <doscalls.h>
*	12-06-90  SRW	Added _CRUISER_ and _WIN32 conditionals.
*	01-18-91  GJF	ANSI naming.
*	02-25-91  SRW	Renamed _get_free_osfhnd to be _alloc_osfhnd [_WIN32_]
*	09-29-91  GJF	Picked up NT implementation (_WIN32_).
*	04-06-92  SRW	Fixed to not rely on setinherit function (_WIN32_).
*	04-28-92  DJM	ifndef for POSIX
*	05-06-92  GJF	Set _osfile[stddup] so that _get_osfhandle knows it's
*			open (bug found by Markl).
*	05-15-92  GJF	Fixed regression Markl found - _close(stddup) to ensure
*			that _osfile[] entry is cleared.
*	01-07-93  GJF	Substantially revised: purged Cruiser support, removed
*			needlessly repeated API calls, closed down a pipe
*			handle accidently left open at end of _popen, removed
*			reduntant CloseHandle call from _pclose, tried to clean
*			up the format and reduce the number of silly casts,
*			and added or revised many comments.
*	04-10-93  GJF	Removed redundant close of child process handle in
*			_pclose().
*
*******************************************************************************/

#ifndef _POSIX_

#include <cruntime.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <process.h>
#include <io.h>
#include <fcntl.h>
#include <internal.h>
#include <errno.h>
#include <msdos.h>
#include <os2dll.h>
#include <oscalls.h>

/* size for pipe buffer
 */
#define PSIZE	  1024

#define STDIN	  0
#define STDOUT	  1


/* definitions for table of stream pointer - process handle pairs. the table
 * is created, maintained and accessed by the idtab function. _popen and
 * _pclose gain access to table entries only by calling idtab. Note that the
 * table is expanded as necessary (by idtab) and free table entries are reused
 * (an entry is free if its stream field is NULL), but the table is never
 * contracted.
 */

typedef struct {
	FILE *stream;
	int prochnd;
} IDpair;

/* number of entries in idpairs table
 */
static unsigned idtabsiz = 0;

/* pointer to first table entry
 */
static IDpair *idpairs = NULL;

/* function to find specified table entries. also, creates and maintains
 * the table.
 */
static IDpair * _CRTAPI3 idtab(FILE *);


/***
*FILE *_popen(cmdstring,type) - initiate a pipe and a child command
*
*Purpose:
*	Creates a pipe and asynchronously executes a child copy of the command
*	processor with cmdstring (see system()). If the type string contains
*	an 'r', the calling process can read child command's standard output
*	via the returned stream. If the type string contains a 'w', the calling
*	process can write to the child command's standard input via the
*	returned stream.
*
*Entry:
*	char *cmdstring - command to be executed
*	char *type	- string of the form "r|w[b|t]", determines the mode
*			  of the returned stream (i.e., read-only vs write-only,
*			  binary vs text mode)
*
*Exit:
*	If successful, returns a stream associated with one end of the created
*	pipe (the other end of the pipe is associated with either the child
*	command's standard input or standard output).
*
*	If an error occurs, NULL is returned.
*
*Exceptions:
*
*******************************************************************************/

FILE * _CRTAPI1 _popen (
	const char *cmdstring,
	const char *type
	)
{

	int phdls[2];		  /* I/O handles for pipe */
	int ph_open[2]; 	  /* flags, set if correspond phdls is open */
	int i1; 		  /* index into phdls[] */
	int i2; 		  /* index into phdls[] */

	int tm = 0;		  /* flag indicating text or binary mode */

	int stdhdl;		  /* either STDIN or STDOUT */
	HANDLE osfhndsv1;	  /* used to save _osfhnd[stdhdl] */
	long osfhndsv2;		  /* used to save _osfhnd[phdls[i2] */
	char osfilesv1;		  /* used to save _osfile[stdhdl] */
	char osfilesv2;		  /* used to save _osfile[phdls[i2]] */

	HANDLE oldhnd;		  /* used to hold OS file handle values... */
	HANDLE newhnd;		  /* ...in calls to DuplicateHandle API */

	FILE *pstream;		  /* stream to be associated with pipe */

	HANDLE prochnd;		  /* handle for current process */

	char *cmdexe;		  /* pathname for the command processor */
	int childhnd;		  /* handle for child process (cmd.exe) */

	IDpair *locidpair;	  /* pointer to IDpair table entry */


	/* first check for errors in the arguments
	 */
	if ( (cmdstring == NULL) || (type == NULL) || ((*type != 'w') &&
	     (*type != 'r')) )
		goto error1;

	/* do the _pipe(). note that neither of the resulting handles will
	 * be inheritable.
	 */

	if ( *(type + 1) == 't' )
		tm = _O_TEXT;
	else if ( *(type + 1) == 'b' )
		tm = _O_BINARY;

        tm |= _O_NOINHERIT;

	if ( _pipe( phdls, PSIZE, tm ) == -1 )
		goto error1;

	/* test *type and set stdhdl, i1 and i2 accordingly.
	 */
	if ( *type == 'w' ) {
		stdhdl = STDIN;
		i1 = 0;
		i2 = 1;
	}
	else {
		stdhdl = STDOUT;
		i1 = 1;
		i2 = 0;
	}

	/* the pipe now exists. the following steps must be carried out before
	 * the child process (cmd.exe) may be spawned:
	 *
	 *	1. save a non-inheritable dup of stdhdl
	 *
	 *	2. force stdhdl to be a dup of ph[i1]. close both ph[i1] and
	 *	   the original OS handle underlying stdhdl
	 *
	 *	3. associate a stdio-level stream with ph[i2].
	 */

	/* set flags to indicate pipe handles are open. note, these are only
	 * used for error recovery.
	 */
	ph_open[ 0 ] = ph_open[ 1 ] = 1;


	/* get the process handle, it will be needed in some API calls
	 */
	prochnd = GetCurrentProcess();

	/* MULTI-THREAD: ASSERT LOCK ON STDHDL HERE!!!!
	 */
	_lock_fh( stdhdl );

	/* save a non-inheritable copy of stdhdl for later restoration.
	 */

	oldhnd = (HANDLE)_osfhnd[ stdhdl ];

	if ( (oldhnd == INVALID_HANDLE_VALUE) ||
	     !DuplicateHandle( prochnd,
			       oldhnd,
			       prochnd,
			       &osfhndsv1,
			       0L,
			       FALSE,			/* non-inheritable */
			       DUPLICATE_SAME_ACCESS )
	) {
		goto error2;
	}

	osfilesv1 = _osfile[ stdhdl ];

	/* force stdhdl to an inheritable dup of phdls[i1] (i.e., force
	 * STDIN to the pipe read handle or STDOUT to the pipe write handle)
	 * and close phdls[i1] (so there won't be a stray open handle on the
	 * pipe after a _pclose). also, clear ph_open[i1] flag so that error
	 * recovery won't try to close it again.
	 */

	if ( !DuplicateHandle( prochnd,
			       (HANDLE)_osfhnd[ phdls[i1] ],
			       prochnd,
			       &newhnd,
			       0L,
			       TRUE,			/* inheritable */
			       DUPLICATE_SAME_ACCESS )
        ) {
		goto error3;
        }

	(void)CloseHandle( (HANDLE)_osfhnd[stdhdl] );
	_free_osfhnd( stdhdl );

	_set_osfhnd( stdhdl, (long)newhnd );
	_osfile[ stdhdl ] = _osfile[ phdls[i1] ];

	(void)_close( phdls[i1] );
	ph_open[ i1 ] = 0;


	/* associate a stream with phdls[i2]. note that if there are no
	 * errors, pstream is the return value to the caller.
	 */
	if ( (pstream = _fdopen( phdls[i2], type )) == NULL )
		goto error4;

	/* MULTI-THREAD: ASSERT LOCK ON IDPAIRS HERE!!!!
	 */
	_mlock( _POPEN_LOCK );

	/* next, set locidpair to a free entry in the idpairs table.
	 */
	if ( (locidpair = idtab( NULL )) == NULL )
		goto error5;


	/* temporarily change the _osfhnd[] and _osfile[] entries so that
	 * the child doesn't get any entries for phdls[i2].
	 */
	osfhndsv2 = _osfhnd[ phdls[i2] ];
	_osfhnd[ phdls[i2] ] = (long)INVALID_HANDLE_VALUE;
	osfilesv2 = _osfile[ phdls[i2] ];
	_osfile[ phdls[i2] ] = 0;

	/* spawn the child copy of cmd.exe. the algorithm is adapted from
	 * SYSTEM.C, and behaves the same way.
	 */
	if ( ((cmdexe = getenv("COMSPEC")) == NULL) ||
	     (((childhnd = _spawnl( _P_NOWAIT,
				    cmdexe,
				    cmdexe,
				    "/c",
				    cmdstring,
				    NULL ))
	     == -1) && ((errno == ENOENT) || (errno == EACCES))) ) {
		/* either COMSPEC wasn't defined, or the spawn failed because
		 * cmdexe wasn't found or was inaccessible. in either case,
		 * try to spawn "cmd.exe" instead. note that spawnlp is used
		 * here so that the path is searched.
		 */
		cmdexe = "cmd.exe";
		childhnd = _spawnlp( _P_NOWAIT,
				     cmdexe,
				     cmdexe,
				     "/c",
				     cmdstring,
				     NULL);
	}

	_osfhnd[ phdls[i2] ] = osfhndsv2;
	_osfile[ phdls[i2] ] = osfilesv2;

	/* check if the last (perhaps only) spawn attempt was successful
	 */
	if ( childhnd == -1 )
		goto error6;

	/* restore stdhdl for the current process, set value of *locidpair,
	 * close osfhndsv1 (note that CloseHandle must be used instead of close)
	 * and return pstream to the caller
	 */

	(void)DuplicateHandle( prochnd,
			       osfhndsv1,
			       prochnd,
			       &newhnd,
			       0L,
			       TRUE,			/* inheritable */
			       DUPLICATE_CLOSE_SOURCE | /* close osfhndsv1 */
			       DUPLICATE_SAME_ACCESS );

	(void)CloseHandle( (HANDLE)_osfhnd[stdhdl] );
	_free_osfhnd( stdhdl );

	_set_osfhnd( stdhdl, (long)newhnd );
	_osfile[stdhdl] = osfilesv1;

	/* MULTI-THREAD: RELEASE LOCK ON STDHDL HERE!!!!
	 */
	_unlock_fh( stdhdl );

	locidpair->prochnd = childhnd;
	locidpair->stream = pstream;

	/* MULTI-THREAD: RELEASE LOCK ON IDPAIRS HERE!!!!
	 */
	_munlock( _POPEN_LOCK );

	/* all successful calls to _popen return to the caller via this return
	 * statement!
	 */
	return( pstream );

	/**
	 * error handling code. all detected errors end up here, entering
	 * via a goto one of the labels. note that the logic is currently
	 * a straight fall-thru scheme (e.g., if entered at error5, the
	 * code for error5, error4,...,error1 is all executed).
	 **********************************************************************/

	error6: /* make sure locidpair is reusable
		 */
		locidpair->stream = NULL;

	error5: /* close pstream (also, clear ph_open[i2] since the stream
		 * close will also close the pipe handle)
		 */
		(void)fclose( pstream );
		ph_open[ i2 ] = 0;

		/* MULTI-THREAD: RELEASE LOCK ON IDPAIRS HERE!!!!
		 */
		_munlock(_POPEN_LOCK);

	error4: /* restore stdhdl
		 */

		(void)DuplicateHandle( prochnd,
				       osfhndsv1,
				       prochnd,
				       &newhnd,
				       0L,
				       TRUE,
				       DUPLICATE_SAME_ACCESS );

		(void)CloseHandle( (HANDLE)_osfhnd[stdhdl] );
		_free_osfhnd( stdhdl );

		_set_osfhnd( stdhdl, (long)newhnd );
		_osfile[ stdhdl ] = osfilesv1;

		/* MULTI-THREAD: RELEASE LOCK ON STDHDL HERE!!!!
		 */
		_unlock_fh( stdhdl );

	error3: /* close osfhndsv1
		 */

		CloseHandle( osfhndsv1 );

	error2: /* close handles on pipe (if they are still open)
		 */
		if ( ph_open[i1] )
			_close( phdls[i1] );
		if ( ph_open[i2] )
			_close( phdls[i2] );

	error1: /* return NULL to the caller indicating failure
		 */
		return( NULL );
}


/***
*int _pclose(pstream) - wait on a child command and close the stream on the
*   associated pipe
*
*Purpose:
*	Closes pstream then waits on the associated child command. The
*	argument, pstream, must be the return value from a previous call to
*	_popen. _pclose first looks up the process handle of child command
*	started by that _popen and does a cwait on it. Then, it closes pstream
*	and returns the exit status of the child command to the caller.
*
*Entry:
*	FILE *pstream - file stream returned by a previous call to _popen
*
*Exit:
*	If successful, _pclose returns the exit status of the child command.
*	The format of the return value is that same as for cwait, except that
*	the low order and high order bytes are swapped.
*
*	If an error occurs, -1 is returned.
*
*Exceptions:
*
*******************************************************************************/

int _CRTAPI1 _pclose (
	FILE *pstream
	)
{
	IDpair *locidpair;	  /* pointer to entry in idpairs table */
	int termstat;		  /* termination status word */
	int retval = -1;	  /* return value (to caller) */

	/* MULTI-THREAD: LOCK IDPAIRS HERE!!!!
	 */
	_mlock(_POPEN_LOCK);

	if ( (pstream == NULL) || ((locidpair = idtab(pstream)) == NULL) )
		/* invalid pstream, exit with retval == -1
		 */
		goto done;

	/* close pstream
	 */
	(void)fclose(pstream);

	/* wait on the child (copy of the command processor) and all of its
	 * children.
	 */
	if ( (_cwait(&termstat, locidpair->prochnd, _WAIT_GRANDCHILD) != -1) ||
	     (errno == EINTR) )
		retval = termstat;

	/* Mark the IDpairtable entry as free (note: prochnd was closed by the
	 * preceding call to _cwait).
	 */
	locidpair->stream = NULL;
	locidpair->prochnd = 0;

	/* only return path!
	 */
	done:
		/* MULTI-THREAD: RELEASE LOCK ON IDPAIRS HERE!!!!
		 */
		_munlock(_POPEN_LOCK);
		return(retval);
}


/***
* static IDpair * idtab(FILE *pstream) - find an idpairs table entry
*
*Purpose:
*   Find an entry in the idpairs table.  This function finds the entry the
*   idpairs table entry corresponding to pstream. In the case where pstream
*   is NULL, the entry being searched for is any free entry. In this case,
*   idtab will create the idpairs table if it doesn't exist, or expand it (by
*   exactly one entry) if there are no free entries.
*
*   [MTHREAD NOTE:  This routine assumes that the caller has acquired the
*   idpairs table lock.]
*
*Entry:
*   FILE *pstream - stream corresponding to table entry to be found (if NULL
*		    then find any free table entry)
*
*Exit:
*   if successful, returns a pointer to the idpairs table entry. otherwise,
*   returns NULL.
*
*Exceptions:
*
*******************************************************************************/

static IDpair * _CRTAPI3 idtab (
	FILE *pstream
	)
{

	IDpair * pairptr;	/* ptr to entry */
	IDpair * newptr;	/* ptr to newly malloc'd memory */


	/* search the table. if table is empty, appropriate action should
	 * fall out automatically.
	 */
	for ( pairptr = idpairs ; pairptr < (idpairs+idtabsiz) ; pairptr++ )
		if ( pairptr->stream == pstream )
			break;

	/* if we found an entry, return it.
	 */
	if ( pairptr < (idpairs + idtabsiz) )
		return(pairptr);

	/* did not find an entry in the table.	if pstream was NULL, then try
	 * creating/expanding the table. otherwise, return NULL. note that
	 * when the table is created or expanded, exactly one new entry is
	 * produced. this must not be changed unless code is added to mark
	 * the extra entries as being free (i.e., set their stream fields to
	 * to NULL).
	 */
	if ( (pstream != NULL) || ((newptr = (IDpair *)realloc((void *)idpairs,
	     (idtabsiz + 1)*sizeof(IDpair))) == NULL) )
		/* either pstream was non-NULL or the attempt to create/expand
		 * the table failed. in either case, return a NULL to indicate
		 * failure.
		 */
		return( NULL );

	idpairs = newptr;		/* new table ptr */
	pairptr = newptr + idtabsiz;	/* first new entry */
	idtabsiz++;			/* new table size */

	return( pairptr );

}


#endif  /* _POSIX_ */
