/* Process handling for Windows. Copyright (C) 1996-2016 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include /* for _get_osfhandle */ #ifdef _MSC_VER # include /* for intptr_t */ #else # include #endif #include #include /* for msvc _beginthreadex, _endthreadex */ #include #include #include "makeint.h" #include "filedef.h" #include "variable.h" #include "sub_proc.h" #include "proc.h" #include "w32err.h" #include "debug.h" #ifdef KMK # include # include "kmkbuiltin.h" extern void kmk_cache_exec_image_a(const char *); /* imagecache.c */ #endif static char *make_command_line(char *shell_name, char *exec_path, char **argv); typedef struct sub_process_t { #ifdef KMK enum { kRegular = 0, kSubmit, kSubProcFreed } enmType; intptr_t clue; #endif intptr_t sv_stdin[2]; intptr_t sv_stdout[2]; intptr_t sv_stderr[2]; int using_pipes; char *inp; DWORD incnt; char * volatile outp; volatile DWORD outcnt; char * volatile errp; volatile DWORD errcnt; pid_t pid; int exit_code; int signal; long last_err; long lerrno; } sub_process; static long process_file_io_private(sub_process *pproc, BOOL fNeedToWait); /* bird */ /* keep track of children so we can implement a waitpid-like routine */ static sub_process *proc_array[MAXIMUM_WAIT_OBJECTS]; static int proc_index = 0; static int fake_exits_pending = 0; /* * Fill a HANDLE list with handles to wait for. */ DWORD process_set_handles(HANDLE *handles) { DWORD count = 0; int i; /* Build array of handles to wait for */ for (i = 0; i < proc_index; i++) { /* Don't wait on child processes that have already finished */ if (fake_exits_pending && proc_array[i]->exit_code) continue; handles[count++] = (HANDLE) proc_array[i]->pid; } return count; } #ifndef KMK /* Inefficient! */ /* * When a process has been waited for, adjust the wait state * array so that we don't wait for it again */ static void process_adjust_wait_state(sub_process* pproc) { int i; if (!proc_index) return; for (i = 0; i < proc_index; i++) if (proc_array[i]->pid == pproc->pid) break; if (i < proc_index) { proc_index--; if (i != proc_index) memmove(&proc_array[i], &proc_array[i+1], (proc_index-i) * sizeof(sub_process*)); proc_array[proc_index] = NULL; } } #endif /* !KMK */ /* * Waits for any of the registered child processes to finish. */ static sub_process * process_wait_for_any_private(int block, DWORD* pdwWaitStatus) { HANDLE handles[MAXIMUM_WAIT_OBJECTS]; DWORD retval, which; int i; if (!proc_index) return NULL; /* build array of handles to wait for */ for (i = 0; i < proc_index; i++) { handles[i] = (HANDLE) proc_array[i]->pid; if (fake_exits_pending && proc_array[i]->exit_code) break; } /* wait for someone to exit */ if (!fake_exits_pending) { #ifdef KMK l_wait_again: #endif retval = WaitForMultipleObjects(proc_index, handles, FALSE, (block ? INFINITE : 0)); which = retval - WAIT_OBJECT_0; } else { fake_exits_pending--; retval = !WAIT_FAILED; which = i; } /* If the pointer is not NULL, set the wait status result variable. */ if (pdwWaitStatus) *pdwWaitStatus = retval; /* return pointer to process */ if ((retval == WAIT_TIMEOUT) || (retval == WAIT_FAILED)) { return NULL; } else { sub_process* pproc = proc_array[which]; #ifdef KMK if (pproc->enmType == kSubmit) { /* Try get the result from kSubmit.c. This may not succeed if the whole result hasn't arrived yet, in which we just restart the wait. */ if (kSubmitSubProcGetResult(pproc->clue, &pproc->exit_code, &pproc->signal) != 0) { goto l_wait_again; } } #endif #ifndef KMK /* Inefficient! */ process_adjust_wait_state(pproc); #else proc_index--; if ((int)which < proc_index) proc_array[which] = proc_array[proc_index]; proc_array[proc_index] = NULL; #endif return pproc; } } /* * Terminate a process. */ BOOL process_kill(HANDLE proc, int signal) { sub_process* pproc = (sub_process*) proc; #ifdef KMK if (pproc->enmType == kRegular) { #endif pproc->signal = signal; return (TerminateProcess((HANDLE) pproc->pid, signal)); #ifdef KMK } else if (pproc->enmType == kSubmit) return kSubmitSubProcKill(pproc->clue, signal) == 0; assert(0); return FALSE; #endif } /* * Use this function to register processes you wish to wait for by * calling process_file_io(NULL) or process_wait_any(). This must be done * because it is possible for callers of this library to reuse the same * handle for multiple processes launches :-( */ void process_register(HANDLE proc) { #ifdef KMK assert(((sub_process *)proc)->enmType == kRegular); #endif if (proc_index < MAXIMUM_WAIT_OBJECTS) proc_array[proc_index++] = (sub_process *) proc; } #ifdef KMK /** * Interface used by kmkbuiltin/kSubmit.c to register stuff going down in a * worker process. * * @returns 0 on success, -1 if there are too many sub-processes already. * @param hEvent The event semaphore to wait on. * @param clue The clue to base. * @param pPid Where to return the pid that job.c expects. */ int process_kmk_register_submit(HANDLE hEvent, intptr_t clue, pid_t *pPid) { if (proc_index < MAXIMUM_WAIT_OBJECTS) { sub_process *pSubProc = (sub_process *)xcalloc(sizeof(*pSubProc)); pSubProc->enmType = kSubmit; pSubProc->clue = clue; pSubProc->pid = (intptr_t)hEvent; proc_array[proc_index++] = pSubProc; *pPid = (intptr_t)pSubProc; return 0; } return -1; } /** * Interface used by kmkbuiltin/kRedirect.c to register a spawned process. * * @returns 0 on success, -1 if there are too many sub-processes already. * @param hProcess The process handle. * @param pPid Where to return the pid that job.c expects. */ int process_kmk_register_redirect(HANDLE hProcess, pid_t *pPid) { if (proc_index < MAXIMUM_WAIT_OBJECTS) { sub_process *pSubProc = (sub_process *)xcalloc(sizeof(*pSubProc)); pSubProc->enmType = kRegular; pSubProc->pid = (intptr_t)hProcess; proc_array[proc_index++] = pSubProc; *pPid = (intptr_t)pSubProc; return 0; } return -1; } #endif /* KMK */ /* * Return the number of processes that we are still waiting for. */ int process_used_slots(void) { return proc_index; } /* * Public function which works kind of like waitpid(). Wait for any * of the children to die and return results. To call this function, * you must do 1 of things: * * x = process_easy(...); * * or * * x = process_init_fd(); * process_register(x); * * or * * x = process_init(); * process_register(x); * * You must NOT then call process_pipe_io() because this function is * not capable of handling automatic notification of any child * death. */ HANDLE process_wait_for_any(int block, DWORD* pdwWaitStatus) { sub_process* pproc = process_wait_for_any_private(block, pdwWaitStatus); if (!pproc) return NULL; else { /* * Ouch! can't tell caller if this fails directly. Caller * will have to use process_last_err() */ #ifdef KMK /* Invalidate negative directory cache entries now that a job has completed and possibly created new files that was missing earlier. */ dir_cache_invalid_after_job (); if (pproc->enmType == kRegular) { (void)process_file_io_private(pproc, FALSE); } #else (void) process_file_io(pproc); #endif return ((HANDLE) pproc); } } long process_signal(HANDLE proc) { if (proc == INVALID_HANDLE_VALUE) return 0; return (((sub_process *)proc)->signal); } long process_last_err(HANDLE proc) { if (proc == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE; return (((sub_process *)proc)->last_err); } long process_exit_code(HANDLE proc) { if (proc == INVALID_HANDLE_VALUE) return EXIT_FAILURE; return (((sub_process *)proc)->exit_code); } void process_noinherit(int fd) { HANDLE fh = (HANDLE)_get_osfhandle(fd); if (fh && fh != INVALID_HANDLE_VALUE) SetHandleInformation(fh, HANDLE_FLAG_INHERIT, 0); } /* 2006-02: All the following functions are currently unused. All of them would crash gmake if called with argument INVALID_HANDLE_VALUE. Hence whoever wants to use one of this functions must invent and implement a reasonable error handling for this function. char * process_outbuf(HANDLE proc) { return (((sub_process *)proc)->outp); } char * process_errbuf(HANDLE proc) { return (((sub_process *)proc)->errp); } int process_outcnt(HANDLE proc) { return (((sub_process *)proc)->outcnt); } int process_errcnt(HANDLE proc) { return (((sub_process *)proc)->errcnt); } void process_pipes(HANDLE proc, int pipes[3]) { pipes[0] = ((sub_process *)proc)->sv_stdin[0]; pipes[1] = ((sub_process *)proc)->sv_stdout[0]; pipes[2] = ((sub_process *)proc)->sv_stderr[0]; return; } */ HANDLE process_init() { sub_process *pproc; /* * open file descriptors for attaching stdin/stdout/sterr */ HANDLE stdin_pipes[2]; HANDLE stdout_pipes[2]; HANDLE stderr_pipes[2]; SECURITY_ATTRIBUTES inherit; BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH]; pproc = malloc(sizeof(*pproc)); memset(pproc, 0, sizeof(*pproc)); /* We can't use NULL for lpSecurityDescriptor because that uses the default security descriptor of the calling process. Instead we use a security descriptor with no DACL. This allows nonrestricted access to the associated objects. */ if (!InitializeSecurityDescriptor((PSECURITY_DESCRIPTOR)(&sd), SECURITY_DESCRIPTOR_REVISION)) { pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; return((HANDLE)pproc); } inherit.nLength = sizeof(inherit); inherit.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)(&sd); inherit.bInheritHandle = TRUE; // By convention, parent gets pipe[0], and child gets pipe[1] // This means the READ side of stdin pipe goes into pipe[1] // and the WRITE side of the stdout and stderr pipes go into pipe[1] if (CreatePipe( &stdin_pipes[1], &stdin_pipes[0], &inherit, 0) == FALSE || CreatePipe( &stdout_pipes[0], &stdout_pipes[1], &inherit, 0) == FALSE || CreatePipe( &stderr_pipes[0], &stderr_pipes[1], &inherit, 0) == FALSE) { pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; return((HANDLE)pproc); } // // Mark the parent sides of the pipes as non-inheritable // if (SetHandleInformation(stdin_pipes[0], HANDLE_FLAG_INHERIT, 0) == FALSE || SetHandleInformation(stdout_pipes[0], HANDLE_FLAG_INHERIT, 0) == FALSE || SetHandleInformation(stderr_pipes[0], HANDLE_FLAG_INHERIT, 0) == FALSE) { pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; return((HANDLE)pproc); } pproc->sv_stdin[0] = (intptr_t) stdin_pipes[0]; pproc->sv_stdin[1] = (intptr_t) stdin_pipes[1]; pproc->sv_stdout[0] = (intptr_t) stdout_pipes[0]; pproc->sv_stdout[1] = (intptr_t) stdout_pipes[1]; pproc->sv_stderr[0] = (intptr_t) stderr_pipes[0]; pproc->sv_stderr[1] = (intptr_t) stderr_pipes[1]; pproc->using_pipes = 1; pproc->lerrno = 0; return((HANDLE)pproc); } HANDLE process_init_fd(HANDLE stdinh, HANDLE stdouth, HANDLE stderrh) { sub_process *pproc; pproc = malloc(sizeof(*pproc)); if (pproc) { memset(pproc, 0, sizeof(*pproc)); /* * Just pass the provided file handles to the 'child * side' of the pipe, bypassing pipes altogether. */ pproc->sv_stdin[1] = (intptr_t) stdinh; pproc->sv_stdout[1] = (intptr_t) stdouth; pproc->sv_stderr[1] = (intptr_t) stderrh; pproc->last_err = pproc->lerrno = 0; } return((HANDLE)pproc); } static HANDLE find_file(const char *exec_path, const char *path_var, char *full_fname, DWORD full_len) { HANDLE exec_handle; char *fname; char *ext; DWORD req_len; int i; static const char *extensions[] = /* Should .com come before no-extension case? */ { ".exe", ".cmd", ".bat", "", ".com", NULL }; fname = xmalloc(strlen(exec_path) + 5); strcpy(fname, exec_path); ext = fname + strlen(fname); for (i = 0; extensions[i]; i++) { strcpy(ext, extensions[i]); if (((req_len = SearchPath (path_var, fname, NULL, full_len, full_fname, NULL)) > 0 /* For compatibility with previous code, which used OpenFile, and with Windows operation in general, also look in various default locations, such as Windows directory and Windows System directory. Warning: this also searches PATH in the Make's environment, which might not be what the Makefile wants, but it seems to be OK as a fallback, after the previous SearchPath failed to find on child's PATH. */ || (req_len = SearchPath (NULL, fname, NULL, full_len, full_fname, NULL)) > 0) && req_len <= full_len && (exec_handle = CreateFile(full_fname, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE) { free(fname); return(exec_handle); } } free(fname); return INVALID_HANDLE_VALUE; } /* * Return non-zero of FNAME specifies a batch file and its name * includes embedded whitespace. */ static int batch_file_with_spaces(const char *fname) { size_t fnlen = strlen(fname); return (fnlen > 4 && (_strnicmp(fname + fnlen - 4, ".bat", 4) == 0 || _strnicmp(fname + fnlen - 4, ".cmd", 4) == 0) /* The set of characters in the 2nd arg to strpbrk should be the same one used by make_command_line below to decide whether an argv[] element needs quoting. */ && strpbrk(fname, " \t") != NULL); } /* * Description: Create the child process to be helped * * Returns: success <=> 0 * * Notes/Dependencies: */ long process_begin( HANDLE proc, char **argv, char **envp, char *exec_path, char *as_user) { sub_process *pproc = (sub_process *)proc; char *shell_name = 0; int file_not_found=0; HANDLE exec_handle; char exec_fname[MAX_PATH]; const char *path_var = NULL; char **ep; char buf[MAX_PATH]; DWORD bytes_returned; DWORD flags; char *command_line; STARTUPINFO startInfo; PROCESS_INFORMATION procInfo; char *envblk=NULL; int envsize_needed = 0; int pass_null_exec_path = 0; #ifdef KMK size_t exec_path_len; extern int process_priority; assert (pproc->enmType == kRegular); #endif /* * Shell script detection... if the exec_path starts with #! then * we want to exec shell-script-name exec-path, not just exec-path * NT doesn't recognize #!/bin/sh or #!/etc/Tivoli/bin/perl. We do not * hard-code the path to the shell or perl or whatever: Instead, we * assume it's in the path somewhere (generally, the NT tools * bin directory) */ #ifdef KMK /* kmk performance: Don't bother looking for shell scripts in .exe files. */ exec_path_len = strlen(exec_path); if (exec_path_len > 4 && exec_path[exec_path_len - 4] == '.' && !stricmp(exec_path + exec_path_len - 3, "exe")) { exec_handle = INVALID_HANDLE_VALUE; exec_fname[0] = '\0'; } else { #endif /* KMK */ /* Use the Makefile's value of PATH to look for the program to execute, because it could be different from Make's PATH (e.g., if the target sets its own value. */ if (envp) for (ep = envp; *ep; ep++) { if (strncmp (*ep, "PATH=", 5) == 0 || strncmp (*ep, "Path=", 5) == 0) { path_var = *ep + 5; break; } } exec_handle = find_file(exec_path, path_var, exec_fname, sizeof(exec_fname)); #ifdef KMK } #endif /* * If we couldn't open the file, just assume that Windows will be * somehow able to find and execute it. If the first character * of the command is '/', assume they set SHELL to a Unixy shell * that have some magic mounts known only to it, and run the whole * command via $SHELL -c "COMMAND" instead. */ if (exec_handle == INVALID_HANDLE_VALUE) { if (exec_path[0] == '/') { char *new_argv0; char **argvi = argv; int arglen = 0; strcpy(buf, variable_expand ("$(SHELL)")); shell_name = &buf[0]; strcpy(exec_fname, "-c"); /* Construct a single command string in argv[0]. */ while (*argvi) { arglen += strlen(*argvi) + 1; argvi++; } new_argv0 = xmalloc(arglen + 1); new_argv0[0] = '\0'; for (argvi = argv; *argvi; argvi++) { strcat(new_argv0, *argvi); strcat(new_argv0, " "); } /* Remove the extra blank at the end. */ new_argv0[arglen-1] = '\0'; free(argv[0]); argv[0] = new_argv0; argv[1] = NULL; } else file_not_found++; } else { /* Attempt to read the first line of the file */ if (ReadFile( exec_handle, buf, sizeof(buf) - 1, /* leave room for trailing NULL */ &bytes_returned, 0) == FALSE || bytes_returned < 2) { pproc->last_err = GetLastError(); pproc->lerrno = E_IO; CloseHandle(exec_handle); return(-1); } if (buf[0] == '#' && buf[1] == '!') { /* * This is a shell script... Change the command line from * exec_path args to shell_name exec_path args */ char *p; /* Make sure buf is NULL terminated */ buf[bytes_returned] = 0; /* * Depending on the file system type, etc. the first line * of the shell script may end with newline or newline-carriage-return * Whatever it ends with, cut it off. */ p= strchr(buf, '\n'); if (p) *p = 0; p = strchr(buf, '\r'); if (p) *p = 0; /* * Find base name of shell */ shell_name = strrchr( buf, '/'); if (shell_name) { shell_name++; } else { shell_name = &buf[2];/* skipping "#!" */ } } CloseHandle(exec_handle); } flags = 0; if (file_not_found) command_line = make_command_line( shell_name, exec_path, argv); else { /* If exec_fname includes whitespace, CreateProcess behaves erratically and unreliably, and often fails if argv[0] also includes whitespace (and thus will be quoted by make_command_line below). So in that case, we don't pass exec_fname as the 1st arg to CreateProcess, but instead replace argv[0] with exec_fname (to keep its leading directories and extension as found by find_file), and pass NULL to CreateProcess as its 1st arg. This works around the bugs in CreateProcess, which are probably caused by its passing the command to cmd.exe with some incorrect quoting. */ if (!shell_name && batch_file_with_spaces(exec_fname) && _stricmp(exec_path, argv[0]) == 0) { char *new_argv, *p; char **argvi; int arglen, i; pass_null_exec_path = 1; /* Rewrite argv[] replacing argv[0] with exec_fname. */ for (argvi = argv + 1, arglen = strlen(exec_fname) + 1; *argvi; argvi++) { arglen += strlen(*argvi) + 1; } new_argv = xmalloc(arglen); p = strcpy(new_argv, exec_fname) + strlen(exec_fname) + 1; for (argvi = argv + 1, i = 1; *argvi; argvi++, i++) { strcpy(p, *argvi); argv[i] = p; p += strlen(*argvi) + 1; } argv[i] = NULL; free (argv[0]); argv[0] = new_argv; } command_line = make_command_line( shell_name, exec_fname, argv); } if ( command_line == NULL ) { pproc->last_err = 0; pproc->lerrno = E_NO_MEM; return(-1); } if (envp) { if (arr2envblk(envp, &envblk, &envsize_needed) == FALSE) { pproc->lerrno = E_NO_MEM; free( command_line ); if ((pproc->last_err == ERROR_INVALID_PARAMETER || pproc->last_err == ERROR_MORE_DATA) && envsize_needed > 32*1024) { fprintf (stderr, "CreateProcess failed, probably because environment is too large (%d bytes).\n", envsize_needed); } pproc->last_err = 0; return(-1); } } if (shell_name || file_not_found || pass_null_exec_path) { exec_path = 0; /* Search for the program in %Path% */ } else { exec_path = exec_fname; } /* * Set up inherited stdin, stdout, stderr for child */ memset(&startInfo, '\0', sizeof(startInfo)); GetStartupInfo(&startInfo); #ifndef KMK startInfo.dwFlags = STARTF_USESTDHANDLES; #endif startInfo.lpReserved = 0; startInfo.cbReserved2 = 0; startInfo.lpReserved2 = 0; #ifndef KMK startInfo.hStdInput = (HANDLE)pproc->sv_stdin[1]; startInfo.hStdOutput = (HANDLE)pproc->sv_stdout[1]; startInfo.hStdError = (HANDLE)pproc->sv_stderr[1]; #else if ( ((HANDLE)pproc->sv_stdin[1] != INVALID_HANDLE_VALUE && pproc->sv_stdin[1]) || ((HANDLE)pproc->sv_stdout[1] != INVALID_HANDLE_VALUE && pproc->sv_stdout[1]) || ((HANDLE)pproc->sv_stderr[1] != INVALID_HANDLE_VALUE && pproc->sv_stderr[1]) ) { startInfo.dwFlags = STARTF_USESTDHANDLES; startInfo.hStdInput = (HANDLE)pproc->sv_stdin[1]; startInfo.hStdOutput = (HANDLE)pproc->sv_stdout[1]; startInfo.hStdError = (HANDLE)pproc->sv_stderr[1]; } else { startInfo.dwFlags = 0; startInfo.hStdInput = 0; startInfo.hStdOutput = 0; startInfo.hStdError = 0; } #endif if (as_user) { free(envblk); return -1; } else { DB (DB_JOBS, ("CreateProcess(%s,%s,...)\n", exec_path ? exec_path : "NULL", command_line ? command_line : "NULL")); #ifdef KMK if (exec_fname[0]) kmk_cache_exec_image_a(exec_fname); else if (exec_path) kmk_cache_exec_image_a(exec_path); else if (argv[0]) kmk_cache_exec_image_a(argv[0]); switch (process_priority) { case 1: flags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break; case 2: flags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break; case 3: flags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break; case 4: flags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break; case 5: flags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break; } #endif if (CreateProcess( exec_path, command_line, NULL, 0, /* default security attributes for thread */ TRUE, /* inherit handles (e.g. helper pipes, oserv socket) */ flags, envblk, 0, /* default starting directory */ &startInfo, &procInfo) == FALSE) { pproc->last_err = GetLastError(); pproc->lerrno = E_FORK; fprintf(stderr, "process_begin: CreateProcess(%s, %s, ...) failed.\n", exec_path ? exec_path : "NULL", command_line); free(envblk); free( command_line ); return(-1); } #ifdef KMK switch (process_priority) { case 1: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_IDLE); break; case 2: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break; case 3: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_NORMAL); break; case 4: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_HIGHEST); break; case 5: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break; } ResumeThread(procInfo.hThread); #endif } pproc->pid = (pid_t)procInfo.hProcess; /* Close the thread handle -- we'll just watch the process */ CloseHandle(procInfo.hThread); /* Close the halves of the pipes we don't need */ if ((HANDLE)pproc->sv_stdin[1] != INVALID_HANDLE_VALUE && pproc->sv_stdin[1]) CloseHandle((HANDLE)pproc->sv_stdin[1]); if ((HANDLE)pproc->sv_stdout[1] != INVALID_HANDLE_VALUE && pproc->sv_stdout[1]) CloseHandle((HANDLE)pproc->sv_stdout[1]); if ((HANDLE)pproc->sv_stderr[1] != INVALID_HANDLE_VALUE && pproc->sv_stderr[1]) CloseHandle((HANDLE)pproc->sv_stderr[1]); pproc->sv_stdin[1] = 0; pproc->sv_stdout[1] = 0; pproc->sv_stderr[1] = 0; free( command_line ); free(envblk); pproc->lerrno=0; return 0; } #if 0 /* unused */ static DWORD proc_stdin_thread(sub_process *pproc) { DWORD in_done; for (;;) { if (WriteFile( (HANDLE) pproc->sv_stdin[0], pproc->inp, pproc->incnt, &in_done, NULL) == FALSE) _endthreadex(0); // This if should never be true for anonymous pipes, but gives // us a chance to change I/O mechanisms later if (in_done < pproc->incnt) { pproc->incnt -= in_done; pproc->inp += in_done; } else { _endthreadex(0); } } return 0; // for compiler warnings only.. not reached } static DWORD proc_stdout_thread(sub_process *pproc) { DWORD bufsize = 1024; char c; DWORD nread; pproc->outp = malloc(bufsize); if (pproc->outp == NULL) _endthreadex(0); pproc->outcnt = 0; for (;;) { if (ReadFile( (HANDLE)pproc->sv_stdout[0], &c, 1, &nread, NULL) == FALSE) { /* map_windows32_error_to_string(GetLastError());*/ _endthreadex(0); } if (nread == 0) _endthreadex(0); if (pproc->outcnt + nread > bufsize) { bufsize += nread + 512; pproc->outp = realloc(pproc->outp, bufsize); if (pproc->outp == NULL) { pproc->outcnt = 0; _endthreadex(0); } } pproc->outp[pproc->outcnt++] = c; } return 0; } static DWORD proc_stderr_thread(sub_process *pproc) { DWORD bufsize = 1024; char c; DWORD nread; pproc->errp = malloc(bufsize); if (pproc->errp == NULL) _endthreadex(0); pproc->errcnt = 0; for (;;) { if (ReadFile( (HANDLE)pproc->sv_stderr[0], &c, 1, &nread, NULL) == FALSE) { map_windows32_error_to_string(GetLastError()); _endthreadex(0); } if (nread == 0) _endthreadex(0); if (pproc->errcnt + nread > bufsize) { bufsize += nread + 512; pproc->errp = realloc(pproc->errp, bufsize); if (pproc->errp == NULL) { pproc->errcnt = 0; _endthreadex(0); } } pproc->errp[pproc->errcnt++] = c; } return 0; } /* * Purpose: collects output from child process and returns results * * Description: * * Returns: * * Notes/Dependencies: */ long process_pipe_io( HANDLE proc, char *stdin_data, int stdin_data_len) { sub_process *pproc = (sub_process *)proc; bool_t stdin_eof = FALSE, stdout_eof = FALSE, stderr_eof = FALSE; HANDLE childhand = (HANDLE) pproc->pid; HANDLE tStdin = NULL, tStdout = NULL, tStderr = NULL; unsigned int dwStdin, dwStdout, dwStderr; HANDLE wait_list[4]; DWORD wait_count; DWORD wait_return; HANDLE ready_hand; bool_t child_dead = FALSE; BOOL GetExitCodeResult; #ifdef KMK assert (pproc->enmType == kRegular); #endif /* * Create stdin thread, if needed */ pproc->inp = stdin_data; pproc->incnt = stdin_data_len; if (!pproc->inp) { stdin_eof = TRUE; CloseHandle((HANDLE)pproc->sv_stdin[0]); pproc->sv_stdin[0] = 0; } else { tStdin = (HANDLE) _beginthreadex( 0, 1024, (unsigned (__stdcall *) (void *))proc_stdin_thread, pproc, 0, &dwStdin); if (tStdin == 0) { pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; goto done; } } /* * Assume child will produce stdout and stderr */ tStdout = (HANDLE) _beginthreadex( 0, 1024, (unsigned (__stdcall *) (void *))proc_stdout_thread, pproc, 0, &dwStdout); tStderr = (HANDLE) _beginthreadex( 0, 1024, (unsigned (__stdcall *) (void *))proc_stderr_thread, pproc, 0, &dwStderr); if (tStdout == 0 || tStderr == 0) { pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; goto done; } /* * Wait for all I/O to finish and for the child process to exit */ while (!stdin_eof || !stdout_eof || !stderr_eof || !child_dead) { wait_count = 0; if (!stdin_eof) { wait_list[wait_count++] = tStdin; } if (!stdout_eof) { wait_list[wait_count++] = tStdout; } if (!stderr_eof) { wait_list[wait_count++] = tStderr; } if (!child_dead) { wait_list[wait_count++] = childhand; } wait_return = WaitForMultipleObjects(wait_count, wait_list, FALSE, /* don't wait for all: one ready will do */ child_dead? 1000 :INFINITE); /* after the child dies, subthreads have one second to collect all remaining output */ if (wait_return == WAIT_FAILED) { /* map_windows32_error_to_string(GetLastError());*/ pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; goto done; } ready_hand = wait_list[wait_return - WAIT_OBJECT_0]; if (ready_hand == tStdin) { CloseHandle((HANDLE)pproc->sv_stdin[0]); pproc->sv_stdin[0] = 0; CloseHandle(tStdin); tStdin = 0; stdin_eof = TRUE; } else if (ready_hand == tStdout) { CloseHandle((HANDLE)pproc->sv_stdout[0]); pproc->sv_stdout[0] = 0; CloseHandle(tStdout); tStdout = 0; stdout_eof = TRUE; } else if (ready_hand == tStderr) { CloseHandle((HANDLE)pproc->sv_stderr[0]); pproc->sv_stderr[0] = 0; CloseHandle(tStderr); tStderr = 0; stderr_eof = TRUE; } else if (ready_hand == childhand) { DWORD ierr; GetExitCodeResult = GetExitCodeProcess(childhand, &ierr); if (ierr == CONTROL_C_EXIT) { pproc->signal = SIGINT; } else { pproc->exit_code = ierr; } if (GetExitCodeResult == FALSE) { pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; goto done; } child_dead = TRUE; } else { /* ?? Got back a handle we didn't query ?? */ pproc->last_err = 0; pproc->lerrno = E_FAIL; goto done; } } done: if (tStdin != 0) CloseHandle(tStdin); if (tStdout != 0) CloseHandle(tStdout); if (tStderr != 0) CloseHandle(tStderr); if (pproc->lerrno) return(-1); else return(0); } #endif /* unused */ #ifndef KMK /* unused */ /* * Purpose: collects output from child process and returns results * * Description: * * Returns: * * Notes/Dependencies: */ long process_file_io( HANDLE proc) { sub_process *pproc; if (proc == NULL) pproc = process_wait_for_any_private(1, 0); else pproc = (sub_process *)proc; /* some sort of internal error */ if (!pproc) return -1; return process_file_io_private(proc, TRUE); } #endif /* !KMK - unused */ /* private function, avoid some kernel calls. (bird) */ static long process_file_io_private( sub_process *pproc, BOOL fNeedToWait) { HANDLE childhand; DWORD wait_return; BOOL GetExitCodeResult; DWORD ierr; childhand = (HANDLE) pproc->pid; /* * This function is poorly named, and could also be used just to wait * for child death if you're doing your own pipe I/O. If that is * the case, close the pipe handles here. */ if (pproc->sv_stdin[0]) { CloseHandle((HANDLE)pproc->sv_stdin[0]); pproc->sv_stdin[0] = 0; } if (pproc->sv_stdout[0]) { CloseHandle((HANDLE)pproc->sv_stdout[0]); pproc->sv_stdout[0] = 0; } if (pproc->sv_stderr[0]) { CloseHandle((HANDLE)pproc->sv_stderr[0]); pproc->sv_stderr[0] = 0; } #ifdef KMK if (childhand == NULL || childhand == INVALID_HANDLE_VALUE) { goto done2; } #endif /* * Wait for the child process to exit */ if (fNeedToWait) { /* bird */ wait_return = WaitForSingleObject(childhand, INFINITE); if (wait_return != WAIT_OBJECT_0) { /* map_windows32_error_to_string(GetLastError());*/ pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; goto done2; } } /* bird */ GetExitCodeResult = GetExitCodeProcess(childhand, &ierr); if (ierr == CONTROL_C_EXIT) { pproc->signal = SIGINT; } else { pproc->exit_code = ierr; } if (GetExitCodeResult == FALSE) { pproc->last_err = GetLastError(); pproc->lerrno = E_SCALL; } done2: if (pproc->lerrno) return(-1); else return(0); } /* * Description: Clean up any leftover handles, etc. It is up to the * caller to manage and free the input, output, and stderr buffers. */ void process_cleanup( HANDLE proc) { sub_process *pproc = (sub_process *)proc; int i; #ifdef KMK if (pproc->enmType == kRegular) { #endif if (pproc->using_pipes) { for (i= 0; i <= 1; i++) { if ((HANDLE)pproc->sv_stdin[i] && (HANDLE)pproc->sv_stdin[i] != INVALID_HANDLE_VALUE) CloseHandle((HANDLE)pproc->sv_stdin[i]); if ((HANDLE)pproc->sv_stdout[i] && (HANDLE)pproc->sv_stdout[i] != INVALID_HANDLE_VALUE) CloseHandle((HANDLE)pproc->sv_stdout[i]); if ((HANDLE)pproc->sv_stderr[i] && (HANDLE)pproc->sv_stderr[i] != INVALID_HANDLE_VALUE) CloseHandle((HANDLE)pproc->sv_stderr[i]); } } if ((HANDLE)pproc->pid) CloseHandle((HANDLE)pproc->pid); #ifdef KMK } else if (pproc->enmType == kSubmit) { kSubmitSubProcCleanup(pproc->clue); } else { assert(0); return; } pproc->enmType = kSubProcFreed; #endif free(pproc); } /* * Description: * Create a command line buffer to pass to CreateProcess * * Returns: the buffer or NULL for failure * Shell case: sh_name a:/full/path/to/script argv[1] argv[2] ... * Otherwise: argv[0] argv[1] argv[2] ... * * Notes/Dependencies: * CreateProcess does not take an argv, so this command creates a * command line for the executable. */ static char * make_command_line( char *shell_name, char *full_exec_path, char **argv) { int argc = 0; char** argvi; int* enclose_in_quotes = NULL; int* enclose_in_quotes_i; unsigned int bytes_required = 0; char* command_line; char* command_line_i; int cygwin_mode = 0; /* HAVE_CYGWIN_SHELL */ int have_sh = 0; /* HAVE_CYGWIN_SHELL */ #undef HAVE_CYGWIN_SHELL /* bird: paranoia */ #ifdef HAVE_CYGWIN_SHELL have_sh = (shell_name != NULL || strstr(full_exec_path, "sh.exe")); cygwin_mode = 1; #endif if (shell_name && full_exec_path) { bytes_required = strlen(shell_name) + 1 + strlen(full_exec_path); /* * Skip argv[0] if any, when shell_name is given. * The special case of "-c" in full_exec_path means * argv[0] is not the shell name, but the command string * to pass to the shell. */ if (*argv && strcmp(full_exec_path, "-c")) argv++; /* * Add one for the intervening space. */ if (*argv) bytes_required++; } argvi = argv; while (*(argvi++)) argc++; if (argc) { enclose_in_quotes = (int*) calloc(1, argc * sizeof(int)); if (!enclose_in_quotes) { return NULL; } } /* We have to make one pass through each argv[i] to see if we need * to enclose it in ", so we might as well figure out how much * memory we'll need on the same pass. */ argvi = argv; enclose_in_quotes_i = enclose_in_quotes; while(*argvi) { char* p = *argvi; unsigned int backslash_count = 0; /* * We have to enclose empty arguments in ". */ if (!(*p)) *enclose_in_quotes_i = 1; while(*p) { switch (*p) { case '\"': /* * We have to insert a backslash for each " * and each \ that precedes the ". */ bytes_required += (backslash_count + 1); backslash_count = 0; break; #if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL) case '\\': backslash_count++; break; #endif /* * At one time we set *enclose_in_quotes_i for '*' or '?' to suppress * wildcard expansion in programs linked with MSVC's SETARGV.OBJ so * that argv in always equals argv out. This was removed. Say you have * such a program named glob.exe. You enter * glob '*' * at the sh command prompt. Obviously the intent is to make glob do the * wildcarding instead of sh. If we set *enclose_in_quotes_i for '*' or '?', * then the command line that glob would see would be * glob "*" * and the _setargv in SETARGV.OBJ would _not_ expand the *. */ case ' ': case '\t': *enclose_in_quotes_i = 1; /* fall through */ default: backslash_count = 0; break; } /* * Add one for each character in argv[i]. */ bytes_required++; p++; } if (*enclose_in_quotes_i) { /* * Add one for each enclosing ", * and one for each \ that precedes the * closing ". */ bytes_required += (backslash_count + 2); } /* * Add one for the intervening space. */ if (*(++argvi)) bytes_required++; enclose_in_quotes_i++; } /* * Add one for the terminating NULL. */ bytes_required++; #ifdef KMK /* for the space before the final " in case we need it. */ bytes_required++; #endif command_line = (char*) malloc(bytes_required); if (!command_line) { free(enclose_in_quotes); return NULL; } command_line_i = command_line; if (shell_name && full_exec_path) { while(*shell_name) { *(command_line_i++) = *(shell_name++); } *(command_line_i++) = ' '; while(*full_exec_path) { *(command_line_i++) = *(full_exec_path++); } if (*argv) { *(command_line_i++) = ' '; } } argvi = argv; enclose_in_quotes_i = enclose_in_quotes; while(*argvi) { char* p = *argvi; unsigned int backslash_count = 0; if (*enclose_in_quotes_i) { *(command_line_i++) = '\"'; } while(*p) { if (*p == '\"') { if (cygwin_mode && have_sh) { /* HAVE_CYGWIN_SHELL */ /* instead of a \", cygwin likes "" */ *(command_line_i++) = '\"'; } else { /* * We have to insert a backslash for the " * and each \ that precedes the ". */ backslash_count++; while(backslash_count) { *(command_line_i++) = '\\'; backslash_count--; }; } #if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL) } else if (*p == '\\') { backslash_count++; } else { backslash_count = 0; #endif } /* * Copy the character. */ *(command_line_i++) = *(p++); } if (*enclose_in_quotes_i) { #if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL) /* * Add one \ for each \ that precedes the * closing ". */ while(backslash_count--) { *(command_line_i++) = '\\'; }; #endif #ifdef KMK /* * ash it put off by echo "hello world" ending up as: * G:/.../kmk_ash.exe -c "echo ""hello world""" * It wants a space before the last '"'. * (The 'test_shell' goals in Makefile.kmk tests this problem.) */ if (command_line_i[-1] == '\"' /* && cygwin_mode && have_sh*/ && !argvi[1]) { *(command_line_i++) = ' '; } #endif *(command_line_i++) = '\"'; } /* * Append an intervening space. */ if (*(++argvi)) { *(command_line_i++) = ' '; } enclose_in_quotes_i++; } /* * Append the terminating NULL. */ *command_line_i = '\0'; free(enclose_in_quotes); return command_line; } /* * Description: Given an argv and optional envp, launch the process * using the default stdin, stdout, and stderr handles. * Also, register process so that process_wait_for_any_private() * can be used via process_file_io(NULL) or * process_wait_for_any(). * * Returns: * * Notes/Dependencies: */ HANDLE process_easy( char **argv, char **envp, int outfd, int errfd) { HANDLE hIn = INVALID_HANDLE_VALUE; HANDLE hOut = INVALID_HANDLE_VALUE; HANDLE hErr = INVALID_HANDLE_VALUE; HANDLE hProcess, tmpIn, tmpOut, tmpErr; DWORD e; if (proc_index >= MAXIMUM_WAIT_OBJECTS) { DB (DB_JOBS, ("process_easy: All process slots used up\n")); return INVALID_HANDLE_VALUE; } #ifdef KMK /* We can effort here by lettering CreateProcess/kernel do the standard handle duplication. */ if (outfd != -1 || errfd != -1) { #endif /* Standard handles returned by GetStdHandle can be NULL or INVALID_HANDLE_VALUE if the parent process closed them. If that happens, we open the null device and pass its handle to CreateProcess as the corresponding handle to inherit. */ tmpIn = GetStdHandle(STD_INPUT_HANDLE); if (DuplicateHandle(GetCurrentProcess(), tmpIn, GetCurrentProcess(), &hIn, 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) { if ((e = GetLastError()) == ERROR_INVALID_HANDLE) { tmpIn = CreateFile("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (tmpIn != INVALID_HANDLE_VALUE && DuplicateHandle(GetCurrentProcess(), tmpIn, GetCurrentProcess(), &hIn, 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) CloseHandle(tmpIn); } if (hIn == INVALID_HANDLE_VALUE) { fprintf(stderr, "process_easy: DuplicateHandle(In) failed (e=%ld)\n", e); return INVALID_HANDLE_VALUE; } } if (outfd >= 0) tmpOut = (HANDLE)_get_osfhandle (outfd); else tmpOut = GetStdHandle (STD_OUTPUT_HANDLE); if (DuplicateHandle(GetCurrentProcess(), tmpOut, GetCurrentProcess(), &hOut, 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) { if ((e = GetLastError()) == ERROR_INVALID_HANDLE) { tmpOut = CreateFile("NUL", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (tmpOut != INVALID_HANDLE_VALUE && DuplicateHandle(GetCurrentProcess(), tmpOut, GetCurrentProcess(), &hOut, 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) CloseHandle(tmpOut); } if (hOut == INVALID_HANDLE_VALUE) { fprintf(stderr, "process_easy: DuplicateHandle(Out) failed (e=%ld)\n", e); return INVALID_HANDLE_VALUE; } } if (errfd >= 0) tmpErr = (HANDLE)_get_osfhandle (errfd); else tmpErr = GetStdHandle(STD_ERROR_HANDLE); if (DuplicateHandle(GetCurrentProcess(), tmpErr, GetCurrentProcess(), &hErr, 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) { if ((e = GetLastError()) == ERROR_INVALID_HANDLE) { tmpErr = CreateFile("NUL", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (tmpErr != INVALID_HANDLE_VALUE && DuplicateHandle(GetCurrentProcess(), tmpErr, GetCurrentProcess(), &hErr, 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) CloseHandle(tmpErr); } if (hErr == INVALID_HANDLE_VALUE) { fprintf(stderr, "process_easy: DuplicateHandle(Err) failed (e=%ld)\n", e); return INVALID_HANDLE_VALUE; } } #ifdef KMK /* saving effort */ } #endif hProcess = process_init_fd(hIn, hOut, hErr); if (process_begin(hProcess, argv, envp, argv[0], NULL)) { fake_exits_pending++; /* process_begin() failed: make a note of that. */ if (!((sub_process*) hProcess)->last_err) ((sub_process*) hProcess)->last_err = -1; #ifdef KMK if (!((sub_process*) hProcess)->exit_code) #endif ((sub_process*) hProcess)->exit_code = process_last_err(hProcess); /* close up unused handles */ if (hIn != INVALID_HANDLE_VALUE) CloseHandle(hIn); if (hOut != INVALID_HANDLE_VALUE) CloseHandle(hOut); if (hErr != INVALID_HANDLE_VALUE) CloseHandle(hErr); } process_register(hProcess); return hProcess; }