summaryrefslogblamecommitdiffstats
path: root/private/crt32/stdio/popen.c
blob: 5a4b69405fc22b704479b0034d12a2d9a3322dbf (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562

















































































































































































































































































































































































































































































































































































                                                                                
/***
*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_ */