/* Process handling for Windows. Copyright (C) 1996-2012 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 #ifdef _MSC_VER # include /* for intptr_t */ #else # include #endif #include /* for msvc _beginthreadex, _endthreadex */ #include #include #include "sub_proc.h" #include "proc.h" #include "w32err.h" #include "debug.h" static char *make_command_line(char *shell_name, char *exec_path, char **argv); extern char *xmalloc (unsigned int); typedef struct sub_process_t { 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; /* 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; /* Windows jobserver implementation variables */ static char jobserver_semaphore_name[MAX_PATH + 1]; static HANDLE jobserver_semaphore = NULL; /* Open existing jobserver semaphore */ int open_jobserver_semaphore(const char* name) { jobserver_semaphore = OpenSemaphore( SEMAPHORE_ALL_ACCESS, // Semaphore access setting FALSE, // Child processes DON'T inherit name); // Semaphore name if (jobserver_semaphore == NULL) return 0; return 1; } /* Create new jobserver semaphore */ int create_jobserver_semaphore(int tokens) { sprintf(jobserver_semaphore_name, "gmake_semaphore_%d", _getpid()); jobserver_semaphore = CreateSemaphore( NULL, // Use default security descriptor tokens, // Initial count tokens, // Maximum count jobserver_semaphore_name); // Semaphore name if (jobserver_semaphore == NULL) return 0; return 1; } /* Close jobserver semaphore */ void free_jobserver_semaphore() { if (jobserver_semaphore != NULL) { CloseHandle(jobserver_semaphore); jobserver_semaphore = NULL; } } /* Decrement semaphore count */ int acquire_jobserver_semaphore() { DWORD dwEvent = WaitForSingleObject( jobserver_semaphore, // Handle to semaphore 0); // DON'T wait on semaphore return (dwEvent == WAIT_OBJECT_0); } /* Increment semaphore count */ int release_jobserver_semaphore() { BOOL bResult = ReleaseSemaphore( jobserver_semaphore, // handle to semaphore 1, // increase count by one NULL); // not interested in previous count return (bResult); } int has_jobserver_semaphore() { return (jobserver_semaphore != NULL); } char* get_jobserver_semaphore_name() { return (jobserver_semaphore_name); } /* Wait for either the jobserver semaphore to become signalled or one of our * child processes to terminate. */ int wait_for_semaphore_or_child_process() { HANDLE handles[MAXIMUM_WAIT_OBJECTS]; DWORD dwHandleCount = 1; DWORD dwEvent; int i; /* Add jobserver semaphore to first slot. */ handles[0] = jobserver_semaphore; /* 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[dwHandleCount++] = (HANDLE) proc_array[i]->pid; } dwEvent = WaitForMultipleObjects( dwHandleCount, // number of objects in array handles, // array of objects FALSE, // wait for any object INFINITE); // wait until object is signalled switch(dwEvent) { case WAIT_FAILED: return -1; case WAIT_OBJECT_0: /* Indicate that the semaphore was signalled */ return 1; default: /* Assume that one or more of the child processes terminated. */ return 0; } } /* * 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; } } /* * 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) { 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]; process_adjust_wait_state(pproc); return pproc; } } /* * Terminate a process. */ BOOL process_kill(HANDLE proc, int signal) { sub_process* pproc = (sub_process*) proc; pproc->signal = signal; return (TerminateProcess((HANDLE) pproc->pid, signal)); } /* * 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) { if (proc_index < MAXIMUM_WAIT_OBJECTS) proc_array[proc_index++] = (sub_process *) proc; } /* * 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() */ (void) process_file_io(pproc); 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); } /* 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; } /* * 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[256]; DWORD bytes_returned; DWORD flags; char *command_line; STARTUPINFO startInfo; PROCESS_INFORMATION procInfo; char *envblk=NULL; /* * 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) */ /* 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)); /* * If we couldn't open the file, just assume that Windows will be * somehow able to find and execute it. */ if (exec_handle == INVALID_HANDLE_VALUE) { 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 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) ==FALSE) { pproc->last_err = 0; pproc->lerrno = E_NO_MEM; free( command_line ); return(-1); } } if ((shell_name) || (file_not_found)) { exec_path = 0; /* Search for the program in %Path% */ } else { exec_path = exec_fname; } /* * Set up inherited stdin, stdout, stderr for child */ GetStartupInfo(&startInfo); startInfo.dwFlags = STARTF_USESTDHANDLES; startInfo.lpReserved = 0; startInfo.cbReserved2 = 0; startInfo.lpReserved2 = 0; startInfo.lpTitle = shell_name ? shell_name : exec_path; startInfo.hStdInput = (HANDLE)pproc->sv_stdin[1]; startInfo.hStdOutput = (HANDLE)pproc->sv_stdout[1]; startInfo.hStdError = (HANDLE)pproc->sv_stderr[1]; if (as_user) { if (envblk) free(envblk); return -1; } else { DB (DB_JOBS, ("CreateProcess(%s,%s,...)\n", exec_path ? exec_path : "NULL", command_line ? command_line : "NULL")); 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); if (envblk) free(envblk); free( command_line ); return(-1); } } 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) CloseHandle((HANDLE)pproc->sv_stdin[1]); if ((HANDLE)pproc->sv_stdout[1] != INVALID_HANDLE_VALUE) CloseHandle((HANDLE)pproc->sv_stdout[1]); if ((HANDLE)pproc->sv_stderr[1] != INVALID_HANDLE_VALUE) CloseHandle((HANDLE)pproc->sv_stderr[1]); pproc->sv_stdin[1] = 0; pproc->sv_stdout[1] = 0; pproc->sv_stderr[1] = 0; free( command_line ); if (envblk) 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; /* * 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 */ /* * Purpose: collects output from child process and returns results * * Description: * * Returns: * * Notes/Dependencies: */ long process_file_io( HANDLE proc) { sub_process *pproc; HANDLE childhand; DWORD wait_return; BOOL GetExitCodeResult; DWORD ierr; 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; 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; } /* * Wait for the child process to exit */ 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; } 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, ouput, and stderr buffers. */ void process_cleanup( HANDLE proc) { sub_process *pproc = (sub_process *)proc; int i; 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); 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 */ #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. */ if (*argv) 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++; command_line = (char*) malloc(bytes_required); if (!command_line) { if (enclose_in_quotes) 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 *(command_line_i++) = '\"'; } /* * Append an intervening space. */ if (*(++argvi)) { *(command_line_i++) = ' '; } enclose_in_quotes_i++; } /* * Append the terminating NULL. */ *command_line_i = '\0'; if (enclose_in_quotes) 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) { 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; } /* 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; } } 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; } } 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; } } 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; ((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; }