From: Gurusamy Sarathy <gsar@cpan.org>
Date: Mon, 22 Mar 1999 07:07:06 +0000 (+0000)
Subject: implement win32_spawnvp() internally, making it return true PIDs
X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=0aaad0ff610b01c0682abfc20594c83a6d49f148;p=p5sagit%2Fp5-mst-13.2.git

implement win32_spawnvp() internally, making it return true PIDs
for asynchronous spawns; fix win32_kill() to always deal with
PIDs

p4raw-id: //depot/perl@3123
---

diff --git a/win32/win32.c b/win32/win32.c
index 726c7c5..4d7721e 100644
--- a/win32/win32.c
+++ b/win32/win32.c
@@ -95,7 +95,9 @@ static BOOL		has_shell_metachars(char *ptr);
 static long		filetime_to_clock(PFILETIME ft);
 static BOOL		filetime_from_time(PFILETIME ft, time_t t);
 static char *		get_emd_part(char *leading, char *trailing, ...);
-static void		remove_dead_process(HANDLE deceased);
+static void		remove_dead_process(long deceased);
+static long		find_pid(int pid);
+static char *		qualified_path(const char *cmd);
 
 HANDLE	w32_perldll_handle = INVALID_HANDLE_VALUE;
 static DWORD	w32_platform = (DWORD)-1;
@@ -841,42 +843,40 @@ chown(const char *path, uid_t owner, gid_t group)
     return 0;
 }
 
-static void
-remove_dead_process(HANDLE deceased)
+static long
+find_pid(int pid)
 {
-#ifndef USE_RTL_WAIT
-    int child;
+    long child;
     for (child = 0 ; child < w32_num_children ; ++child) {
-	if (w32_child_pids[child] == deceased) {
-	    Copy(&w32_child_pids[child+1], &w32_child_pids[child],
-		 (w32_num_children-child-1), HANDLE);
-	    w32_num_children--;
-	    break;
-	}
+	if (w32_child_pids[child] == pid)
+	    return child;
+    }
+    return -1;
+}
+
+static void
+remove_dead_process(long child)
+{
+    if (child >= 0) {
+	CloseHandle(w32_child_handles[child]);
+	Copy(&w32_child_handles[child+1], &w32_child_handles[child],
+	     (w32_num_children-child-1), HANDLE);
+	Copy(&w32_child_pids[child+1], &w32_child_pids[child],
+	     (w32_num_children-child-1), DWORD);
+	w32_num_children--;
     }
-#endif
 }
 
 DllExport int
 win32_kill(int pid, int sig)
 {
-#ifdef USE_RTL_WAIT
-    HANDLE hProcess= OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
-#else
-    HANDLE hProcess = (HANDLE) pid;
-#endif
-
-    if (hProcess == NULL) {
-	croak("kill process failed!\n");
-    }
-    else {
-	if (!TerminateProcess(hProcess, sig))
-	    croak("kill process failed!\n");
+    HANDLE hProcess;
+    hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
+    if (hProcess && TerminateProcess(hProcess, sig))
 	CloseHandle(hProcess);
-
-	/* WaitForMultipleObjects() on a pid that was killed returns error
-	 * so if we know the pid is gone we remove it from process list */
-	remove_dead_process(hProcess);
+    else {
+	errno = EINVAL;
+	return -1;
     }
     return 0;
 }
@@ -1135,27 +1135,40 @@ win32_utime(const char *filename, struct utimbuf *times)
 DllExport int
 win32_waitpid(int pid, int *status, int flags)
 {
-    int rc;
+    int retval = -1;
     if (pid == -1) 
-      return win32_wait(status);
+	return win32_wait(status);
     else {
-      rc = cwait(status, pid, WAIT_CHILD);
-    /* cwait() returns "correctly" on Borland */
+	long child = find_pid(pid);
+	if (child >= 0) {
+	    HANDLE hProcess = w32_child_handles[child];
+	    DWORD waitcode = WaitForSingleObject(hProcess, INFINITE);
+	    if (waitcode != WAIT_FAILED) {
+		if (GetExitCodeProcess(hProcess, &waitcode)) {
+		    *status = (int)((waitcode & 0xff) << 8);
+		    retval = (int)w32_child_pids[child];
+		    remove_dead_process(child);
+		    return retval;
+		}
+	    }
+	    else
+		errno = ECHILD;
+	}
+	else {
+	    retval = cwait(status, pid, WAIT_CHILD);
+	    /* cwait() returns "correctly" on Borland */
 #ifndef __BORLANDC__
-    if (status)
-	*status *= 256;
+	    if (status)
+		*status *= 256;
 #endif
-      remove_dead_process((HANDLE)pid);
+	}
     }
-    return rc >= 0 ? pid : rc;                
+    return retval >= 0 ? pid : retval;                
 }
 
 DllExport int
 win32_wait(int *status)
 {
-#ifdef USE_RTL_WAIT
-    return wait(status);
-#else
     /* XXX this wait emulation only knows about processes
      * spawned via win32_spawnvp(P_NOWAIT, ...).
      */
@@ -1169,7 +1182,7 @@ win32_wait(int *status)
 
     /* if a child exists, wait for it to die */
     waitcode = WaitForMultipleObjects(w32_num_children,
-				      w32_child_pids,
+				      w32_child_handles,
 				      FALSE,
 				      INFINITE);
     if (waitcode != WAIT_FAILED) {
@@ -1178,13 +1191,10 @@ win32_wait(int *status)
 	    i = waitcode - WAIT_ABANDONED_0;
 	else
 	    i = waitcode - WAIT_OBJECT_0;
-	if (GetExitCodeProcess(w32_child_pids[i], &exitcode) ) {
-	    CloseHandle(w32_child_pids[i]);
+	if (GetExitCodeProcess(w32_child_handles[i], &exitcode) ) {
 	    *status = (int)((exitcode & 0xff) << 8);
 	    retval = (int)w32_child_pids[i];
-	    Copy(&w32_child_pids[i+1], &w32_child_pids[i],
-		 (w32_num_children-i-1), HANDLE);
-	    w32_num_children--;
+	    remove_dead_process(i);
 	    return retval;
 	}
     }
@@ -1192,8 +1202,6 @@ win32_wait(int *status)
 FAILED:
     errno = GetLastError();
     return -1;
-
-#endif
 }
 
 static UINT timerid = 0;
@@ -1791,16 +1799,10 @@ win32_pclose(FILE *pf)
     win32_fclose(pf);
     SvIVX(sv) = 0;
 
-    remove_dead_process((HANDLE)childpid);
+    if (win32_waitpid(childpid, &status, 0) == -1)
+        return -1;
 
-    /* wait for the child */
-    if (cwait(&status, childpid, WAIT_CHILD) == -1)
-        return (-1);
-    /* cwait() returns "correctly" on Borland */
-#ifndef __BORLANDC__
-    status *= 256;
-#endif
-    return (status);
+    return status;
 
 #endif /* USE_RTL_POPEN */
 }
@@ -1993,26 +1995,212 @@ win32_chdir(const char *dir)
     return chdir(dir);
 }
 
