]> git.proxmox.com Git - ceph.git/blame - ceph/src/boost/tools/build/src/engine/execnt.c
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / boost / tools / build / src / engine / execnt.c
CommitLineData
7c673cae
FG
1/*
2 * Copyright 1993, 1995 Christopher Seiwald.
3 *
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
6
7/* This file is ALSO:
8 * Copyright 2001-2004 David Abrahams.
9 * Copyright 2007 Rene Rivera.
10 * Distributed under the Boost Software License, Version 1.0.
11 * (See accompanying file LICENSE_1_0.txt or copy at
12 * http://www.boost.org/LICENSE_1_0.txt)
13 */
14
15/*
16 * execnt.c - execute a shell command on Windows NT
17 *
18 * If $(JAMSHELL) is defined, uses that to formulate the actual command. The
19 * default is: cmd.exe /Q/C
20 *
21 * In $(JAMSHELL), % expands to the command string and ! expands to the slot
22 * number (starting at 1) for multiprocess (-j) invocations. If $(JAMSHELL) does
23 * not include a %, it is tacked on as the last argument.
24 *
25 * Each $(JAMSHELL) placeholder must be specified as a separate individual
26 * element in a jam variable value.
27 *
28 * Do not just set JAMSHELL to cmd.exe - it will not work!
29 *
30 * External routines:
31 * exec_check() - preprocess and validate the command
32 * exec_cmd() - launch an async command execution
33 * exec_wait() - wait for any of the async command processes to terminate
34 *
35 * Internal routines:
36 * filetime_to_seconds() - Windows FILETIME --> number of seconds conversion
37 */
38
39#include "jam.h"
40#include "output.h"
41#ifdef USE_EXECNT
42#include "execcmd.h"
43
44#include "lists.h"
45#include "output.h"
46#include "pathsys.h"
47#include "string.h"
48
49#include <assert.h>
50#include <ctype.h>
51#include <errno.h>
52#include <time.h>
53
54#define WIN32_LEAN_AND_MEAN
55#include <windows.h>
56#include <process.h>
57#include <tlhelp32.h>
58
59
60/* get the maximum shell command line length according to the OS */
61static int maxline();
62/* valid raw command string length */
63static long raw_command_length( char const * command );
64/* add two 64-bit unsigned numbers, h1l1 and h2l2 */
65static FILETIME add_64(
66 unsigned long h1, unsigned long l1,
67 unsigned long h2, unsigned long l2 );
68/* */
69static FILETIME add_FILETIME( FILETIME t1, FILETIME t2 );
70/* */
71static FILETIME negate_FILETIME( FILETIME t );
72/* record the timing info for the process */
73static void record_times( HANDLE const, timing_info * const );
74/* calc the current running time of an *active* process */
75static double running_time( HANDLE const );
76/* terminate the given process, after terminating all its children first */
77static void kill_process_tree( DWORD const procesdId, HANDLE const );
78/* waits for a command to complete or time out */
79static int try_wait( int const timeoutMillis );
80/* reads any pending output for running commands */
81static void read_output();
82/* checks if a command ran out of time, and kills it */
83static int try_kill_one();
84/* is the first process a parent (direct or indirect) to the second one */
85static int is_parent_child( DWORD const parent, DWORD const child );
86/* */
87static void close_alert( PROCESS_INFORMATION const * const );
88/* close any alerts hanging around */
89static void close_alerts();
90/* prepare a command file to be executed using an external shell */
91static char const * prepare_command_file( string const * command, int slot );
92/* invoke the actual external process using the given command line */
93static void invoke_cmd( char const * const command, int const slot );
94/* find a free slot in the running commands table */
95static int get_free_cmdtab_slot();
96/* put together the final command string we are to run */
97static void string_new_from_argv( string * result, char const * const * argv );
98/* frees and renews the given string */
99static void string_renew( string * const );
100/* reports the last failed Windows API related error message */
101static void reportWindowsError( char const * const apiName, int slot );
102/* closes a Windows HANDLE and resets its variable to 0. */
103static void closeWinHandle( HANDLE * const handle );
b32b8144
FG
104/* Adds the job index to the list of currently active jobs. */
105static void register_wait( int job_id );
7c673cae
FG
106
107/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
108
109/* CreateProcessA() Windows API places a limit of 32768 characters (bytes) on
110 * the allowed command-line length, including a trailing Unicode (2-byte)
111 * nul-terminator character.
112 */
113#define MAX_RAW_COMMAND_LENGTH 32766
114
115/* We hold handles for pipes used to communicate with child processes in two
116 * element arrays indexed as follows.
117 */
118#define EXECCMD_PIPE_READ 0
119#define EXECCMD_PIPE_WRITE 1
120
121static int intr_installed;
122
123
124/* The list of commands we run. */
b32b8144 125static struct _cmdtab_t
7c673cae
FG
126{
127 /* Temporary command file used to execute the action when needed. */
128 string command_file[ 1 ];
129
130 /* Pipes for communicating with the child process. Parent reads from (0),
131 * child writes to (1).
132 */
133 HANDLE pipe_out[ 2 ];
134 HANDLE pipe_err[ 2 ];
135
136 string buffer_out[ 1 ]; /* buffer to hold stdout, if any */
137 string buffer_err[ 1 ]; /* buffer to hold stderr, if any */
138
139 PROCESS_INFORMATION pi; /* running process information */
140
b32b8144
FG
141 HANDLE wait_handle;
142
11fdf7f2
TL
143 int flags;
144
7c673cae
FG
145 /* Function called when the command completes. */
146 ExecCmdCallback func;
147
148 /* Opaque data passed back to the 'func' callback. */
149 void * closure;
b32b8144
FG
150} * cmdtab = NULL;
151static int cmdtab_size = 0;
7c673cae 152
b32b8144
FG
153/* A thread-safe single element queue. Used by the worker threads
154 * to signal the main thread that a process is completed.
155 */
156struct
157{
158 int job_index;
159 HANDLE read_okay;
160 HANDLE write_okay;
161} process_queue;
7c673cae
FG
162
163/*
164 * Execution unit tests.
165 */
166
167void execnt_unit_test()
168{
169#if !defined( NDEBUG )
170 /* vc6 preprocessor is broken, so assert with these strings gets confused.
171 * Use a table instead.
172 */
173 {
174 typedef struct test { char * command; int result; } test;
175 test tests[] = {
176 { "", 0 },
177 { " ", 0 },
178 { "x", 1 },
179 { "\nx", 1 },
180 { "x\n", 1 },
181 { "\nx\n", 1 },
182 { "\nx \n", 2 },
183 { "\nx \n ", 2 },
184 { " \n\t\t\v\r\r\n \t x \v \t\t\r\n\n\n \n\n\v\t", 8 },
185 { "x\ny", -1 },
186 { "x\n\n y", -1 },
187 { "echo x > foo.bar", -1 },
188 { "echo x < foo.bar", -1 },
189 { "echo x | foo.bar", -1 },
190 { "echo x \">\" foo.bar", 18 },
191 { "echo x '<' foo.bar", 18 },
192 { "echo x \"|\" foo.bar", 18 },
193 { "echo x \\\">\\\" foo.bar", -1 },
194 { "echo x \\\"<\\\" foo.bar", -1 },
195 { "echo x \\\"|\\\" foo.bar", -1 },
196 { "\"echo x > foo.bar\"", 18 },
197 { "echo x \"'\"<' foo.bar", -1 },
198 { "echo x \\\\\"<\\\\\" foo.bar", 22 },
199 { "echo x \\x\\\"<\\\\\" foo.bar", -1 },
200 { 0 } };
201 test const * t;
202 for ( t = tests; t->command; ++t )
203 assert( raw_command_length( t->command ) == t->result );
204 }
205
206 {
207 int const length = maxline() + 9;
208 char * const cmd = (char *)BJAM_MALLOC_ATOMIC( length + 1 );
209 memset( cmd, 'x', length );
210 cmd[ length ] = 0;
211 assert( raw_command_length( cmd ) == length );
212 BJAM_FREE( cmd );
213 }
214#endif
215}
216
b32b8144
FG
217/*
218 * exec_init() - global initialization
219 */
220void exec_init( void )
221{
222 if ( globs.jobs > cmdtab_size )
223 {
224 cmdtab = BJAM_REALLOC( cmdtab, globs.jobs * sizeof( *cmdtab ) );
225 memset( cmdtab + cmdtab_size, 0, ( globs.jobs - cmdtab_size ) * sizeof( *cmdtab ) );
226 cmdtab_size = globs.jobs;
227 }
228 if ( globs.jobs > MAXIMUM_WAIT_OBJECTS && !process_queue.read_okay )
229 {
230 process_queue.read_okay = CreateEvent( NULL, FALSE, FALSE, NULL );
231 process_queue.write_okay = CreateEvent( NULL, FALSE, TRUE, NULL );
232 }
233}
234
235/*
236 * exec_done - free resources.
237 */
238void exec_done( void )
239{
240 if ( process_queue.read_okay )
241 {
242 CloseHandle( process_queue.read_okay );
243 }
244 if ( process_queue.write_okay )
245 {
246 CloseHandle( process_queue.write_okay );
247 }
248 BJAM_FREE( cmdtab );
249}
7c673cae
FG
250
251/*
252 * exec_check() - preprocess and validate the command
253 */
254
255int exec_check
256(
257 string const * command,
258 LIST * * pShell,
259 int * error_length,
260 int * error_max_length
261)
262{
263 /* Default shell does nothing when triggered with an empty or a
264 * whitespace-only command so we simply skip running it in that case. We
265 * still pass them on to non-default shells as we do not really know what
266 * they are going to do with such commands.
267 */
268 if ( list_empty( *pShell ) )
269 {
270 char const * s = command->value;
271 while ( isspace( *s ) ) ++s;
272 if ( !*s )
273 return EXEC_CHECK_NOOP;
274 }
275
276 /* Check prerequisites for executing raw commands. */
277 if ( is_raw_command_request( *pShell ) )
278 {
279 int const raw_cmd_length = raw_command_length( command->value );
280 if ( raw_cmd_length < 0 )
281 {
282 /* Invalid characters detected - fallback to default shell. */
283 list_free( *pShell );
284 *pShell = L0;
285 }
286 else if ( raw_cmd_length > MAX_RAW_COMMAND_LENGTH )
287 {
288 *error_length = raw_cmd_length;
289 *error_max_length = MAX_RAW_COMMAND_LENGTH;
290 return EXEC_CHECK_TOO_LONG;
291 }
292 else
293 return raw_cmd_length ? EXEC_CHECK_OK : EXEC_CHECK_NOOP;
294 }
295
296 /* Now we know we are using an external shell. Note that there is no need to
297 * check for too long command strings when using an external shell since we
298 * use a command file and assume no one is going to set up a JAMSHELL format
299 * string longer than a few hundred bytes at most which should be well under
300 * the total command string limit. Should someone actually construct such a
301 * JAMSHELL value it will get reported as an 'invalid parameter'
302 * CreateProcessA() Windows API failure which seems like a good enough
303 * result for such intentional mischief.
304 */
305
306 /* Check for too long command lines. */
307 return check_cmd_for_too_long_lines( command->value, maxline(),
308 error_length, error_max_length );
309}
310
311
312/*
313 * exec_cmd() - launch an async command execution
314 *
315 * We assume exec_check() already verified that the given command can have its
316 * command string constructed as requested.
317 */
318
319void exec_cmd
320(
321 string const * cmd_orig,
11fdf7f2 322 int flags,
7c673cae
FG
323 ExecCmdCallback func,
324 void * closure,
325 LIST * shell
326)
327{
328 int const slot = get_free_cmdtab_slot();
329 int const is_raw_cmd = is_raw_command_request( shell );
330 string cmd_local[ 1 ];
331
332 /* Initialize default shell - anything more than /Q/C is non-portable. */
333 static LIST * default_shell;
334 if ( !default_shell )
335 default_shell = list_new( object_new( "cmd.exe /Q/C" ) );
336
337 /* Specifying no shell means requesting the default shell. */
338 if ( list_empty( shell ) )
339 shell = default_shell;
340
341 if ( DEBUG_EXECCMD )
342 if ( is_raw_cmd )
343 out_printf( "Executing raw command directly\n" );
344 else
345 {
346 out_printf( "Executing using a command file and the shell: " );
347 list_print( shell );
348 out_printf( "\n" );
349 }
350
351 /* If we are running a raw command directly - trim its leading whitespaces
352 * as well as any trailing all-whitespace lines but keep any trailing
353 * whitespace in the final/only line containing something other than
354 * whitespace).
355 */
356 if ( is_raw_cmd )
357 {
358 char const * start = cmd_orig->value;
359 char const * p = cmd_orig->value + cmd_orig->size;
360 char const * end = p;
361 while ( isspace( *start ) ) ++start;
362 while ( p > start && isspace( p[ -1 ] ) )
363 if ( *--p == '\n' )
364 end = p;
365 string_new( cmd_local );
366 string_append_range( cmd_local, start, end );
367 assert( cmd_local->size == raw_command_length( cmd_orig->value ) );
368 }
369 /* If we are not running a raw command directly, prepare a command file to
370 * be executed using an external shell and the actual command string using
371 * that command file.
372 */
373 else
374 {
375 char const * const cmd_file = prepare_command_file( cmd_orig, slot );
376 char const * argv[ MAXARGC + 1 ]; /* +1 for NULL */
377 argv_from_shell( argv, shell, cmd_file, slot );
378 string_new_from_argv( cmd_local, argv );
379 }
380
381 /* Catch interrupts whenever commands are running. */
382 if ( !intr_installed )
383 {
384 intr_installed = 1;
385 signal( SIGINT, onintr );
386 }
387
11fdf7f2
TL
388 cmdtab[ slot ].flags = flags;
389
7c673cae
FG
390 /* Save input data into the selected running commands table slot. */
391 cmdtab[ slot ].func = func;
392 cmdtab[ slot ].closure = closure;
393
394 /* Invoke the actual external process using the constructed command line. */
395 invoke_cmd( cmd_local->value, slot );
396
397 /* Free our local command string copy. */
398 string_free( cmd_local );
399}
400
401
402/*
403 * exec_wait() - wait for any of the async command processes to terminate
404 *
405 * Wait and drive at most one execution completion, while processing the I/O for
406 * all ongoing commands.
407 */
408
409void exec_wait()
410{
411 int i = -1;
412 int exit_reason; /* reason why a command completed */
413
414 /* Wait for a command to complete, while snarfing up any output. */
415 while ( 1 )
416 {
417 /* Check for a complete command, briefly. */
418 i = try_wait( 500 );
419 /* Read in the output of all running commands. */
420 read_output();
421 /* Close out pending debug style dialogs. */
422 close_alerts();
423 /* Process the completed command we found. */
424 if ( i >= 0 ) { exit_reason = EXIT_OK; break; }
425 /* Check if a command ran out of time. */
426 i = try_kill_one();
427 if ( i >= 0 ) { exit_reason = EXIT_TIMEOUT; break; }
428 }
429
430 /* We have a command... process it. */
431 {
432 DWORD exit_code;
433 timing_info time;
434 int rstat;
435
436 /* The time data for the command. */
437 record_times( cmdtab[ i ].pi.hProcess, &time );
438
439 /* Removed the used temporary command file. */
440 if ( cmdtab[ i ].command_file->size )
441 unlink( cmdtab[ i ].command_file->value );
442
443 /* Find out the process exit code. */
444 GetExitCodeProcess( cmdtab[ i ].pi.hProcess, &exit_code );
445
446 /* The dispossition of the command. */
447 if ( interrupted() )
448 rstat = EXEC_CMD_INTR;
449 else if ( exit_code )
450 rstat = EXEC_CMD_FAIL;
451 else
452 rstat = EXEC_CMD_OK;
453
454 /* Call the callback, may call back to jam rule land. */
455 (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time,
456 cmdtab[ i ].buffer_out->value, cmdtab[ i ].buffer_err->value,
457 exit_reason );
458
459 /* Clean up our child process tracking data. No need to clear the
460 * temporary command file name as it gets reused.
461 */
462 closeWinHandle( &cmdtab[ i ].pi.hProcess );
463 closeWinHandle( &cmdtab[ i ].pi.hThread );
464 closeWinHandle( &cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ] );
465 closeWinHandle( &cmdtab[ i ].pipe_out[ EXECCMD_PIPE_WRITE ] );
466 closeWinHandle( &cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ] );
467 closeWinHandle( &cmdtab[ i ].pipe_err[ EXECCMD_PIPE_WRITE ] );
468 string_renew( cmdtab[ i ].buffer_out );
469 string_renew( cmdtab[ i ].buffer_err );
470 }
471}
472
473
474/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
475
476/*
477 * Invoke the actual external process using the given command line. Track the
478 * process in our running commands table.
479 */
480
481static void invoke_cmd( char const * const command, int const slot )
482{
483 SECURITY_ATTRIBUTES sa = { sizeof( SECURITY_ATTRIBUTES ), 0, 0 };
484 SECURITY_DESCRIPTOR sd;
485 STARTUPINFO si = { sizeof( STARTUPINFO ), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
486 0, 0, 0, 0, 0, 0 };
487
488 /* Init the security data. */
489 InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION );
490 SetSecurityDescriptorDacl( &sd, TRUE, NULL, FALSE );
491 sa.lpSecurityDescriptor = &sd;
492 sa.bInheritHandle = TRUE;
493
494 /* Create output buffers. */
495 string_new( cmdtab[ slot ].buffer_out );
496 string_new( cmdtab[ slot ].buffer_err );
497
498 /* Create pipes for communicating with the child process. */
499 if ( !CreatePipe( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ],
500 &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ], &sa, 0 ) )
501 {
502 reportWindowsError( "CreatePipe", slot );
503 return;
504 }
505 if ( globs.pipe_action && !CreatePipe( &cmdtab[ slot ].pipe_err[
506 EXECCMD_PIPE_READ ], &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ],
507 &sa, 0 ) )
508 {
509 reportWindowsError( "CreatePipe", slot );
510 return;
511 }
512
513 /* Set handle inheritance off for the pipe ends the parent reads from. */
514 SetHandleInformation( cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ],
515 HANDLE_FLAG_INHERIT, 0 );
516 if ( globs.pipe_action )
517 SetHandleInformation( cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_READ ],
518 HANDLE_FLAG_INHERIT, 0 );
519
520 /* Hide the child window, if any. */
521 si.dwFlags |= STARTF_USESHOWWINDOW;
522 si.wShowWindow = SW_HIDE;
523
524 /* Redirect the child's output streams to our pipes. */
525 si.dwFlags |= STARTF_USESTDHANDLES;
526 si.hStdOutput = cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ];
527 si.hStdError = globs.pipe_action
528 ? cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ]
529 : cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ];
530
531 /* Let the child inherit stdin, as some commands assume it is available. */
532 si.hStdInput = GetStdHandle( STD_INPUT_HANDLE );
533
534 if ( DEBUG_EXECCMD )
535 out_printf( "Command string for CreateProcessA(): '%s'\n", command );
536
537 /* Run the command by creating a sub-process for it. */
538 if ( !CreateProcessA(
539 NULL , /* application name */
540 (char *)command , /* command line */
541 NULL , /* process attributes */
542 NULL , /* thread attributes */
543 TRUE , /* inherit handles */
544 CREATE_NEW_PROCESS_GROUP, /* create flags */
545 NULL , /* env vars, null inherits env */
546 NULL , /* current dir, null is our current dir */
547 &si , /* startup info */
548 &cmdtab[ slot ].pi ) ) /* child process info, if created */
549 {
550 reportWindowsError( "CreateProcessA", slot );
551 return;
552 }
b32b8144
FG
553
554 register_wait( slot );
7c673cae
FG
555}
556
557
558/*
559 * For more details on Windows cmd.exe shell command-line length limitations see
560 * the following MSDN article:
561 * http://support.microsoft.com/default.aspx?scid=kb;en-us;830473
562 */
563
564static int raw_maxline()
565{
566 OSVERSIONINFO os_info;
567 os_info.dwOSVersionInfoSize = sizeof( os_info );
568 GetVersionEx( &os_info );
569
570 if ( os_info.dwMajorVersion >= 5 ) return 8191; /* XP */
571 if ( os_info.dwMajorVersion == 4 ) return 2047; /* NT 4.x */
572 return 996; /* NT 3.5.1 */
573}
574
575static int maxline()
576{
b32b8144 577 static int result;
7c673cae
FG
578 if ( !result ) result = raw_maxline();
579 return result;
580}
581
582
583/*
584 * Closes a Windows HANDLE and resets its variable to 0.
585 */
586
587static void closeWinHandle( HANDLE * const handle )
588{
589 if ( *handle )
590 {
591 CloseHandle( *handle );
592 *handle = 0;
593 }
594}
595
596
597/*
598 * Frees and renews the given string.
599 */
600
601static void string_renew( string * const s )
602{
603 string_free( s );
604 string_new( s );
605}
606
607
608/*
609 * raw_command_length() - valid raw command string length
610 *
611 * Checks whether the given command may be executed as a raw command. If yes,
612 * returns the corresponding command string length. If not, returns -1.
613 *
614 * Rules for constructing raw command strings:
615 * - Command may not contain unquoted shell I/O redirection characters.
616 * - May have at most one command line with non-whitespace content.
617 * - Leading whitespace trimmed.
618 * - Trailing all-whitespace lines trimmed.
619 * - Trailing whitespace on the sole command line kept (may theoretically
620 * affect the executed command).
621 */
622
623static long raw_command_length( char const * command )
624{
625 char const * p;
626 char const * escape = 0;
627 char inquote = 0;
628 char const * newline = 0;
629
630 /* Skip leading whitespace. */
631 while ( isspace( *command ) )
632 ++command;
633
634 p = command;
635
636 /* Look for newlines and unquoted I/O redirection. */
637 do
638 {
639 p += strcspn( p, "\n\"'<>|\\" );
640 switch ( *p )
641 {
642 case '\n':
643 /* If our command contains non-whitespace content split over
644 * multiple lines we can not execute it directly.
645 */
646 newline = p;
647 while ( isspace( *++p ) );
648 if ( *p ) return -1;
649 break;
650
651 case '\\':
652 escape = escape && escape == p - 1 ? 0 : p;
653 ++p;
654 break;
655
656 case '"':
657 case '\'':
658 if ( escape && escape == p - 1 )
659 escape = 0;
660 else if ( inquote == *p )
661 inquote = 0;
662 else if ( !inquote )
663 inquote = *p;
664 ++p;
665 break;
666
667 case '<':
668 case '>':
669 case '|':
670 if ( !inquote )
671 return -1;
672 ++p;
673 break;
674 }
675 }
676 while ( *p );
677
678 /* Return the number of characters the command will occupy. */
679 return ( newline ? newline : p ) - command;
680}
681
682
683/* 64-bit arithmetic helpers. */
684
685/* Compute the carry bit from the addition of two 32-bit unsigned numbers. */
686#define add_carry_bit( a, b ) ((((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1)
687
688/* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1
689 * and h2l2.
690 */
691#define add_64_hi( h1, l1, h2, l2 ) ((h1) + (h2) + add_carry_bit(l1, l2))
692
693
694/*
695 * Add two 64-bit unsigned numbers, h1l1 and h2l2.
696 */
697
698static FILETIME add_64
699(
700 unsigned long h1, unsigned long l1,
701 unsigned long h2, unsigned long l2
702)
703{
704 FILETIME result;
705 result.dwLowDateTime = l1 + l2;
706 result.dwHighDateTime = add_64_hi( h1, l1, h2, l2 );
707 return result;
708}
709
710
711static FILETIME add_FILETIME( FILETIME t1, FILETIME t2 )
712{
713 return add_64( t1.dwHighDateTime, t1.dwLowDateTime, t2.dwHighDateTime,
714 t2.dwLowDateTime );
715}
716
717
718static FILETIME negate_FILETIME( FILETIME t )
719{
720 /* 2s complement negation */
721 return add_64( ~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1 );
722}
723
724
725/*
726 * filetime_to_seconds() - Windows FILETIME --> number of seconds conversion
727 */
728
729static double filetime_to_seconds( FILETIME const ft )
730{
731 return ft.dwHighDateTime * ( (double)( 1UL << 31 ) * 2.0 * 1.0e-7 ) +
732 ft.dwLowDateTime * 1.0e-7;
733}
734
735
736static void record_times( HANDLE const process, timing_info * const time )
737{
738 FILETIME creation;
739 FILETIME exit;
740 FILETIME kernel;
741 FILETIME user;
742 if ( GetProcessTimes( process, &creation, &exit, &kernel, &user ) )
743 {
744 time->system = filetime_to_seconds( kernel );
745 time->user = filetime_to_seconds( user );
746 timestamp_from_filetime( &time->start, &creation );
747 timestamp_from_filetime( &time->end, &exit );
748 }
749}
750
751
752#define IO_BUFFER_SIZE ( 16 * 1024 )
753
754static char ioBuffer[ IO_BUFFER_SIZE + 1 ];
755
11fdf7f2
TL
756#define FORWARD_PIPE_NONE 0
757#define FORWARD_PIPE_STDOUT 1
758#define FORWARD_PIPE_STDERR 2
7c673cae
FG
759
760static void read_pipe
761(
762 HANDLE in, /* the pipe to read from */
11fdf7f2
TL
763 string * out,
764 int forwarding_mode
7c673cae
FG
765)
766{
767 DWORD bytesInBuffer = 0;
768 DWORD bytesAvailable = 0;
769
770 do
771 {
772 /* check if we have any data to read */
773 if ( !PeekNamedPipe( in, ioBuffer, IO_BUFFER_SIZE, &bytesInBuffer,
774 &bytesAvailable, NULL ) )
775 bytesAvailable = 0;
776
777 /* read in the available data */
778 if ( bytesAvailable > 0 )
779 {
780 /* we only read in the available bytes, to avoid blocking */
781 if ( ReadFile( in, ioBuffer, bytesAvailable <= IO_BUFFER_SIZE ?
782 bytesAvailable : IO_BUFFER_SIZE, &bytesInBuffer, NULL ) )
783 {
784 if ( bytesInBuffer > 0 )
785 {
786 /* Clean up some illegal chars. */
787 int i;
788 for ( i = 0; i < bytesInBuffer; ++i )
789 {
790 if ( ( (unsigned char)ioBuffer[ i ] < 1 ) )
791 ioBuffer[ i ] = '?';
792 }
793 /* Null, terminate. */
794 ioBuffer[ bytesInBuffer ] = '\0';
795 /* Append to the output. */
796 string_append( out, ioBuffer );
11fdf7f2
TL
797 /* Copy it to our output if appropriate */
798 if ( forwarding_mode == FORWARD_PIPE_STDOUT )
799 out_data( ioBuffer );
800 else if ( forwarding_mode == FORWARD_PIPE_STDERR )
801 err_data( ioBuffer );
7c673cae
FG
802 /* Subtract what we read in. */
803 bytesAvailable -= bytesInBuffer;
804 }
805 else
806 {
807 /* Likely read a error, bail out. */
808 bytesAvailable = 0;
809 }
810 }
811 else
812 {
813 /* Definitely read a error, bail out. */
814 bytesAvailable = 0;
815 }
816 }
817 }
818 while ( bytesAvailable > 0 );
819}
820
11fdf7f2
TL
821#define EARLY_OUTPUT( cmd ) \
822 ( ! ( cmd.flags & EXEC_CMD_QUIET ) )
823
824#define FORWARD_STDOUT( c ) \
825 ( ( EARLY_OUTPUT( c ) && ( globs.pipe_action != 2 ) ) ? \
826 FORWARD_PIPE_STDOUT : FORWARD_PIPE_NONE )
827#define FORWARD_STDERR( c ) \
828 ( ( EARLY_OUTPUT( c ) && ( globs.pipe_action & 2 ) ) ? \
829 FORWARD_PIPE_STDERR : FORWARD_PIPE_NONE )
7c673cae
FG
830
831static void read_output()
832{
833 int i;
834 for ( i = 0; i < globs.jobs; ++i )
835 if ( cmdtab[ i ].pi.hProcess )
836 {
837 /* Read stdout data. */
838 if ( cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ] )
839 read_pipe( cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ],
11fdf7f2 840 cmdtab[ i ].buffer_out, FORWARD_STDOUT( cmdtab[ i ] ) );
7c673cae
FG
841 /* Read stderr data. */
842 if ( cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ] )
843 read_pipe( cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ],
11fdf7f2 844 cmdtab[ i ].buffer_err, FORWARD_STDERR( cmdtab[ i ] ) );
7c673cae
FG
845 }
846}
847
b32b8144
FG
848static void CALLBACK try_wait_callback( void * data, BOOLEAN is_timeout )
849{
850 struct _cmdtab_t * slot = ( struct _cmdtab_t * )data;
851 WaitForSingleObject( process_queue.write_okay, INFINITE );
852 process_queue.job_index = slot - cmdtab;
853 assert( !is_timeout );
854 SetEvent( process_queue.read_okay );
855 /* Okay. Non-blocking. */
856 UnregisterWait( slot->wait_handle );
857}
858
859static int try_wait_impl( DWORD timeout )
860{
861 int job_index;
862 int timed_out;
863 int res = WaitForSingleObject( process_queue.read_okay, timeout );
864 if ( res != WAIT_OBJECT_0 )
865 return -1;
866 job_index = process_queue.job_index;
867 SetEvent( process_queue.write_okay );
868 return job_index;
869}
870
871static void register_wait( int job_id )
872{
873 if ( globs.jobs > MAXIMUM_WAIT_OBJECTS )
874 {
875 HANDLE ignore;
876 RegisterWaitForSingleObject( &cmdtab[ job_id ].wait_handle,
877 cmdtab[ job_id ].pi.hProcess,
878 &try_wait_callback, &cmdtab[ job_id ], INFINITE,
879 WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE );
880 }
881}
7c673cae
FG
882
883/*
884 * Waits for a single child process command to complete, or the timeout,
885 * whichever comes first. Returns the index of the completed command in the
886 * cmdtab array, or -1.
887 */
888
7c673cae
FG
889static int try_wait( int const timeoutMillis )
890{
b32b8144
FG
891 if ( globs.jobs <= MAXIMUM_WAIT_OBJECTS )
892 {
893 int i;
894 HANDLE active_handles[ MAXIMUM_WAIT_OBJECTS ];
895 int job_ids[ MAXIMUM_WAIT_OBJECTS ];
896 DWORD num_handles = 0;
897 DWORD wait_api_result;
898 for ( i = 0; i < globs.jobs; ++i )
7c673cae 899 {
b32b8144 900 if( cmdtab[ i ].pi.hProcess )
7c673cae 901 {
b32b8144
FG
902 job_ids[ num_handles ] = i;
903 active_handles[ num_handles ] = cmdtab[ i ].pi.hProcess;
904 ++num_handles;
7c673cae 905 }
7c673cae 906 }
b32b8144
FG
907 wait_api_result = WaitForMultipleObjects( num_handles, active_handles, FALSE, timeoutMillis );
908 if ( WAIT_OBJECT_0 <= wait_api_result && wait_api_result < WAIT_OBJECT_0 + globs.jobs )
7c673cae 909 {
b32b8144 910 return job_ids[ wait_api_result - WAIT_OBJECT_0 ];
7c673cae
FG
911 }
912 else
913 {
b32b8144 914 return -1;
7c673cae
FG
915 }
916 }
b32b8144 917 else
7c673cae 918 {
b32b8144 919 return try_wait_impl( timeoutMillis );
7c673cae
FG
920 }
921
7c673cae
FG
922}
923
924
925static int try_kill_one()
926{
927 /* Only need to check if a timeout was specified with the -l option. */
928 if ( globs.timeout > 0 )
929 {
930 int i;
931 for ( i = 0; i < globs.jobs; ++i )
932 if ( cmdtab[ i ].pi.hProcess )
933 {
934 double const t = running_time( cmdtab[ i ].pi.hProcess );
935 if ( t > (double)globs.timeout )
936 {
937 /* The job may have left an alert dialog around, try and get
938 * rid of it before killing the job itself.
939 */
940 close_alert( &cmdtab[ i ].pi );
941 /* We have a "runaway" job, kill it. */
942 kill_process_tree( cmdtab[ i ].pi.dwProcessId,
943 cmdtab[ i ].pi.hProcess );
944 /* And return its running commands table slot. */
945 return i;
946 }
947 }
948 }
949 return -1;
950}
951
952
953static void close_alerts()
954{
955 /* We only attempt this every 5 seconds or so, because it is not a cheap
956 * operation, and we will catch the alerts eventually. This check uses
957 * floats as some compilers define CLOCKS_PER_SEC as a float or double.
958 */
959 if ( ( (float)clock() / (float)( CLOCKS_PER_SEC * 5 ) ) < ( 1.0 / 5.0 ) )
960 {
961 int i;
962 for ( i = 0; i < globs.jobs; ++i )
963 if ( cmdtab[ i ].pi.hProcess )
964 close_alert( &cmdtab[ i ].pi );
965 }
966}
967
968
969/*
970 * Calc the current running time of an *active* process.
971 */
972
973static double running_time( HANDLE const process )
974{
975 FILETIME creation;
976 FILETIME exit;
977 FILETIME kernel;
978 FILETIME user;
979 if ( GetProcessTimes( process, &creation, &exit, &kernel, &user ) )
980 {
981 /* Compute the elapsed time. */
982 FILETIME current;
983 GetSystemTimeAsFileTime( &current );
984 return filetime_to_seconds( add_FILETIME( current,
985 negate_FILETIME( creation ) ) );
986 }
987 return 0.0;
988}
989
990
991/*
992 * Not really optimal, or efficient, but it is easier this way, and it is not
993 * like we are going to be killing thousands, or even tens of processes.
994 */
995
996static void kill_process_tree( DWORD const pid, HANDLE const process )
997{
998 HANDLE const process_snapshot_h = CreateToolhelp32Snapshot(
999 TH32CS_SNAPPROCESS, 0 );
1000 if ( INVALID_HANDLE_VALUE != process_snapshot_h )
1001 {
1002 BOOL ok = TRUE;
1003 PROCESSENTRY32 pinfo;
1004 pinfo.dwSize = sizeof( PROCESSENTRY32 );
1005 for (
1006 ok = Process32First( process_snapshot_h, &pinfo );
1007 ok == TRUE;
1008 ok = Process32Next( process_snapshot_h, &pinfo ) )
1009 {
1010 if ( pinfo.th32ParentProcessID == pid )
1011 {
1012 /* Found a child, recurse to kill it and anything else below it.
1013 */
1014 HANDLE const ph = OpenProcess( PROCESS_ALL_ACCESS, FALSE,
1015 pinfo.th32ProcessID );
1016 if ( ph )
1017 {
1018 kill_process_tree( pinfo.th32ProcessID, ph );
1019 CloseHandle( ph );
1020 }
1021 }
1022 }
1023 CloseHandle( process_snapshot_h );
1024 }
1025 /* Now that the children are all dead, kill the root. */
1026 TerminateProcess( process, -2 );
1027}
1028
1029
1030static double creation_time( HANDLE const process )
1031{
1032 FILETIME creation;
1033 FILETIME exit;
1034 FILETIME kernel;
1035 FILETIME user;
1036 return GetProcessTimes( process, &creation, &exit, &kernel, &user )
1037 ? filetime_to_seconds( creation )
1038 : 0.0;
1039}
1040
1041
1042/*
1043 * Recursive check if first process is parent (directly or indirectly) of the
1044 * second one. Both processes are passed as process ids, not handles. Special
1045 * return value 2 means that the second process is smss.exe and its parent
1046 * process is System (first argument is ignored).
1047 */
1048
1049static int is_parent_child( DWORD const parent, DWORD const child )
1050{
1051 HANDLE process_snapshot_h = INVALID_HANDLE_VALUE;
1052
1053 if ( !child )
1054 return 0;
1055 if ( parent == child )
1056 return 1;
1057
1058 process_snapshot_h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
1059 if ( INVALID_HANDLE_VALUE != process_snapshot_h )
1060 {
1061 BOOL ok = TRUE;
1062 PROCESSENTRY32 pinfo;
1063 pinfo.dwSize = sizeof( PROCESSENTRY32 );
1064 for (
1065 ok = Process32First( process_snapshot_h, &pinfo );
1066 ok == TRUE;
1067 ok = Process32Next( process_snapshot_h, &pinfo ) )
1068 {
1069 if ( pinfo.th32ProcessID == child )
1070 {
1071 /* Unfortunately, process ids are not really unique. There might
1072 * be spurious "parent and child" relationship match between two
1073 * non-related processes if real parent process of a given
1074 * process has exited (while child process kept running as an
1075 * "orphan") and the process id of such parent process has been
1076 * reused by internals of the operating system when creating
1077 * another process.
1078 *
1079 * Thus an additional check is needed - process creation time.
1080 * This check may fail (i.e. return 0) for system processes due
1081 * to insufficient privileges, and that is OK.
1082 */
1083 double tchild = 0.0;
1084 double tparent = 0.0;
1085 HANDLE const hchild = OpenProcess( PROCESS_QUERY_INFORMATION,
1086 FALSE, pinfo.th32ProcessID );
1087 CloseHandle( process_snapshot_h );
1088
1089 /* csrss.exe may display message box like following:
1090 * xyz.exe - Unable To Locate Component
1091 * This application has failed to start because
1092 * boost_foo-bar.dll was not found. Re-installing the
1093 * application may fix the problem
1094 * This actually happens when starting a test process that
1095 * depends on a dynamic library which failed to build. We want
1096 * to automatically close these message boxes even though
1097 * csrss.exe is not our child process. We may depend on the fact
1098 * that (in all current versions of Windows) csrss.exe is a
1099 * direct child of the smss.exe process, which in turn is a
1100 * direct child of the System process, which always has process
1101 * id == 4. This check must be performed before comparing
1102 * process creation times.
1103 */
1104 if ( !stricmp( pinfo.szExeFile, "csrss.exe" ) &&
1105 is_parent_child( parent, pinfo.th32ParentProcessID ) == 2 )
1106 return 1;
1107 if ( !stricmp( pinfo.szExeFile, "smss.exe" ) &&
1108 ( pinfo.th32ParentProcessID == 4 ) )
1109 return 2;
1110
1111 if ( hchild )
1112 {
1113 HANDLE hparent = OpenProcess( PROCESS_QUERY_INFORMATION,
1114 FALSE, pinfo.th32ParentProcessID );
1115 if ( hparent )
1116 {
1117 tchild = creation_time( hchild );
1118 tparent = creation_time( hparent );
1119 CloseHandle( hparent );
1120 }
1121 CloseHandle( hchild );
1122 }
1123
1124 /* Return 0 if one of the following is true:
1125 * 1. we failed to read process creation time
1126 * 2. child was created before alleged parent
1127 */
1128 if ( ( tchild == 0.0 ) || ( tparent == 0.0 ) ||
1129 ( tchild < tparent ) )
1130 return 0;
1131
1132 return is_parent_child( parent, pinfo.th32ParentProcessID ) & 1;
1133 }
1134 }
1135
1136 CloseHandle( process_snapshot_h );
1137 }
1138
1139 return 0;
1140}
1141
1142
1143/*
1144 * Called by the OS for each topmost window.
1145 */
1146
1147BOOL CALLBACK close_alert_window_enum( HWND hwnd, LPARAM lParam )
1148{
1149 char buf[ 7 ] = { 0 };
1150 PROCESS_INFORMATION const * const pi = (PROCESS_INFORMATION *)lParam;
1151 DWORD pid;
1152 DWORD tid;
1153
1154 /* We want to find and close any window that:
1155 * 1. is visible and
1156 * 2. is a dialog and
1157 * 3. is displayed by any of our child processes
1158 */
1159 if (
1160 /* We assume hidden windows do not require user interaction. */
1161 !IsWindowVisible( hwnd )
1162 /* Failed to read class name; presume it is not a dialog. */
1163 || !GetClassNameA( hwnd, buf, sizeof( buf ) )
1164 /* All Windows system dialogs use the same Window class name. */
1165 || strcmp( buf, "#32770" ) )
1166 return TRUE;
1167
1168 /* GetWindowThreadProcessId() returns 0 on error, otherwise thread id of
1169 * the window's message pump thread.
1170 */
1171 tid = GetWindowThreadProcessId( hwnd, &pid );
1172 if ( !tid || !is_parent_child( pi->dwProcessId, pid ) )
1173 return TRUE;
1174
1175 /* Ask real nice. */
1176 PostMessageA( hwnd, WM_CLOSE, 0, 0 );
1177
1178 /* Wait and see if it worked. If not, insist. */
1179 if ( WaitForSingleObject( pi->hProcess, 200 ) == WAIT_TIMEOUT )
1180 {
1181 PostThreadMessageA( tid, WM_QUIT, 0, 0 );
1182 WaitForSingleObject( pi->hProcess, 300 );
1183 }
1184
1185 /* Done, we do not want to check any other windows now. */
1186 return FALSE;
1187}
1188
1189
1190static void close_alert( PROCESS_INFORMATION const * const pi )
1191{
1192 EnumWindows( &close_alert_window_enum, (LPARAM)pi );
1193}
1194
1195
1196/*
1197 * Open a command file to store the command into for executing using an external
1198 * shell. Returns a pointer to a FILE open for writing or 0 in case such a file
1199 * could not be opened. The file name used is stored back in the corresponding
1200 * running commands table slot.
1201 *
1202 * Expects the running commands table slot's command_file attribute to contain
1203 * either a zeroed out string object or one prepared previously by this same
1204 * function.
1205 */
1206
1207static FILE * open_command_file( int const slot )
1208{
1209 string * const command_file = cmdtab[ slot ].command_file;
1210
1211 /* If the temporary command file name has not already been prepared for this
1212 * slot number, prepare a new one containing a '##' place holder that will
1213 * be changed later and needs to be located at a fixed distance from the
1214 * end.
1215 */
1216 if ( !command_file->value )
1217 {
1218 DWORD const procID = GetCurrentProcessId();
1219 string const * const tmpdir = path_tmpdir();
1220 string_new( command_file );
1221 string_reserve( command_file, tmpdir->size + 64 );
1222 command_file->size = sprintf( command_file->value,
1223 "%s\\jam%d-%02d-##.bat", tmpdir->value, procID, slot );
1224 }
1225
1226 /* For some reason opening a command file can fail intermittently. But doing
1227 * some retries works. Most likely this is due to a previously existing file
1228 * of the same name that happens to still be opened by an active virus
1229 * scanner. Originally pointed out and fixed by Bronek Kozicki.
1230 *
1231 * We first try to open several differently named files to avoid having to
1232 * wait idly if not absolutely necessary. Our temporary command file names
1233 * contain a fixed position place holder we use for generating different
1234 * file names.
1235 */
1236 {
1237 char * const index1 = command_file->value + command_file->size - 6;
1238 char * const index2 = index1 + 1;
1239 int waits_remaining;
1240 assert( command_file->value < index1 );
1241 assert( index2 + 1 < command_file->value + command_file->size );
1242 assert( index2[ 1 ] == '.' );
1243 for ( waits_remaining = 3; ; --waits_remaining )
1244 {
1245 int index;
1246 for ( index = 0; index != 20; ++index )
1247 {
1248 FILE * f;
1249 *index1 = '0' + index / 10;
1250 *index2 = '0' + index % 10;
1251 f = fopen( command_file->value, "w" );
1252 if ( f ) return f;
1253 }
1254 if ( !waits_remaining ) break;
1255 Sleep( 250 );
1256 }
1257 }
1258
1259 return 0;
1260}
1261
1262
1263/*
1264 * Prepare a command file to be executed using an external shell.
1265 */
1266
1267static char const * prepare_command_file( string const * command, int slot )
1268{
1269 FILE * const f = open_command_file( slot );
1270 if ( !f )
1271 {
1272 err_printf( "failed to write command file!\n" );
1273 exit( EXITBAD );
1274 }
1275 fputs( command->value, f );
1276 fclose( f );
1277 return cmdtab[ slot ].command_file->value;
1278}
1279
1280
1281/*
1282 * Find a free slot in the running commands table.
1283 */
1284
1285static int get_free_cmdtab_slot()
1286{
1287 int slot;
b32b8144 1288 for ( slot = 0; slot < globs.jobs; ++slot )
7c673cae
FG
1289 if ( !cmdtab[ slot ].pi.hProcess )
1290 return slot;
1291 err_printf( "no slots for child!\n" );
1292 exit( EXITBAD );
1293}
1294
1295
1296/*
1297 * Put together the final command string we are to run.
1298 */
1299
1300static void string_new_from_argv( string * result, char const * const * argv )
1301{
1302 assert( argv );
1303 assert( argv[ 0 ] );
1304 string_copy( result, *(argv++) );
1305 while ( *argv )
1306 {
1307 string_push_back( result, ' ' );
11fdf7f2 1308 string_push_back( result, '"' );
7c673cae 1309 string_append( result, *(argv++) );
11fdf7f2 1310 string_push_back( result, '"' );
7c673cae
FG
1311 }
1312}
1313
1314
1315/*
1316 * Reports the last failed Windows API related error message.
1317 */
1318
1319static void reportWindowsError( char const * const apiName, int slot )
1320{
1321 char * errorMessage;
1322 char buf[24];
1323 string * err_buf;
1324 timing_info time;
1325 DWORD const errorCode = GetLastError();
1326 DWORD apiResult = FormatMessageA(
1327 FORMAT_MESSAGE_ALLOCATE_BUFFER | /* __in DWORD dwFlags */
1328 FORMAT_MESSAGE_FROM_SYSTEM |
1329 FORMAT_MESSAGE_IGNORE_INSERTS,
1330 NULL, /* __in_opt LPCVOID lpSource */
1331 errorCode, /* __in DWORD dwMessageId */
1332 0, /* __in DWORD dwLanguageId */
1333 (LPSTR)&errorMessage, /* __out LPTSTR lpBuffer */
1334 0, /* __in DWORD nSize */
1335 0 ); /* __in_opt va_list * Arguments */
1336
1337 /* Build a message as if the process had written to stderr. */
1338 if ( globs.pipe_action )
1339 err_buf = cmdtab[ slot ].buffer_err;
1340 else
1341 err_buf = cmdtab[ slot ].buffer_out;
1342 string_append( err_buf, apiName );
1343 string_append( err_buf, "() Windows API failed: " );
1344 sprintf( buf, "%d", errorCode );
1345 string_append( err_buf, buf );
1346
1347 if ( !apiResult )
1348 string_append( err_buf, ".\n" );
1349 else
1350 {
1351 string_append( err_buf, " - " );
1352 string_append( err_buf, errorMessage );
1353 /* Make sure that the buffer is terminated with a newline */
1354 if( err_buf->value[ err_buf->size - 1 ] != '\n' )
1355 string_push_back( err_buf, '\n' );
1356 LocalFree( errorMessage );
1357 }
1358
1359 /* Since the process didn't actually start, use a blank timing_info. */
1360 time.system = 0;
1361 time.user = 0;
1362 timestamp_current( &time.start );
1363 timestamp_current( &time.end );
1364
1365 /* Invoke the callback with a failure status. */
1366 (*cmdtab[ slot ].func)( cmdtab[ slot ].closure, EXEC_CMD_FAIL, &time,
1367 cmdtab[ slot ].buffer_out->value, cmdtab[ slot ].buffer_err->value,
1368 EXIT_OK );
1369
1370 /* Clean up any handles that were opened. */
1371 closeWinHandle( &cmdtab[ slot ].pi.hProcess );
1372 closeWinHandle( &cmdtab[ slot ].pi.hThread );
1373 closeWinHandle( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ] );
1374 closeWinHandle( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ] );
1375 closeWinHandle( &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_READ ] );
1376 closeWinHandle( &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ] );
1377 string_renew( cmdtab[ slot ].buffer_out );
1378 string_renew( cmdtab[ slot ].buffer_err );
1379}
1380
1381
1382#endif /* USE_EXECNT */