Re: [boost] [Boost-build] Milestone 11, release candidate 3

On Wednesday 12 July 2006 20:13, Bronek Kozicki wrote: Hi Bronek, I'm replying on list, so that Win32 experts can comment too.
attached .diff is addressing 3 bugs in execnt.c - memory leak in is_parent_child - closing unrelated dialogs (displayed by descendants of explorer.exe) - missing dialogs displayed by csrss.exe ("... - Unable To Locate Component")
there are also other improvements and changes that I tested much more thoroughly than previous update to execnt.c
--- C:\Documents and Settings\Bronek Kozicki\Desktop\execnt.c Thu Jun 8 02:25:58 2006 UTC +++ E:\DEVEL\BOOST_RTEST\boost\tools\jam\src\execnt.c Sun Jun 25 18:55:27 2006 UTC @@ -1001,8 +1001,10 @@ int is_parent_child(DWORD parent, DWORD child) {
While we're at it, can be have a comment for this function, at least saying what are 'parent' and 'child' arguments. Are they pids or something else?
HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; + if (!child) + return 0; if (parent == child) return 1; process_snapshot_h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); @@ -1015,49 +1017,100 @@ ok = Process32First(process_snapshot_h, &pinfo); ok == TRUE; ok = Process32Next(process_snapshot_h, &pinfo) ) { - if (pinfo.th32ProcessID == child && pinfo.th32ParentProcessID) + if (pinfo.th32ProcessID == child) + { + CloseHandle(process_snapshot_h); + if (!stricmp(pinfo.szExeFile, "explorer.exe"))
This will be clearer if written like: if (stricmp(pinfo.szExeFile, "explorer.exe") == 0)
+ { + /* explorer.exe is orphaned and process_id of its parent may + accidentally match process_id of process we are after. We must not + close dialog boxes displayed by children of explorer.exe even + though (thanks to its parent process id) it might appear to be + our child. This is not very reliable - there might be more + orphaned processes or shell might be something else than + explorer.exe, but this is most common and important scenario */
The last line of this comment is longer than 80 characters. At the same time, thanks for detailed comments! This is tricky code so it's good to have explanations.
+ return 0; + } + if (!stricmp(pinfo.szExeFile, "csrss.exe")) + { + /* csrss.exe may display message box like following: + xyz.exe - Unable To Locate Component + This application has failed to start because boost_foo-bar.dll + was not found. Re-installing the application may fix the problem + This actually happens when starting test process that depends on + dynamic library which failed to build. We want to automatically + close these message boxes even though csrss.exe is not our + child process. We may depend on the fact that (in all current + versions of Windows) csrss.exe is indirectly child of System which + always has process id == 4 */ + if (is_parent_child(4, pinfo.th32ParentProcessID)) + return 1;
Oh, closing those message box is cool. The comment has lines longer than 80 characters, though ;-)
+ } return is_parent_child(parent, pinfo.th32ParentProcessID); + } } CloseHandle(process_snapshot_h); } return 0; } -int related(HANDLE h, DWORD p) +int related(DWORD d, DWORD p) { - return is_parent_child(get_process_id(h), p); + return is_parent_child(d, p); }
If this function just forwards to is_parent_child, can we just change all callers to call is_parent_child?
+typedef struct PROCESS_HANDLE_ID {HANDLE h; DWORD pid;} PROCESS_HANDLE_ID; + BOOL CALLBACK window_enum(HWND hwnd, LPARAM lParam) { - char buf[10] = {0}; - HANDLE h = *((HANDLE*) (lParam)); + char buf[7] = {0}; + PROCESS_HANDLE_ID p = *((PROCESS_HANDLE_ID*) (lParam)); DWORD pid = 0; + DWORD tid = 0; + + if (!IsWindowVisible(hwnd)) + return TRUE; - if (!GetClassNameA(hwnd, buf, 10)) - return TRUE; // failed to read class name + if (!GetClassNameA(hwnd, buf, 7)) + return TRUE; /* failed to read class name; presume it's not a dialog */
Use sizeof(buf), perhaps?
if (strcmp(buf, "#32770"))
While we're at it, it will be clearer if written like: if (strcmp(buf, "#32770") != 0) I realize this is not originally yours code, but anyway.
- return TRUE; // not a dialog + return TRUE; /* not a dialog */
BTW, what does this code does? Do all dialogs have class name of #32770? Or this is specific class of dialogs we're interested in?
- GetWindowThreadProcessId(hwnd, &pid); - if (related(h, pid)) + tid = GetWindowThreadProcessId(hwnd, &pid); + if (tid && related(p.pid, pid))
What's the point of check for 'tid'?
{ - PostMessage(hwnd, WM_QUIT, 0, 0); - // just one window at a time + /* ask really nice */ + PostMessageA(hwnd, WM_CLOSE, 0, 0); + /* now wait and see if it worked. If not, insist */ + if (WaitForSingleObject(p.h, 200) == WAIT_TIMEOUT) + { + PostThreadMessageA(tid, WM_QUIT, 0, 0); + if (WaitForSingleObject(p.h, 500) == WAIT_TIMEOUT) + { + PostThreadMessageA(tid, WM_QUIT, 0, 0); + WaitForSingleObject(p.h, 500);
Is posting WM_QUIT message twice better than posting it once?
+ } + } return FALSE; } return TRUE; } void close_alert(HANDLE process) { - EnumWindows(&window_enum, (LPARAM) &process); + DWORD pid = get_process_id(process); + /* If process already exited or we just cannot get its process id, do not go any further */ + if (pid) + { + PROCESS_HANDLE_ID p = {process, pid}; + EnumWindows(&window_enum, (LPARAM) &p); + } } static int my_wait( int *status ) @@ -1093,23 +1146,29 @@ } if ( globs.timeout > 0 ) { + unsigned int alert_wait = 1; /* with a timeout we wait for a finish or a timeout, we check every second to see if something timed out */ - for (waitcode = WAIT_TIMEOUT; waitcode == WAIT_TIMEOUT;) + for (waitcode = WAIT_TIMEOUT; waitcode == WAIT_TIMEOUT; ++alert_wait) { waitcode = WaitForMultipleObjects( num_active, active_handles, FALSE, 1*1000 /* 1 second */ ); if ( waitcode == WAIT_TIMEOUT ) { /* check if any jobs have surpassed the maximum run time. */ for ( i = 0; i < num_active; ++i ) { double t = running_time(active_handles[i]); + + /* periodically (each 5 secs) review and close alert dialogs hanging around */ + if ((alert_wait % ((unsigned int) 5)) == 0) + close_alert(active_handles[i]); +
Is the point here to close dialogs right away, and not wait possibly long time for timeout? If so, maybe this should be added to comment? Overall, looks reasonable. Would you like to address the comments and sent the patch again, or it will be more convenient if I commit the patch right now and you'll send further patches when you have the time? Thanks, Volodya

Although I'm not familiar with the context for this code, I have a little comment...
+ { + /* explorer.exe is orphaned and process_id of its parent may + accidentally match process_id of process we are after. We must not + close dialog boxes displayed by children of explorer.exe even + though (thanks to its parent process id) it might appear to be + our child. This is not very reliable - there might be more + orphaned processes or shell might be something else than + explorer.exe, but this is most common and important scenario */
You may be able to use GetProcessTimes on the process handle to determine the creation time of the particular process. If it is older than the time at which the parent spawned child processes, then even though it might appear to be a child it almost certainly isn't. Conversely if the creation time is after, then you can almost certainly say it is a child process. (Note that even this isn't perfect, since the system clock can be changed, but it's probably more reliable.) Cheers, Chris

Vladimir Prus wrote:
While we're at it, can be have a comment for this function, at least saying what are 'parent' and 'child' arguments. Are they pids or something else?
these are process IDs. I will supply some comment there.
This will be clearer if written like:
if (stricmp(pinfo.szExeFile, "explorer.exe") == 0)
OK.
The last line of this comment is longer than 80 characters. At the same time, thanks for detailed comments! This is tricky code so it's good to have explanations.
ok, but I think that instead of reformatting I will implement workaround suggested by Chris Kohlhoff. With comments, too ;)
The comment has lines longer than 80 characters, though ;-)
OK,
-int related(HANDLE h, DWORD p) +int related(DWORD d, DWORD p) { - return is_parent_child(get_process_id(h), p); + return is_parent_child(d, p); }
If this function just forwards to is_parent_child, can we just change all callers to call is_parent_child?
good point.
Use sizeof(buf), perhaps?
OK,
if (strcmp(buf, "#32770"))
While we're at it, it will be clearer if written like:
if (strcmp(buf, "#32770") != 0)
OK
BTW, what does this code does? Do all dialogs have class name of #32770? Or this is specific class of dialogs we're interested in?
all "system" dialogs have class name #32770 . It's obviously possible to create window that looks just like dialog but is other class, but no such window is known to me to display assertion or process failure report.
+ tid = GetWindowThreadProcessId(hwnd, &pid); + if (tid && related(p.pid, pid)) What's the point of check for 'tid'?
GetWindowThreadProcessId returns 0 if failed (undocumented feature; I asked Microsoft to supplement MSDN)
+ PostMessageA(hwnd, WM_CLOSE, 0, 0); + /* now wait and see if it worked. If not, insist */ + if (WaitForSingleObject(p.h, 200) == WAIT_TIMEOUT) + { + PostThreadMessageA(tid, WM_QUIT, 0, 0); + if (WaitForSingleObject(p.h, 500) == WAIT_TIMEOUT) + { + PostThreadMessageA(tid, WM_QUIT, 0, 0); + WaitForSingleObject(p.h, 500);
Is posting WM_QUIT message twice better than posting it once?
this is why I call it "insisting". Windows messages are sometimes a little unreliable (IMHO) and if first message "misses" there is small chance than next one will "hit" . Maybe it's needless, but it's also harmless.
Is the point here to close dialogs right away, and not wait possibly long time for timeout? If so, maybe this should be added to comment?
exactly. The code to automatically close message boxes is quite useless if it does not close the window in reasonably short time - the whole purpose of this code is to let us (testers) use long process timeouts (option -l , so that slow compilers or tests have time to do their job) without tremendously slowing tests while displaying these messages. It's especially useful for all kinds of experiments, when these dialogs may appear quite often.
Overall, looks reasonable. Would you like to address the comments and sent the patch again, or it will be more convenient if I commit the patch right now and you'll send further patches when you have the time?
submit now, because the code is well tested and works, and I will try to prepare another patch before weekend. B.

Vladimir Prus wrote:
Overall, looks reasonable. Would you like to address the comments and sent the patch again, or it will be more convenient if I commit the patch right now and you'll send further patches when you have the time?
Hi Volodya Here is a new version of execnt.c . I will be happy if you can review it. It's no tested yet, I will test it tonight. B. /* * Copyright 1993, 1995 Christopher Seiwald. * * This file is part of Jam - see jam.c for Copyright information. */ /* This file is ALSO: * Copyright 2001-2004 David Abrahams. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) */ # include "jam.h" # include "lists.h" # include "execcmd.h" # include "pathsys.h" # include "debug.h" # include <errno.h> # include <assert.h> # include <ctype.h> # include <time.h> # ifdef USE_EXECNT # define WIN32_LEAN_AND_MEAN # include <windows.h> /* do the ugly deed */ # include <process.h> # if !defined( __BORLANDC__ ) # include <tlhelp32.h> # endif # if !defined( __BORLANDC__ ) && !defined( OS_OS2 ) # define wait my_wait static int my_wait( int *status ); # endif /* * execnt.c - execute a shell command on Windows NT and Windows 95/98 * * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). * The default is: * * /bin/sh -c % [ on UNIX/AmigaOS ] * cmd.exe /c % [ on Windows NT ] * * Each word must be an individual element in a jam variable value. * * In $(JAMSHELL), % expands to the command string and ! expands to * the slot number (starting at 1) for multiprocess (-j) invocations. * If $(JAMSHELL) doesn't include a %, it is tacked on as the last * argument. * * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work! * * External routines: * execcmd() - launch an async command execution * execwait() - wait and drive at most one execution completion * * Internal routines: * onintr() - bump intr to note command interruption * * 04/08/94 (seiwald) - Coherent/386 support added. * 05/04/94 (seiwald) - async multiprocess interface * 01/22/95 (seiwald) - $(JAMSHELL) support * 06/02/97 (gsar) - full async multiprocess support for Win32 */ static int intr = 0; static int cmdsrunning = 0; static void (*istat)( int ); static int is_nt_351 = 0; static int is_win95 = 1; static int is_win95_defined = 0; static struct { int pid; /* on win32, a real process handle */ void (*func)( void *closure, int status, timing_info* ); void *closure; char *tempfile; } cmdtab[ MAXJOBS ] = {{0}}; static void set_is_win95( void ) { OSVERSIONINFO os_info; os_info.dwOSVersionInfoSize = sizeof(os_info); os_info.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; GetVersionEx( &os_info ); is_win95 = (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); is_win95_defined = 1; /* now, test wether we're running Windows 3.51 */ /* this is later used to limit the system call command length */ if (os_info.dwPlatformId == VER_PLATFORM_WIN32_NT) is_nt_351 = os_info.dwMajorVersion == 3; } int maxline() { if (!is_win95_defined) set_is_win95(); /* Set the maximum command line length according to the OS */ return is_nt_351 ? 996 : is_win95 ? 1023 : 2047; } static void free_argv( char** args ) { free( args[0] ); free( args ); } /* Convert a command string into arguments for spawnvp. The original * code, inherited from ftjam, tried to break up every argument on the * command-line, dealing with quotes, but that's really a waste of * time on Win32, at least. It turns out that all you need to do is * get the raw path to the executable in the first argument to * spawnvp, and you can pass all the rest of the command-line * arguments to spawnvp in one, un-processed string. * * New strategy: break the string in at most one place. */ static char** string_to_args( const char* string ) { int src_len; int in_quote; char* line; char const* src; char* dst; char** argv; /* drop leading and trailing whitespace if any */ while (isspace(*string)) ++string; src_len = strlen( string ); while ( src_len > 0 && isspace( string[src_len - 1] ) ) --src_len; /* Copy the input string into a buffer we can modify */ line = (char*)malloc( src_len+1 ); if (!line) return 0; if ( DEBUG_PROFILE ) profile_memory( src_len+1 ); /* allocate the argv array. * element 0: stores the path to the executable * element 1: stores the command-line arguments to the executable * element 2: NULL terminator */ argv = (char**)malloc( 3 * sizeof(char*) ); if (!argv) { free( line ); return 0; } if ( DEBUG_PROFILE ) profile_memory( 3 * sizeof(char*) ); /* Strip quotes from the first command-line argument and find * where it ends. Quotes are illegal in Win32 pathnames, so we * don't need to worry about preserving escaped quotes here. * Spaces can't be escaped in Win32, only enclosed in quotes, so * removing backslash escapes is also a non-issue. */ in_quote = 0; for ( src = string, dst = line ; *src; src++ ) { if (*src == '"') in_quote = !in_quote; else if (!in_quote && isspace(*src)) break; else *dst++ = *src; } *dst++ = 0; argv[0] = line; /* skip whitespace in src */ while (isspace(*src)) ++src; argv[1] = dst; /* Copy the rest of the arguments verbatim */ src_len -= src - string; /* Use strncat because it appends a trailing nul */ *dst = 0; strncat(dst, src, src_len); argv[2] = 0; return argv; } /* process a "del" or "erase" command under Windows 95/98 */ static int process_del( char* command ) { char** arg; char* p = command, *q; int wildcard = 0, result = 0; /* first of all, skip the command itself */ if ( p[0] == 'd' ) p += 3; /* assumes "del..;" */ else if ( p[0] == 'e' ) p += 5; /* assumes "erase.." */ else return 1; /* invalid command */ /* process all targets independently */ for (;;) { /* skip leading spaces */ while ( *p && isspace(*p) ) p++; /* exit if we encounter an end of string */ if (!*p) return 0; /* ignore toggles/flags */ while (*p == '/') { p++; while ( *p && isalnum(*p) ) p++; while (*p && isspace(*p) ) ++p; } { int in_quote = 0; int wildcard = 0; int go_on = 1; q = p; while (go_on) { switch (*p) { case '"': in_quote = !in_quote; break; case '?': case '*': if (!in_quote) wildcard = 1; break; case '\0': if (in_quote) return 1; /* fall-through */ case ' ': case '\t': if (!in_quote) { int len = p - q; int result; char* line; /* q..p-1 contains the delete argument */ if ( len <= 0 ) return 1; line = (char*)malloc( len+4+1 ); if (!line) return 1; if ( DEBUG_PROFILE ) profile_memory( len+4+1 ); strncpy( line, "del ", 4 ); strncpy( line+4, q, len ); line[len+4] = '\0'; if ( wildcard ) result = system( line ); else result = !DeleteFile( line+4 ); free( line ); if (result) return 1; go_on = 0; } default: ; } p++; } /* while (go_on) */ } } } /* * onintr() - bump intr to note command interruption */ void onintr( int disp ) { intr++; printf( "...interrupted\n" ); } /* * can_spawn() - If the command is suitable for execution via spawnvp, * return a number >= the number of characters it would occupy on the * command-line. Otherwise, return zero. */ long can_spawn(char* command) { char *p; char inquote = 0; /* Move to the first non-whitespace */ command += strspn( command, " \t" ); p = command; /* Look for newlines and unquoted i/o redirection */ do { p += strcspn( p, "'\n\"<>|" ); switch (*p) { case '\n': /* skip over any following spaces */ while( isspace( *p ) ) ++p; /* Must use a .bat file if there is anything significant * following the newline */ if (*p) return 0; break; case '"': case '\'': if (p > command && p[-1] != '\\') { if (inquote == *p) inquote = 0; else if (inquote == 0) inquote = *p; } ++p; break; case '<': case '>': case '|': if (!inquote) return 0; ++p; break; } } while (*p); /* Return the number of characters the command will occupy */ return p - command; } void execnt_unit_test() { #if !defined(NDEBUG) /* vc6 preprocessor is broken, so assert with these strings gets * confused. Use a table instead. */ typedef struct test { char* command; int result; } test; test tests[] = { { "x", 0 }, { "x\n ", 0 }, { "x\ny", 1 }, { "x\n\n y", 1 }, { "echo x > foo.bar", 1 }, { "echo x < foo.bar", 1 }, { "echo x \">\" foo.bar", 0 }, { "echo x \"<\" foo.bar", 0 }, { "echo x \\\">\\\" foo.bar", 1 }, { "echo x \\\"<\\\" foo.bar", 1 } }; int i; for ( i = 0; i < sizeof(tests)/sizeof(*tests); ++i) { assert( !can_spawn( tests[i].command ) == tests[i].result ); } { char* long_command = malloc(MAXLINE + 10); assert( long_command != 0 ); memset( long_command, 'x', MAXLINE + 9 ); long_command[MAXLINE + 9] = 0; assert( can_spawn( long_command ) == MAXLINE + 9); free( long_command ); } { /* Work around vc6 bug; it doesn't like escaped string * literals inside assert */ char** argv = string_to_args("\"g++\" -c -I\"Foobar\""); char const expected[] = "-c -I\"Foobar\""; assert(!strcmp(argv[0], "g++")); assert(!strcmp(argv[1], expected)); free_argv(argv); } #endif } /* 64-bit arithmetic helpers */ /* Compute the carry bit from the addition of two 32-bit unsigned numbers */ #define add_carry_bit(a, b) ( (((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1 ) /* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1 and h2l2 */ #define add_64_hi(h1, l1, h2, l2) ((h1) + (h2) + add_carry_bit(l1, l2)) /* Add two 64-bit unsigned numbers, h1l1 and h2l2 */ static FILETIME add_64( unsigned long h1, unsigned long l1, unsigned long h2, unsigned long l2) { FILETIME result; result.dwLowDateTime = l1 + l2; result.dwHighDateTime = add_64_hi(h1, l1, h2, l2); return result; } static FILETIME add_FILETIME(FILETIME t1, FILETIME t2) { return add_64( t1.dwHighDateTime, t1.dwLowDateTime , t2.dwHighDateTime, t2.dwLowDateTime); } static FILETIME negate_FILETIME(FILETIME t) { /* 2s complement negation */ return add_64(~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1); } /* COnvert a FILETIME to a number of seconds */ static double filetime_seconds(FILETIME t) { return t.dwHighDateTime * (double)(1UL << 31) * 2 + t.dwLowDateTime * 1.0e-7; } static void record_times(int pid, timing_info* time) { FILETIME creation, exit, kernel, user; if (GetProcessTimes((HANDLE)pid, &creation, &exit, &kernel, &user)) { /* Compute the elapsed time */ #if 0 /* We don't know how to get this number this on Unix */ time->elapsed = filetime_seconds( add_FILETIME( exit, negate_FILETIME(creation) ) ); #endif time->system = filetime_seconds(kernel); time->user = filetime_seconds(user); } CloseHandle((HANDLE)pid); } /* * execcmd() - launch an async command execution */ void execcmd( char *string, void (*func)( void *closure, int status, timing_info* ), void *closure, LIST *shell ) { int pid; int slot; int raw_cmd = 0 ; char *argv_static[ MAXARGC + 1 ]; /* +1 for NULL */ char **argv = argv_static; char *p; /* Check to see if we need to hack around the line-length limitation. */ /* Look for a JAMSHELL setting of "%", indicating that the command * should be invoked directly */ if ( shell && !strcmp(shell->string,"%") && !list_next(shell) ) { raw_cmd = 1; shell = 0; } if ( !is_win95_defined ) set_is_win95(); /* Find a slot in the running commands table for this one. */ if ( is_win95 ) { /* only synchronous spans are supported on Windows 95/98 */ slot = 0; } else { for( slot = 0; slot < MAXJOBS; slot++ ) if( !cmdtab[ slot ].pid ) break; } if( slot == MAXJOBS ) { printf( "no slots for child!\n" ); exit( EXITBAD ); } if( !cmdtab[ slot ].tempfile ) { const char *tempdir = path_tmpdir(); DWORD procID = GetCurrentProcessId(); /* SVA - allocate 64 other just to be safe */ cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 64 ); if ( DEBUG_PROFILE ) profile_memory( strlen( tempdir ) + 64 ); sprintf( cmdtab[ slot ].tempfile, "%s\\jam%d-%02d.bat", tempdir, procID, slot ); } /* Trim leading, ending white space */ while( isspace( *string ) ) ++string; /* Write to .BAT file unless the line would be too long and it * meets the other spawnability criteria. */ if( raw_cmd && can_spawn( string ) >= MAXLINE ) { if( DEBUG_EXECCMD ) printf("Executing raw command directly\n"); } else { FILE *f = 0; int tries = 0; raw_cmd = 0; /* Write command to bat file. For some reason this open can fails intermitently. But doing some retries works. Most likely this is due to a previously existing file of the same name that happens to be opened by an active virus scanner. Pointed out, and fix by Bronek Kozicki. */ for (; !f && tries < 4; ++tries) { f = fopen( cmdtab[ slot ].tempfile, "w" ); if ( !f && tries < 4 ) Sleep( 250 ); } if (!f) { printf( "failed to write command file!\n" ); exit( EXITBAD ); } fputs( string, f ); fclose( f ); string = cmdtab[ slot ].tempfile; if( DEBUG_EXECCMD ) { if (shell) printf("using user-specified shell: %s", shell->string); else printf("Executing through .bat file\n"); } } /* Forumulate argv */ /* If shell was defined, be prepared for % and ! subs. */ /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */ if( shell ) { int i; char jobno[4]; int gotpercent = 0; sprintf( jobno, "%d", slot + 1 ); for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) ) { switch( shell->string[0] ) { case '%': argv[i] = string; gotpercent++; break; case '!': argv[i] = jobno; break; default: argv[i] = shell->string; } if( DEBUG_EXECCMD ) printf( "argv[%d] = '%s'\n", i, argv[i] ); } if( !gotpercent ) argv[i++] = string; argv[i] = 0; } else if (raw_cmd) { argv = string_to_args(string); } else { /* don't worry, this is ignored on Win95/98, see later.. */ argv[0] = "cmd.exe"; argv[1] = "/Q/C"; /* anything more is non-portable */ argv[2] = string; argv[3] = 0; } /* Catch interrupts whenever commands are running. */ if( !cmdsrunning++ ) istat = signal( SIGINT, onintr ); /* Start the command */ /* on Win95, we only do a synchronous call */ if ( is_win95 ) { static const char* hard_coded[] = { "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir", "ren", "rename", "move", 0 }; const char** keyword; int len, spawn = 1; int result; timing_info time = {0,0}; for ( keyword = hard_coded; keyword[0]; keyword++ ) { len = strlen( keyword[0] ); if ( strnicmp( string, keyword[0], len ) == 0 && !isalnum(string[len]) ) { /* this is one of the hard coded symbols, use 'system' to run */ /* them.. except for "del"/"erase" */ if ( keyword - hard_coded < 2 ) result = process_del( string ); else result = system( string ); spawn = 0; break; } } if (spawn) { char** args; /* convert the string into an array of arguments */ /* we need to take care of double quotes !! */ args = string_to_args( string ); if ( args ) { #if 0 char** arg; fprintf( stderr, "%s: ", args[0] ); arg = args+1; while ( arg[0] ) { fprintf( stderr, " {%s}", arg[0] ); arg++; } fprintf( stderr, "\n" ); #endif result = spawnvp( P_WAIT, args[0], args ); record_times(result, &time); free_argv( args ); } else result = 1; } func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK, &time ); return; } if( DEBUG_EXECCMD ) { char **argp = argv; printf("Executing command"); while(*argp != 0) { printf(" [%s]", *argp); argp++; } printf("\n"); } /* the rest is for Windows NT only */ /* spawn doesn't like quotes around the command name */ if ( argv[0][0] == '"') { int l = strlen(argv[0]); /* Clobber any closing quote, shortening the string by one * element */ if (argv[0][l-1] == '"') argv[0][l-1] = '\0'; /* Move everything *including* the original terminating zero * back one place in memory, covering up the opening quote */ memmove(argv[0],argv[0]+1,l); } if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 ) { perror( "spawn" ); exit( EXITBAD ); } /* Save the operation for execwait() to find. */ cmdtab[ slot ].pid = pid; cmdtab[ slot ].func = func; cmdtab[ slot ].closure = closure; /* Wait until we're under the limit of concurrent commands. */ /* Don't trust globs.jobs alone. */ while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs ) if( !execwait() ) break; if (argv != argv_static) { free_argv(argv); } } /* * execwait() - wait and drive at most one execution completion */ int execwait() { int i; int status, w; int rstat; timing_info time; /* Handle naive make1() which doesn't know if cmds are running. */ if( !cmdsrunning ) return 0; if ( is_win95 ) return 0; /* Pick up process pid and status */ while( ( w = wait( &status ) ) == -1 && errno == EINTR ) ; if( w == -1 ) { printf( "child process(es) lost!\n" ); perror("wait"); exit( EXITBAD ); } /* Find the process in the cmdtab. */ for( i = 0; i < MAXJOBS; i++ ) if( w == cmdtab[ i ].pid ) break; if( i == MAXJOBS ) { printf( "waif child found!\n" ); exit( EXITBAD ); } record_times(cmdtab[i].pid, &time); /* Clear the temp file */ if ( cmdtab[i].tempfile ) unlink( cmdtab[ i ].tempfile ); /* Drive the completion */ if( !--cmdsrunning ) signal( SIGINT, istat ); if( intr ) rstat = EXEC_CMD_INTR; else if( w == -1 || status != 0 ) rstat = EXEC_CMD_FAIL; else rstat = EXEC_CMD_OK; cmdtab[ i ].pid = 0; /* SVA don't leak temp files */ if(cmdtab[i].tempfile != NULL) { free(cmdtab[i].tempfile); cmdtab[i].tempfile = NULL; } (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time ); return 1; } # if !defined( __BORLANDC__ ) /* The possible result codes from check_process_exit, below */ typedef enum { process_error, process_active, process_finished } process_state; /* Helper for my_wait() below. Checks to see whether the process has * exited and if so, records timing information. */ static process_state check_process_exit( HANDLE process /* The process we're looking at */ , int* status /* Storage for the finished process' exit * code. If the process is still active * this location is left untouched. */ , HANDLE* active_handles /* Storage for the process handle if it is * found to be still active, or NULL. The * process is treated as though it is * complete. */ , int* num_active /* The current length of active_handles */ ) { DWORD exitcode; process_state result; /* Try to get the process exit code */ if (!GetExitCodeProcess(process, &exitcode)) { result = process_error; /* signal an error */ } else if ( exitcode == STILL_ACTIVE /* If the process is still active */ && active_handles != 0 /* and we've been passed a place to buffer it */ ) { active_handles[(*num_active)++] = process; /* push it onto the active stack */ result = process_active; } else { *status = (int)((exitcode & 0xff) << 8); result = process_finished; } return result; } static double running_time(HANDLE process) { FILETIME creation, exit, kernel, user, current; if (GetProcessTimes(process, &creation, &exit, &kernel, &user)) { /* Compute the elapsed time */ GetSystemTimeAsFileTime(¤t); { double delta = filetime_seconds( add_FILETIME( current, negate_FILETIME(creation) ) ); return delta; } } return 0.0; } static double creation_time(HANDLE process) { FILETIME creation, exit, kernel, user, current; if (GetProcessTimes(process, &creation, &exit, &kernel, &user)) { return filetime_seconds(creation); } return 0.0; } /* it's just stupidly silly that one has to do this! */ typedef struct PROCESS_BASIC_INFORMATION__ { LONG ExitStatus; PVOID PebBaseAddress; ULONG AffinityMask; LONG BasePriority; ULONG UniqueProcessId; ULONG InheritedFromUniqueProcessId; } PROCESS_BASIC_INFORMATION_; typedef LONG (__stdcall * NtQueryInformationProcess__)( HANDLE ProcessHandle, LONG ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); static NtQueryInformationProcess__ NtQueryInformationProcess_ = NULL; static HMODULE NTDLL_ = NULL; DWORD get_process_id(HANDLE process) { PROCESS_BASIC_INFORMATION_ pinfo; if ( ! NtQueryInformationProcess_ ) { if ( ! NTDLL_ ) { NTDLL_ = GetModuleHandleA("ntdll"); } if ( NTDLL_ ) { NtQueryInformationProcess_ = (NtQueryInformationProcess__)GetProcAddress( NTDLL_,"NtQueryInformationProcess" ); } } if ( NtQueryInformationProcess_ ) { LONG r = (*NtQueryInformationProcess_)( process,/* ProcessBasicInformation == */ 0,&pinfo,sizeof(PROCESS_BASIC_INFORMATION_),NULL); return pinfo.UniqueProcessId; } else { return 0; } } /* not really optimal, or efficient, but it's easier this way, and it's not like we are going to be killing thousands, or even tens or processes. */ static void kill_all(DWORD pid, HANDLE process) { HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; if ( !pid ) { pid = get_process_id(process); } process_snapshot_h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); if (INVALID_HANDLE_VALUE != process_snapshot_h) { BOOL ok = TRUE; PROCESSENTRY32 pinfo; pinfo.dwSize = sizeof(PROCESSENTRY32); for ( ok = Process32First(process_snapshot_h,&pinfo); TRUE == ok; ok = Process32Next(process_snapshot_h,&pinfo) ) { if (pinfo.th32ParentProcessID == pid) { /* found a child, recurse to kill it and anything else below it */ HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pinfo.th32ProcessID); if (NULL != ph) { kill_all(pinfo.th32ProcessID,ph); CloseHandle(ph); } } } CloseHandle(process_snapshot_h); } /* now that the children are all dead, kill the root */ TerminateProcess(process,-2); } /* recursive check if first process is parent (directly or indirectly) of the latter one. Both processes are passed as process ids, not handles */ static int is_parent_child(DWORD parent, DWORD child) { HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; if (!child) return 0; if (parent == child) return 1; process_snapshot_h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); if (INVALID_HANDLE_VALUE != process_snapshot_h) { BOOL ok = TRUE; PROCESSENTRY32 pinfo; pinfo.dwSize = sizeof(PROCESSENTRY32); for ( ok = Process32First(process_snapshot_h, &pinfo); ok == TRUE; ok = Process32Next(process_snapshot_h, &pinfo) ) { if (pinfo.th32ProcessID == child) { /* Unfortunately, process ids are not really unique. There might be spurious "parent and child" relationship match between two non-related processes if real parent process of a given process has exited (while child process kept running as an "orphan") and the process id of such parent process has been reused by internals of the operating system when creating another process. Thus additional check is needed - process creation time. */ double tchild = 0.0; double tparent = 0.0; HANDLE hchild = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ProcessID); if (hchild != 0) { HANDLE hparent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ParentProcessID); if (hparent != 0) { tchild = creation_time(hchild); tparent = creation_time(hparent); CloseHandle(hparent); } CloseHandle(hchild); } CloseHandle(process_snapshot_h); /* was child created before alleged parent? */ if (tchild == 0.0 || tparent == 0.0 || tchild < tparent) return 0; /* csrss.exe may display message box like following: xyz.exe - Unable To Locate Component This application has failed to start because boost_foo-bar.dll was not found. Re-installing the application may fix the problem This actually happens when starting test process that depends on a dynamic library which failed to build. We want to automatically close these message boxes even though csrss.exe is not our child process. We may depend on the fact that (in all current versions of Windows) csrss.exe is indirectly child of System process, which always has process id == 4 */ if (stricmp(pinfo.szExeFile, "csrss.exe") == 0) { if (is_parent_child(4, pinfo.th32ParentProcessID)) return 1; } return is_parent_child(parent, pinfo.th32ParentProcessID); } } CloseHandle(process_snapshot_h); } return 0; } typedef struct PROCESS_HANDLE_ID {HANDLE h; DWORD pid;} PROCESS_HANDLE_ID; /* This function is called by the operating system for each topmost window. */ BOOL CALLBACK window_enum(HWND hwnd, LPARAM lParam) { char buf[7] = {0}; PROCESS_HANDLE_ID p = *((PROCESS_HANDLE_ID*) (lParam)); DWORD pid = 0; DWORD tid = 0; /* we want to find and close any window that: 1. is visible and 2. is a dialog and 3. is displayed by any of our child processes */ if (!IsWindowVisible(hwnd)) return TRUE; if (!GetClassNameA(hwnd, buf, sizeof(buf))) return TRUE; /* failed to read class name; presume it's not a dialog */ if (strcmp(buf, "#32770") != 0) return TRUE; /* not a dialog */ /* GetWindowThreadProcessId returns 0 on error, otherwise thread id of window message pump thread */ tid = GetWindowThreadProcessId(hwnd, &pid); if (tid && is_parent_child(p.pid, pid)) { /* ask really nice */ PostMessageA(hwnd, WM_CLOSE, 0, 0); /* now wait and see if it worked. If not, insist */ if (WaitForSingleObject(p.h, 200) == WAIT_TIMEOUT) { PostThreadMessageA(tid, WM_QUIT, 0, 0); WaitForSingleObject(p.h, 300); } /* done, we do not want to check any other window now */ return FALSE; } return TRUE; } static void close_alert(HANDLE process) { DWORD pid = get_process_id(process); /* If process already exited or we just cannot get its process id, do not go any further */ if (pid) { PROCESS_HANDLE_ID p = {process, pid}; EnumWindows(&window_enum, (LPARAM) &p); } } static int my_wait( int *status ) { int i, num_active = 0; DWORD exitcode, waitcode; HANDLE active_handles[MAXJOBS]; /* first see if any non-waited-for processes are dead, * and return if so. */ for ( i = 0; i < globs.jobs; i++ ) { int pid = cmdtab[i].pid; if ( pid ) { process_state state = check_process_exit((HANDLE)pid, status, active_handles, &num_active); if ( state == process_error ) goto FAILED; else if ( state == process_finished ) return pid; } } /* if a child exists, wait for it to die */ if ( !num_active ) { errno = ECHILD; return -1; } if ( globs.timeout > 0 ) { unsigned int alert_wait = 1; /* with a timeout we wait for a finish or a timeout, we check every second to see if something timed out */ for (waitcode = WAIT_TIMEOUT; waitcode == WAIT_TIMEOUT; ++alert_wait) { waitcode = WaitForMultipleObjects( num_active, active_handles, FALSE, 1*1000 /* 1 second */ ); if ( waitcode == WAIT_TIMEOUT ) { /* check if any jobs have surpassed the maximum run time. */ for ( i = 0; i < num_active; ++i ) { double t = running_time(active_handles[i]); /* periodically (each 5 secs) check and close message boxes displayed by any of our child processes */ if ((alert_wait % ((unsigned int) 5)) == 0) close_alert(active_handles[i]); if ( t > (double)globs.timeout ) { /* the job may have left an alert dialog around, try and get rid of it before killing */ close_alert(active_handles[i]); /* we have a "runaway" job, kill it */ kill_all(0,active_handles[i]); /* indicate the job "finished" so we query its status below */ waitcode = WAIT_ABANDONED_0+i; } } } } } else { /* no timeout, so just wait indefinately for something to finish */ waitcode = WaitForMultipleObjects( num_active, active_handles, FALSE, INFINITE ); } if ( waitcode != WAIT_FAILED ) { if ( waitcode >= WAIT_ABANDONED_0 && waitcode < WAIT_ABANDONED_0 + num_active ) i = waitcode - WAIT_ABANDONED_0; else i = waitcode - WAIT_OBJECT_0; if ( check_process_exit(active_handles[i], status, 0, 0) == process_finished ) return (int)active_handles[i]; } FAILED: errno = GetLastError(); return -1; } # endif /* !__BORLANDC__ */ # endif /* USE_EXECNT */

On Sunday 16 July 2006 19:03, Bronek Kozicki wrote:
Vladimir Prus wrote:
Overall, looks reasonable. Would you like to address the comments and sent the patch again, or it will be more convenient if I commit the patch right now and you'll send further patches when you have the time?
Hi Volodya
Here is a new version of execnt.c . I will be happy if you can review it. It's no tested yet, I will test it tonight.
Hi Bronek, I like this patch. It's now committed. I've just one, largely irrelevant comment to make: +/* recursive check if first process is parent (directly or indirectly) of +the latter one. Both processes are passed as process ids, not handles */ +static int Here, you should use the "former" and "latter", or "first" and "second". Using "first process" followed by "latter one" strikes me as grammar inconsistency. Anyway, that's not something that matters much. Thanks again, Volodya

Vladimir Prus wrote:
I like this patch. It's now committed.
I'm sorry to say this, but it was still buggy. Attached you will find relevant patch B. --- C:\Documents and Settings\Bronek Kozicki\Desktop\execnt.c Fri Jul 21 08:26:21 2006 UTC +++ E:\DEVEL\BOOST_RTEST\boost\tools\jam\src\execnt.c Fri Jul 21 08:22:47 2006 UTC @@ -1002,22 +1002,22 @@ CloseHandle(ph); } } } CloseHandle(process_snapshot_h); } /* now that the children are all dead, kill the root */ TerminateProcess(process,-2); } -/* recursive check if first process is parent (directly or indirectly) of -the latter one. Both processes are passed as process ids, not handles */ +/* Recursive check if first process is parent (directly or indirectly) of +the second one. Both processes are passed as process ids, not handles */ static int is_parent_child(DWORD parent, DWORD child) { HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; if (!child) return 0; if (parent == child) return 1; @@ -1035,59 +1035,68 @@ if (pinfo.th32ProcessID == child) { /* Unfortunately, process ids are not really unique. There might be spurious "parent and child" relationship match between two non-related processes if real parent process of a given process has exited (while child process kept running as an "orphan") and the process id of such parent process has been reused by internals of the operating system when creating another process. Thus additional check is needed - process - creation time. */ + creation time. This check may fail (ie. return 0) for system + processes due to insufficient privileges, and that's OK. */ double tchild = 0.0; double tparent = 0.0; HANDLE hchild = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ProcessID); + + CloseHandle(process_snapshot_h); + + /* csrss.exe may display message box like following: + xyz.exe - Unable To Locate Component + This application has failed to start because + boost_foo-bar.dll was not found. Re-installing the + application may fix the problem + This actually happens when starting test process that depends + on a dynamic library which failed to build. We want to + automatically close these message boxes even though csrss.exe + is not our child process. We may depend on the fact that (in + all current versions of Windows) csrss.exe is directly + child of smss.exe process, which in turn is directly child of + System process, which always has process id == 4 . + This check must be performed before comparison of process + creation time */ + if (stricmp(pinfo.szExeFile, "csrss.exe") == 0 + || stricmp(pinfo.szExeFile, "smss.exe") == 0) + { + if (is_parent_child(4, pinfo.th32ParentProcessID)) + return 1; + } + if (hchild != 0) { HANDLE hparent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ParentProcessID); if (hparent != 0) { tchild = creation_time(hchild); tparent = creation_time(hparent); CloseHandle(hparent); } CloseHandle(hchild); } - CloseHandle(process_snapshot_h); - /* was child created before alleged parent? */ + /* return 0 if one of the following is true: + 1. we failed to read process creation time + 2. child was created before alleged parent */ if (tchild == 0.0 || tparent == 0.0 || tchild < tparent) return 0; - /* csrss.exe may display message box like following: - xyz.exe - Unable To Locate Component - This application has failed to start because - boost_foo-bar.dll was not found. Re-installing the - application may fix the problem - This actually happens when starting test process that depends - on a dynamic library which failed to build. We want to - automatically close these message boxes even though csrss.exe - is not our child process. We may depend on the fact that (in - all current versions of Windows) csrss.exe is indirectly - child of System process, which always has process id == 4 */ - if (stricmp(pinfo.szExeFile, "csrss.exe") == 0) - { - if (is_parent_child(4, pinfo.th32ParentProcessID)) - return 1; - } - return is_parent_child(parent, pinfo.th32ParentProcessID); } } CloseHandle(process_snapshot_h); } return 0; }
participants (3)
-
Bronek Kozicki
-
Christopher Kohlhoff
-
Vladimir Prus