+static char *
+create_command_line(const char* command, const char * const *args)
+{
+    int index;
+    char *cmd, *ptr, *arg;
+    STRLEN len = strlen(command) + 1;
+
+    for (index = 0; (ptr = (char*)args[index]) != NULL; ++index)
+	len += strlen(ptr) + 1;
+
+    New(1310, cmd, len, char);
+    ptr = cmd;
+    strcpy(ptr, command);
+    ptr += strlen(ptr);
+    *ptr++ = ' ';
+
+    for (index = 0; (arg = (char*)args[index]) != NULL; ++index) {
+	strcpy(ptr, arg);
+	ptr += strlen(ptr);
+	if ((char*)args[index+1] != NULL)
+	    *ptr++ = ' ';
+    }
+
+    return cmd;
+}
+
+static char *
+qualified_path(const char *cmd)
+{
+    char *pathstr;
+    char *fullcmd, *curfullcmd;
+    STRLEN cmdlen = 0;
+    int has_slash = 0;
+
+    if (!cmd)
+	return Nullch;
+    fullcmd = (char*)cmd;
+    while (*fullcmd) {
+	if (*fullcmd == '/' || *fullcmd == '\\')
+	    has_slash++;
+	fullcmd++;
+	cmdlen++;
+    }
+
+    /* look in PATH */
+    pathstr = win32_getenv("PATH");
+    New(0, fullcmd, MAX_PATH+1, char);
+    curfullcmd = fullcmd;
+
+    while (1) {
+	DWORD res;
+
+	/* start by appending the name to the current prefix */
+	strcpy(curfullcmd, cmd);
+	curfullcmd += cmdlen;
+
+	/* if it doesn't end with '.', or has no extension, try adding
+	 * a trailing .exe first */
+	if (cmd[cmdlen-1] != '.'
+	    && (cmdlen < 4 || cmd[cmdlen-4] != '.'))
+	{
+	    strcpy(curfullcmd, ".exe");
+	    res = GetFileAttributes(fullcmd);
+	    if (res != 0xFFFFFFFF && !(res & FILE_ATTRIBUTE_DIRECTORY))
+		return fullcmd;
+	    *curfullcmd = '\0';
+	}
+
+	/* that failed, try the bare name */
+	res = GetFileAttributes(fullcmd);
+	if (res != 0xFFFFFFFF && !(res & FILE_ATTRIBUTE_DIRECTORY))
+	    return fullcmd;
+
+	/* quit if no other path exists, or if cmd already has path */
+	if (!pathstr || !*pathstr || has_slash)
+	    break;
+
+	/* skip leading semis */
+	while (*pathstr == ';')
+	    pathstr++;
+
+	/* build a new prefix from scratch */
+	curfullcmd = fullcmd;
+	while (*pathstr && *pathstr != ';') {
+	    if (*pathstr == '"') {	/* foo;"baz;etc";bar */
+		pathstr++;		/* skip initial '"' */
+		while (*pathstr && *pathstr != '"') {
+		    if (curfullcmd-fullcmd < MAX_PATH-cmdlen-5)
+			*curfullcmd++ = *pathstr;
+		    pathstr++;
+		}
+		if (*pathstr)
+		    pathstr++;		/* skip trailing '"' */
+	    }
+	    else {
+		if (curfullcmd-fullcmd < MAX_PATH-cmdlen-5)
+		    *curfullcmd++ = *pathstr;
+		pathstr++;
+	    }
+	}
+	if (*pathstr)
+	    pathstr++;			/* skip trailing semi */
+	if (curfullcmd > fullcmd	/* append a dir separator */
+	    && curfullcmd[-1] != '/' && curfullcmd[-1] != '\\')
+	{
+	    *curfullcmd++ = '\\';
+	}
+    }
+GIVE_UP:
+    Safefree(fullcmd);
+    return Nullch;
+}
+
+/* XXX this needs to be made more compatible with the spawnvp()
+ * provided by the various RTLs.  In particular, searching for
+ * *.{com,bat,cmd} files (as done by the RTLs) is unimplemented.
+ * This doesn't significantly affect perl itself, because we
+ * always invoke things using PERL5SHELL if a direct attempt to
+ * spawn the executable fails.
+ * 
+ * XXX splitting and rejoining the commandline between do_aspawn()
+ * and win32_spawnvp() could also be avoided.
+ */
+
 DllExport int
 win32_spawnvp(int mode, const char *cmdname, const char *const *argv)
 {
-    int status;
+#ifdef USE_RTL_SPAWNVP
+    return spawnvp(mode, cmdname, (char * const *)argv);
+#else
+    DWORD ret;
+    STARTUPINFO StartupInfo;
+    PROCESS_INFORMATION ProcessInformation;
+    DWORD create = 0;
+
+    char *cmd = create_command_line(cmdname, strcmp(cmdname, argv[0]) == 0
+			     	             ? &argv[1] : argv);
+    char *fullcmd = Nullch;
+
+    switch(mode) {
+    case P_NOWAIT:	/* asynch + remember result */
+	if (w32_num_children >= MAXIMUM_WAIT_OBJECTS) {
+	    errno = EAGAIN;
+	    ret = -1;
+	    goto RETVAL;
+	}
+	/* FALL THROUGH */
+    case P_WAIT:	/* synchronous execution */
+	break;
+    default:		/* invalid mode */
+	errno = EINVAL;
+	ret = -1;
+	goto RETVAL;
+    }
+    memset(&StartupInfo,0,sizeof(StartupInfo));
+    StartupInfo.cb = sizeof(StartupInfo);
+    StartupInfo.wShowWindow = SW_SHOWDEFAULT;
+
+RETRY:
+    if (!CreateProcess(cmdname,		/* search PATH to find executable */
+		       cmd,		/* executable, and its arguments */
+		       NULL,		/* process attributes */
+		       NULL,		/* thread attributes */
+		       TRUE,		/* inherit handles */
+		       create,		/* creation flags */
+		       NULL,		/* inherit environment */
+		       NULL,		/* inherit cwd */
+		       &StartupInfo,
+		       &ProcessInformation))
+    {
+	/* initial NULL argument to CreateProcess() does a PATH
+	 * search, but it always first looks in the directory
+	 * where the current process was started, which behavior
+	 * is undesirable for backward compatibility.  So we
+	 * jump through our own hoops by picking out the path
+	 * we really want it to use. */
+	if (!fullcmd) {
+	    fullcmd = qualified_path(cmdname);
+	    if (fullcmd) {
+		cmdname = fullcmd;
+		goto RETRY;
+	    }
+	}
+	errno = ENOENT;
+	ret = -1;
+	goto RETVAL;
+    }
 
-#ifndef USE_RTL_WAIT
-    if (mode == P_NOWAIT && w32_num_children >= MAXIMUM_WAIT_OBJECTS)
-	return -1;
-#endif
+    if (mode == P_NOWAIT) {
+	/* asynchronous spawn -- store handle, return PID */
+	w32_child_handles[w32_num_children] = ProcessInformation.hProcess;
+	ret = w32_child_pids[w32_num_children] = ProcessInformation.dwProcessId;
+	++w32_num_children;
+    }
+    else  {
+	WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
+	GetExitCodeProcess(ProcessInformation.hProcess, &ret);
+	CloseHandle(ProcessInformation.hProcess);
+    }
 
-    status = spawnvp(mode, cmdname, (char * const *) argv);
-#ifndef USE_RTL_WAIT
-    /* XXX For the P_NOWAIT case, Borland RTL returns pinfo.dwProcessId
-     * while VC RTL returns pinfo.hProcess. For purposes of the custom
-     * implementation of win32_wait(), we assume the latter.
-     */
-    if (mode == P_NOWAIT && status >= 0)
-	w32_child_pids[w32_num_children++] = (HANDLE)status;
+    CloseHandle(ProcessInformation.hThread);
+RETVAL:
+    Safefree(cmd);
+    Safefree(fullcmd);
+    return (int)ret;
 #endif
-    return status;
 }
 
 DllExport int
@@ -2567,9 +2755,8 @@ Perl_init_os_extras()
     w32_perlshell_tokens = Nullch;
     w32_perlshell_items = -1;
     w32_fdpid = newAV();		/* XXX needs to be in Perl_win32_init()? */
-#ifndef USE_RTL_WAIT
+    New(1313, w32_children, 1, child_tab);
     w32_num_children = 0;
-#endif
 
     /* these names are Activeware compatible */
     newXS("Win32::GetCwd", w32_GetCwd, file);
diff --git a/win32/win32.h b/win32/win32.h
index fef3cbc..0b8b710 100644
--- a/win32/win32.h
+++ b/win32/win32.h
@@ -161,8 +161,6 @@ struct tms {
 #pragma warn -pro	/* "call to function with no prototype" */
 #pragma warn -stu	/* "undefined structure 'foo'" */
 
-#define USE_RTL_WAIT	/* Borland has a working wait() */
-
 /* Borland is picky about a bare member function name used as its ptr */
 #ifdef PERL_OBJECT
 #define FUNC_NAME_TO_PTR(name)	&(name)
@@ -329,26 +327,29 @@ EXT void win32_strip_return(struct sv *sv);
 #endif
 
 #define HAVE_INTERP_INTERN
+typedef struct {
+    long	num;
+    DWORD	pids[MAXIMUM_WAIT_OBJECTS];
+} child_tab;
+
 struct interp_intern {
-    char *	w32_perlshell_tokens;
-    char **	w32_perlshell_vec;
-    long	w32_perlshell_items;
-    struct av *	w32_fdpid;
-#ifndef USE_RTL_WAIT
-    long	w32_num_children;
-    HANDLE	w32_child_pids[MAXIMUM_WAIT_OBJECTS];
-#endif
+    char *	perlshell_tokens;
+    char **	perlshell_vec;
+    long	perlshell_items;
+    struct av *	fdpid;
+    child_tab *	children;
+    HANDLE	child_handles[MAXIMUM_WAIT_OBJECTS];
 };
 
-#define w32_perlshell_tokens	(PL_sys_intern.w32_perlshell_tokens)
-#define w32_perlshell_vec	(PL_sys_intern.w32_perlshell_vec)
-#define w32_perlshell_items	(PL_sys_intern.w32_perlshell_items)
-#define w32_fdpid		(PL_sys_intern.w32_fdpid)
 
-#ifndef USE_RTL_WAIT
-#  define w32_num_children	(PL_sys_intern.w32_num_children)
-#  define w32_child_pids	(PL_sys_intern.w32_child_pids)
-#endif
+#define w32_perlshell_tokens	(PL_sys_intern.perlshell_tokens)
+#define w32_perlshell_vec	(PL_sys_intern.perlshell_vec)
+#define w32_perlshell_items	(PL_sys_intern.perlshell_items)
+#define w32_fdpid		(PL_sys_intern.fdpid)
+#define w32_children		(PL_sys_intern.children)
+#define w32_num_children	(w32_children->num)
+#define w32_child_pids		(w32_children->pids)
+#define w32_child_handles	(PL_sys_intern.child_handles)
 
 /* 
  * Now Win32 specific per-thread data stuff