/* ** The cvsgui protocol used by WinCvs ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation; either ** version 2.1 of the License, or (at your option) any later version. ** ** This library 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 ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /*! \file cvsgui_process.cpp \brief CvsGui process code implementation \author Alexandre Parenteau --- November 1999 \note To be used by GUI and CVS client \note Derived from plugin.c in GIMP */ #ifdef HAVE_CONFIG_H extern "C" { #include "config.h" } #endif #ifdef HAVE_STRINGS_H # include #endif #include #include #include #include #include #include #include #include #include // hpux requirement f/c++ apps #ifndef WIN32 # include # include # include # include #endif #include #include #include #include "cvsgui_process.h" #include "cvsgui_protocol.h" static int cvs_process_write(pipe_t fd, guint8* buf, gulong count); static int cvs_process_flush(pipe_t fd); static void cvs_process_push(CvsProcess* cvs_process); static void cvs_process_pop(void); static void cvs_process_recv_message(CvsProcess* p); static void cvs_process_handle_message(WireMessage* msg); void cvs_process_kill(CvsProcess* cvs_process); static void cvs_process_close(CvsProcess* cvs_process, int kill_it); static void cvs_process_destroy(CvsProcess* cvs_process); static CvsProcess* cvs_process_new(char* name, int argc, char** argv); static std::dequecvs_process_stack; /*!< Process stack container */ static std::vectoropen_cvs_process; /*!< Open process container */ static CvsProcess* current_cvs_process = NULL; /*!< Current CVS process */ static int current_write_buffer_index = 0; /*!< Current write buffer index */ static char* current_write_buffer = NULL; /*!< Current write buffer */ static char process_write_buffer[WRITE_BUFFER_SIZE]; /*!< Buffer for writing, only for the process */ #ifdef WIN32 /// Use the runtime to initialize and delete a critical section class CDummyInit { public: // Construction CDummyInit() { ::InitializeCriticalSection(&m_destroyLock); ::InitializeCriticalSection(&m_stackLock); } ~CDummyInit() { ::DeleteCriticalSection(&m_destroyLock); ::DeleteCriticalSection(&m_stackLock); } private: // Data members static CRITICAL_SECTION m_destroyLock; /*!< Destroy lock */ static CRITICAL_SECTION m_stackLock; /*!< Stack lock */ public: // Interface /// Get the destroy lock inline static CRITICAL_SECTION* GetDestroyLock() { return &m_destroyLock; } /// Get the stack lock inline static CRITICAL_SECTION* GetStackLock() { return &m_stackLock; } } sDummyObject; CRITICAL_SECTION CDummyInit::m_destroyLock; CRITICAL_SECTION CDummyInit::m_stackLock; /*! Write data to a file \param fd File handle \param buf Data to be written \param count Buffer size \return The count of data written \note Wraps ::WriteFile API */ static int write(pipe_t fd, const void* buf, int count) { DWORD write; if( !WriteFile(fd, buf, count, &write, NULL) ) { errno = EINVAL; return -1; } return write; } /*! Callback enumerating all top-level windows and posting the WM_CLOSE message to the process that matches given process ID \param hWnd Window handle \param lParam Process ID to post message to \return TRUE to continue enumerate, FALSE to stop */ BOOL CALLBACK PostCloseEnum(HWND hWnd, LPARAM lParam) { DWORD pid = 0; GetWindowThreadProcessId(hWnd, &pid); if( pid == (DWORD)lParam ) { PostMessage(hWnd, WM_CLOSE, 0, 0); } return TRUE; } /*! Terminate cvs process \param cvs_process Cvs process to be terminated \return true if process was terminate, false otherwise \note See also Q178893 - HOWTO: Terminate an Application "Cleanly" in Win32 */ bool cvs_process_terminate(CvsProcess* cvs_process) { bool res = FALSE; EnumWindows((WNDENUMPROC)PostCloseEnum, (LPARAM)cvs_process->threadID); if( WaitForSingleObject(cvs_process->pid, 300) == WAIT_OBJECT_0 ) { res = TRUE; } else { res = TerminateProcess(cvs_process->pid, 0) != 0; } return res; } /// Critical section used when destroying cvs process struct CDestroyThreadLock { public: // Construction inline CDestroyThreadLock() { EnterCriticalSection(CDummyInit::GetDestroyLock()); } inline ~CDestroyThreadLock() { LeaveCriticalSection(CDummyInit::GetDestroyLock()); } }; /// Critical section used for cvs process stack operations struct CStackThreadLock { public: inline CStackThreadLock() { EnterCriticalSection(CDummyInit::GetStackLock()); } inline ~CStackThreadLock() { LeaveCriticalSection(CDummyInit::GetStackLock()); } }; #else /// Dummy critical section used when destroying cvs process struct CDestroyThreadLock { public: inline CDestroyThreadLock() { } inline ~CDestroyThreadLock() { } }; /// Dummy critical section used for cvs process stack operations struct CStackThreadLock { public: inline CStackThreadLock() { } inline ~CStackThreadLock() { } }; #endif /*! Read the exit code message from the pipe \param fd Pipe \param msg Message */ static void _gp_quit_read(pipe_t fd, WireMessage* msg) { GPT_QUIT* t = (GPT_QUIT*)malloc(sizeof(GPT_QUIT)); if( t == 0L ) return; if( !wire_read_int32(fd, (guint32*)&t->code, 1) ) return; msg->data = t; } /*! Write the exit code message to the pipe \param fd Pipe \param msg Message */ static void _gp_quit_write(pipe_t fd, WireMessage* msg) { GPT_QUIT* t = (GPT_QUIT*)msg->data; if( !wire_write_int32(fd, (guint32*)&t->code, 1) ) return; } /*! Destroy message data \param msg Message */ static void _gp_quit_destroy(WireMessage* msg) { free(msg->data); } /*! Read the environment message from the pipe \param fd Pipe \param msg Message */ static void _gp_getenv_read(pipe_t fd, WireMessage* msg) { GPT_GETENV* t = (GPT_GETENV*)malloc(sizeof(GPT_GETENV)); if( t == 0L ) return; if( !wire_read_int8(fd, &t->empty, 1) ) return; if( !wire_read_string(fd, &t->str, 1) ) return; msg->data = t; } /*! Write the environment message to the pipe \param fd Pipe \param msg Message */ static void _gp_getenv_write(pipe_t fd, WireMessage* msg) { GPT_GETENV* t = (GPT_GETENV*)msg->data; if( !wire_write_int8(fd, &t->empty, 1) ) return; if( !wire_write_string(fd, &t->str, 1, -1) ) return; } /*! Destroy environment data \param msg Message */ static void _gp_getenv_destroy(WireMessage* msg) { GPT_GETENV* t = (GPT_GETENV*)msg->data; free(t->str); free(t); } /*! Read the console message from the pipe \param fd Pipe \param msg Message */ static void _gp_console_read(pipe_t fd, WireMessage* msg) { GPT_CONSOLE* t = (GPT_CONSOLE*)malloc(sizeof(GPT_CONSOLE)); if( t == 0L ) return; if( !wire_read_int8(fd, &t->isStderr, 1) ) return; if( !wire_read_int32(fd, &t->len, 1) ) return; if( !wire_read_string(fd, &t->str, 1) ) return; msg->data = t; } /*! Write the console message to the pipe \param fd Pipe \param msg Message */ static void _gp_console_write(pipe_t fd, WireMessage* msg) { GPT_CONSOLE* t = (GPT_CONSOLE*)msg->data; if( !wire_write_int8(fd, &t->isStderr, 1) ) return; if( !wire_write_int32(fd, &t->len, 1) ) return; if( !wire_write_string(fd, &t->str, 1, t->len) ) return; } /*! Destroy console data \param msg Message */ static void _gp_console_destroy(WireMessage* msg) { GPT_CONSOLE* t = (GPT_CONSOLE*)msg->data; free(t->str); free(t); } /*! Register read, write and destroy functions */ static void gp_init() { wire_register(GP_QUIT, _gp_quit_read, _gp_quit_write, _gp_quit_destroy); wire_register(GP_GETENV, _gp_getenv_read, _gp_getenv_write, _gp_getenv_destroy); wire_register(GP_CONSOLE, _gp_console_read, _gp_console_write, _gp_console_destroy); } /*! Write the exit code to the pipe \param fd Pipe \param code Exit code \return TRUE on success, FALSE otherwise */ int gp_quit_write(pipe_t fd, int code) { WireMessage msg; GPT_QUIT* t = (GPT_QUIT*)malloc(sizeof(GPT_QUIT)); msg.type = GP_QUIT; msg.data = t; t->code = code; if( !wire_write_msg(fd, &msg) ) return FALSE; if( !wire_flush(fd) ) return FALSE; return TRUE; } /*! Write the environment variable to the pipe \param fd Pipe \param env Name of the environment variable \return TRUE on success, FALSE otherwise */ int gp_getenv_write(pipe_t fd, const char* env) { WireMessage msg; GPT_GETENV* t = (GPT_GETENV*)malloc(sizeof(GPT_GETENV)); msg.type = GP_GETENV; msg.data = t; t->empty = env == 0L; t->str = strdup(env == 0L ? "" : env); if( !wire_write_msg(fd, &msg) ) return FALSE; wire_destroy(&msg); if( !wire_flush(fd) ) return FALSE; return TRUE; } /*! Read the environment variable from the pipe \param fd Pipe \return The value of environment variable */ char* gp_getenv_read(pipe_t fd) { WireMessage msg; char* res; memset(&msg, 0, sizeof(WireMessage)); if( !wire_read_msg(fd, &msg) || msg.type != GP_GETENV ) { fprintf(stderr, "cvsgui protocol error !\n"); exit(-1); } GPT_GETENV* t = (GPT_GETENV*)msg.data; res = t->empty ? 0L : strdup(t->str); wire_destroy(&msg); return res; } #ifdef WIN32 extern "C" #endif /*! Write the console data to the pipe \param fd Pipe \param str Data to be written \param len Data length \param isStderr Non-zero to write stderr, otherwise write stdout \param binary Non-zero if data is binary, text data otherwise \return TRUE on success, FALSE otherwise \note For binary data an additional empty message is written first before the actual data */ int gp_console_write(pipe_t fd, const char* str, int len, int isStderr, int binary) { WireMessage msg; GPT_CONSOLE* t = (GPT_CONSOLE*)malloc(sizeof(GPT_CONSOLE)); if( binary ) { // Sending an empty message to indicate the binary stream coming gp_console_write(fd, "", 0, 0, 0); } msg.type = GP_CONSOLE; msg.data = t; t->isStderr = isStderr; t->len = len; t->str = (char*)malloc((len + 1) * sizeof(char)); memcpy(t->str, str, len * sizeof(char)); t->str[len] = '\0'; if( !wire_write_msg(fd, &msg) ) return FALSE; if( !wire_flush(fd) ) return FALSE; return TRUE; } /*! Initialize the protocol library (both for the process and the application) */ void cvs_process_init() { gp_init(); wire_set_writer(cvs_process_write); wire_set_flusher(cvs_process_flush); } /*! Create new cvs process object and initialize it's data members \param name Command \param argc Arguments count \param argv Arguments \return Pointer to the newly allocated object \note You must free allocated memory using cvs_process_destroy */ static CvsProcess* cvs_process_new(const char* name, int argc, char** argv) { CvsProcess* cvs_process; cvs_process_init(); cvs_process = (CvsProcess*)malloc(sizeof(CvsProcess)); if( cvs_process == 0L ) return 0L; cvs_process->open = FALSE; cvs_process->destroy = FALSE; #ifdef WIN32 cvs_process->starting = TRUE; cvs_process->threadID = 0; #endif cvs_process->pid = 0; cvs_process->callbacks = 0L; cvs_process->argc = argc + 4; cvs_process->args = (char**)malloc((cvs_process->argc + 1) * sizeof(char*)); cvs_process->args[0] = strdup(name); cvs_process->args[1] = strdup("-cvsgui"); cvs_process->args[2] = (char*)malloc(16 * sizeof(char)); cvs_process->args[3] = (char*)malloc(16 * sizeof(char)); for(int i = 0; i < argc; i++) { cvs_process->args[4 + i] = strdup(argv[i]); } cvs_process->args[cvs_process->argc] = 0L; cvs_process->my_read = 0; cvs_process->my_write = 0; cvs_process->his_read = 0; cvs_process->his_write = 0; cvs_process->write_buffer_index = 0; cvs_process->pstdin = 0; cvs_process->pstdout = 0; cvs_process->pstderr = 0; cvs_process->appData = 0L; #ifdef WIN32 memset(cvs_process->threads, 0, sizeof(cvs_process->threads)); memset(cvs_process->threadsID, 0, sizeof(cvs_process->threadsID)); cvs_process->stopProcessEvent = NULL; #endif return cvs_process; } /*! Destroy cvs process object and free allocated memory \param cvs_process Cvs process to be destroyed */ static void cvs_process_destroy(CvsProcess* cvs_process) { if( cvs_process ) { cvs_process_close(cvs_process, TRUE); if( cvs_process->args != 0L ) { for(int i = 0; i < cvs_process->argc; i++) { if( cvs_process->args[i] ) { free(cvs_process->args[i]); cvs_process->args[i] = 0L; } } free(cvs_process->args); cvs_process->args = 0L; } if( cvs_process == current_cvs_process ) cvs_process_pop(); if( !cvs_process->destroy ) { cvs_process->destroy = TRUE; #ifdef WIN32 // wait so the threads do not continue to use the pointer HANDLE WaitH[4]; int cnt = 0; DWORD curT = GetCurrentThreadId(); for(int i = 0; i < 4; i++) { if( cvs_process->threads[i] && curT != cvs_process->threadsID[i] ) WaitH[cnt++] = cvs_process->threads[i]; } WaitForMultipleObjects(cnt, WaitH, TRUE, INFINITE); // Close the handles for(int i = 0; i < 4; i++) { if( cvs_process->threads[i] && CloseHandle(cvs_process->threads[i]) ) { cvs_process->threads[i] = NULL; } } if( cvs_process->stopProcessEvent ) { CloseHandle(cvs_process->stopProcessEvent); } #endif free(cvs_process); } } } #ifdef WIN32 /*! Given an array of arguments that one might pass to spawnv construct a command line that one might pass to CreateProcess \param argc Arguments count \param argv Arguments \return The built command string \note Tries to quote things appropriately */ static char* build_command(int argc, char* const* argv) { int len; /* Compute the total length the command will have. */ { int i; len = 0; for(i = 0; i < argc; i++) { char* p; len += 2; /* for the double quotes */ for(p = argv[i]; *p; p++) { if( *p == '"' ) len += 2; else len++; } len++; /* for the space or the '\0' */ } } { /* The + 10 is in case len is 0. */ char* command = (char*)malloc(len + 10); int i; char* p; if( !command ) { errno = ENOMEM; return command; } p = command; *p = '\0'; /* copy each element of argv to command, putting each command in double quotes, and backslashing any quotes that appear within an argument. */ for(i = 0; i < argc; i++) { char* a; *p++ = '"'; for(a = argv[i]; *a; a++) { if( *a == '"' ) *p++ = '\\', *p++ = '"'; else *p++ = *a; } *p++ = '"'; *p++ = ' '; } if( p > command ) p[-1] = '\0'; return command; } } /// Read file buffer size #define BUFFSIZE 1024 /// Thread return value to mark successful completion #define THREAD_EXIT_SUCCESS (1) /*! Worker thread to read the cvs process stdout \param process Cvs process \return THREAD_EXIT_SUCCESS */ static DWORD GetChldOutput(CvsProcess* process) { char buff[BUFFSIZE]; DWORD dread; while( ReadFile(process->pstdout, buff, BUFFSIZE-1, &dread, NULL) ) { buff[dread] = '\0'; process->callbacks->consoleout(buff, dread, process); } return THREAD_EXIT_SUCCESS; } /*! Worker thread to read the cvs process stderr \param process Cvs process \return THREAD_EXIT_SUCCESS */ static DWORD GetChldError(CvsProcess* process) { char buff[BUFFSIZE]; DWORD dread; while( ReadFile(process->pstderr, buff, BUFFSIZE-1, &dread, NULL) ) { buff[dread] = '\0'; process->callbacks->consoleerr(buff, dread, process); } return THREAD_EXIT_SUCCESS; } /*! Worker thread to monitor the cvs process operation and stop request \param process Cvs process \return THREAD_EXIT_SUCCESS */ static DWORD MonitorChld(CvsProcess* process) { HANDLE waitHandles[2] = { process->pid, process->stopProcessEvent }; const DWORD waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); if( waitResult == (WAIT_OBJECT_0 + 1) ) { cvs_process_kill(process); } else { cvs_process_destroy(process); } return THREAD_EXIT_SUCCESS; } /*! Worker thread to read and process pipe communication \param process Cvs process \return THREAD_EXIT_SUCCESS */ static DWORD ServeProtocol(CvsProcess* process) { while( process->starting ) { Sleep(20); continue; } DWORD avail; while( cvs_process_is_active(process) && process->open ) { if( !PeekNamedPipe(process->my_read, 0L, 0 , 0L, &avail, 0L) ) break; if( avail == 0 ) { Sleep(20); continue; } cvs_process_recv_message(process); } return THREAD_EXIT_SUCCESS; } #endif #ifndef WIN32 /// Pointer to the process if sigtt_handler is forced to kill CvsProcess* sigtt_cvs_process = NULL; #define SIGTT_ERR "This CVS command required an interactive TTY, I had to kill it.\n" /*! Signal handler \param sig Signal */ static void sigtt_handler(int sig) { if( sigtt_cvs_process ) { // Keep that for later CvsProcessCallbacks* callbacks = sigtt_cvs_process->callbacks; // Killing the cvs process avoids getting stuck in a SIGSTOP cvs_process_destroy(sigtt_cvs_process); callbacks->consoleerr(SIGTT_ERR, strlen(SIGTT_ERR), sigtt_cvs_process); } sigtt_cvs_process = NULL; } #endif /*! Start the cvs process \param name Command to be started \param argc Arguments count \param argv Arguments \param callbacks stdout/stderr redirection callbacks \param startupInfo Additional parameters to control command's execution \param appData Application-specific data \return Pointer to the new CvsProcess on success, NULL otherwise */ CvsProcess* cvs_process_run(const char* name, int argc, char** argv, CvsProcessCallbacks* callbacks, CvsProcessStartupInfo* startupInfo, void* appData) { if( !callbacks || !startupInfo ) return 0L; CvsProcess* cvs_process = cvs_process_new(name, argc, argv); if( !cvs_process || !callbacks || !startupInfo ) return 0L; cvs_process->callbacks = callbacks; cvs_process->appData = appData; #ifndef WIN32 int my_read[2] = { 0, 0 }; int my_write[2] = { 0, 0 }; /* Open two pipes. (Bidirectional communication). */ if( (pipe(my_read) == -1) || (pipe(my_write) == -1) ) { fprintf(stderr, "unable to open pipe\n"); cvs_process_destroy(cvs_process); return 0L; } cvs_process->my_read = my_read[0]; cvs_process->my_write = my_write[1]; cvs_process->his_read = my_write[0]; cvs_process->his_write = my_read[1]; /* Remember the file descriptors for the pipes. */ sprintf(cvs_process->args[2], "%d", cvs_process->his_read); sprintf(cvs_process->args[3], "%d", cvs_process->his_write); /* Add the relevant commands to spawn in a terminal if necessary */ if( startupInfo->hasTty ) { cvs_process->argc += 2; char** old_args = cvs_process->args; cvs_process->args = (char**)malloc((cvs_process->argc + 1) * sizeof(char*)); cvs_process->args[0] = strdup("xterm"); cvs_process->args[1] = strdup("-e"); int i = 0; while( old_args[i] ) { cvs_process->args[i+2] = old_args[i]; i++; } cvs_process->args[cvs_process->argc] = 0L; free(old_args); } /* If we are running non interactively (i.e. gcvs&), it is * possible that the cvs process would require entering a password * or something similar (happens in some cases for CVS_RSH=ssh) * this would raise a SIGTTOU, which turns into a SIGSTOP if not handled */ sigtt_cvs_process = cvs_process; signal(SIGTTIN, sigtt_handler); // TODO: check if necessary on Mac signal(SIGTTOU, sigtt_handler); // TODO: check if necessary on Mac /* Fork another process. We'l remember the process id * so that we can later use it to kill the filter if * necessary. */ cvs_process->pid = fork(); if( cvs_process->pid == 0 ) { close(cvs_process->my_read); close(cvs_process->my_write); /* Execute the filter. The "_exit" call should never * be reached, unless some strange error condition * exists. */ execvp(cvs_process->args[0], cvs_process->args); _exit(1); } else if( cvs_process->pid == -1 ) { cvs_process_destroy(cvs_process); // fork failed sigtt_cvs_process = NULL; return 0L; } close(cvs_process->his_read); cvs_process->his_read = -1; close(cvs_process->his_write); cvs_process->his_write = -1; #else SECURITY_ATTRIBUTES lsa = { 0 }; STARTUPINFOA si = { 0 }; PROCESS_INFORMATION pi = { 0 }; HANDLE dupIn = 0L, dupOut = 0L; HANDLE stdChildIn = 0L, stdChildOut = 0L, stdChildErr = 0L; HANDLE stdoldIn = 0L, stdoldOut = 0L, stdoldErr = 0L; HANDLE stddupIn = 0L, stddupOut = 0L, stddupErr = 0L; char* command = 0L; BOOL resCreate; int cnt, i; LPTHREAD_START_ROUTINE threadsFunc[4]; lsa.nLength = sizeof(SECURITY_ATTRIBUTES); lsa.lpSecurityDescriptor = NULL; lsa.bInheritHandle = TRUE; // Create the pipes used for the cvsgui protocol if( !CreatePipe(&cvs_process->his_read, &dupIn, &lsa, 0) ) goto error; if( !CreatePipe(&dupOut, &cvs_process->his_write, &lsa, 0) ) goto error; // Duplicate the application side handles so they lose the inheritance if( !DuplicateHandle(GetCurrentProcess(), dupIn, GetCurrentProcess(), &cvs_process->my_write, 0, FALSE, DUPLICATE_SAME_ACCESS) ) { goto error; } CloseHandle(dupIn); dupIn = 0; if( !DuplicateHandle(GetCurrentProcess(), dupOut, GetCurrentProcess(), &cvs_process->my_read, 0, FALSE, DUPLICATE_SAME_ACCESS) ) { goto error; } CloseHandle(dupOut); dupOut = 0; if( !startupInfo->hasTty ) { // redirect stdout, stderr, stdin if( !CreatePipe(&stdChildIn, &stddupIn, &lsa, 0) ) goto error; if( !CreatePipe(&stddupOut, &stdChildOut, &lsa, 0) ) goto error; if( !CreatePipe(&stddupErr, &stdChildErr, &lsa, 0) ) goto error; // same thing as above if( !DuplicateHandle(GetCurrentProcess(), stddupIn, GetCurrentProcess(), &cvs_process->pstdin, 0, FALSE, DUPLICATE_SAME_ACCESS) ) { goto error; } CloseHandle(stddupIn); stddupIn = 0; if( !DuplicateHandle(GetCurrentProcess(), stddupOut, GetCurrentProcess(), &cvs_process->pstdout, 0, FALSE, DUPLICATE_SAME_ACCESS) ) { goto error; } CloseHandle(stddupOut); stddupOut = 0; if( !DuplicateHandle(GetCurrentProcess(), stddupErr, GetCurrentProcess(), &cvs_process->pstderr, 0, FALSE, DUPLICATE_SAME_ACCESS) ) { goto error; } CloseHandle(stddupErr); stddupErr = 0; } // Build the arguments for cvs sprintf(cvs_process->args[2], "%d", (int)cvs_process->his_read); sprintf(cvs_process->args[3], "%d", (int)cvs_process->his_write); command = build_command(cvs_process->argc, cvs_process->args); if( command == 0L ) goto error; // Redirect Console StdHandles and set the start options si.cb = sizeof(STARTUPINFO); si.dwFlags = startupInfo->hasTty ? 0 : STARTF_USESTDHANDLES; si.hStdInput = stdChildIn; si.hStdOutput = stdChildOut; si.hStdError = stdChildErr; if( !startupInfo->hasTty ) { si.dwFlags |= STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWMINNOACTIVE; stdoldIn = GetStdHandle(STD_INPUT_HANDLE); stdoldOut = GetStdHandle(STD_OUTPUT_HANDLE); stdoldErr = GetStdHandle(STD_ERROR_HANDLE); SetStdHandle(STD_INPUT_HANDLE, stdChildIn); SetStdHandle(STD_OUTPUT_HANDLE, stdChildOut); SetStdHandle(STD_ERROR_HANDLE, stdChildErr); } // Create Child Process resCreate = CreateProcessA( NULL, command, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED, NULL, startupInfo->currentDirectory && strlen(startupInfo->currentDirectory) ? startupInfo->currentDirectory : NULL, &si, &pi); if( !startupInfo->hasTty ) { SetStdHandle(STD_INPUT_HANDLE, stdoldIn); SetStdHandle(STD_OUTPUT_HANDLE, stdoldOut); SetStdHandle(STD_ERROR_HANDLE, stdoldErr); } if( !resCreate ) goto error; cvs_process->threadID = pi.dwProcessId; cvs_process->pid = pi.hProcess; cnt = 0; // Create a stop process event cvs_process->stopProcessEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); if( !cvs_process->stopProcessEvent ) { goto error; } // Now open-up threads which will serve the pipes threadsFunc[cnt++] = (LPTHREAD_START_ROUTINE)ServeProtocol; threadsFunc[cnt++] = (LPTHREAD_START_ROUTINE)MonitorChld; if( !startupInfo->hasTty ) { threadsFunc[cnt++] = (LPTHREAD_START_ROUTINE)GetChldOutput; threadsFunc[cnt++] = (LPTHREAD_START_ROUTINE)GetChldError; } for(i = 0; i < cnt; i++) { if( (cvs_process->threads[i] = ::CreateThread( (LPSECURITY_ATTRIBUTES)NULL, // No security attributes. (DWORD)0, // Use same stack size. (LPTHREAD_START_ROUTINE)threadsFunc[i], // Thread procedure. (LPVOID)cvs_process, // Parameter to pass. (DWORD)0, // Run immediately. (LPDWORD)&cvs_process->threadsID[i])) == NULL ) { TerminateProcess(cvs_process->pid, 0); goto error; } } // Close unnecessary Handles CloseHandle(cvs_process->his_read); cvs_process->his_read = 0; CloseHandle(cvs_process->his_write); cvs_process->his_write = 0; if( !startupInfo->hasTty ) { CloseHandle(stdChildIn); CloseHandle(stdChildOut); CloseHandle(stdChildErr); } // Resume process execution ResumeThread(pi.hThread); CloseHandle(pi.hThread); // Small tactical delay to reduce problems with process termination Sleep(100); free(command); goto goodboy; error: if( cvs_process->his_read != 0L ) CloseHandle(cvs_process->his_read); if( cvs_process->his_write != 0L ) CloseHandle(cvs_process->his_write); if( dupIn != 0L ) CloseHandle(dupIn); if( dupOut != 0L ) CloseHandle(dupOut); if( cvs_process->my_read != 0L ) CloseHandle(cvs_process->my_read); if( cvs_process->my_write != 0L ) CloseHandle(cvs_process->my_write); if( command != 0L ) free(command); if( stdChildIn != 0L ) CloseHandle(stdChildIn); if( stdChildOut != 0L ) CloseHandle(stdChildOut); if( stdChildErr != 0L ) CloseHandle(stdChildErr); if( stddupIn != 0L ) CloseHandle(stddupIn); if( stddupOut != 0L ) CloseHandle(stddupOut); if( stddupErr != 0L ) CloseHandle(stddupErr); if( cvs_process->pstdin != 0L ) CloseHandle(cvs_process->pstdin); if( cvs_process->pstdout != 0L ) CloseHandle(cvs_process->pstdout); if( cvs_process->pstderr != 0L ) CloseHandle(cvs_process->pstderr); cvs_process_destroy(cvs_process); return 0L; goodboy: #endif { CStackThreadLock locker; open_cvs_process.push_back(cvs_process); } cvs_process->open = TRUE; #ifdef WIN32 cvs_process->starting = FALSE; #endif return cvs_process; } /*! Close cvs project \param cvs_process Cvs process to be closed \param kill_it Flag indicating whether to kill the process */ static void cvs_process_close(CvsProcess* cvs_process, int kill_it) { CDestroyThreadLock locker; if( cvs_process && cvs_process->open ) { #ifdef WIN32 if( GetCurrentThreadId() != cvs_process->threadsID[0] ) { // We wait for all the protocol to be over before we close the process and the pipe. WaitForSingleObject(cvs_process->threads[0], 500); cvs_process->open = FALSE; WaitForSingleObject(cvs_process->threads[0], 500); } #endif cvs_process->open = FALSE; #ifndef WIN32 int status; /* If necessary, kill the filter. */ if( kill_it && cvs_process->pid ) status = kill(cvs_process->pid, SIGKILL); /* Wait for the process to exit. This will happen * immediately if it was just killed. */ if( cvs_process->pid ) waitpid(cvs_process->pid, &status, 0); /* Close the pipes. */ if( cvs_process->my_read ) close(cvs_process->my_read); if( cvs_process->my_write ) close(cvs_process->my_write); if( cvs_process->his_read ) close(cvs_process->his_read); if( cvs_process->his_write ) close(cvs_process->his_write); #else cvs_process->starting = TRUE; if( kill_it && cvs_process->pid ) { cvs_process_terminate(cvs_process); } if( cvs_process->pid ) CloseHandle(cvs_process->pid); if( cvs_process->my_read ) CloseHandle(cvs_process->my_read); if( cvs_process->my_write ) CloseHandle(cvs_process->my_write); if( cvs_process->his_read ) CloseHandle(cvs_process->his_read); if( cvs_process->his_write ) CloseHandle(cvs_process->his_write); if( cvs_process->pstdin ) CloseHandle(cvs_process->pstdin); if( cvs_process->pstdout ) CloseHandle(cvs_process->pstdout); if( cvs_process->pstderr ) CloseHandle(cvs_process->pstderr); #endif wire_clear_error(); /* Set the fields to null values */ cvs_process->pid = 0; cvs_process->my_read = 0; cvs_process->my_write = 0; cvs_process->his_read = 0; cvs_process->his_write = 0; cvs_process->pstdin = 0; cvs_process->pstdout = 0; cvs_process->pstderr = 0; { CStackThreadLock locker; std::vector::iterator i = std::find(open_cvs_process.begin(), open_cvs_process.end(), cvs_process); if( i != open_cvs_process.end() ) open_cvs_process.erase(i); } } } /*! Tells if the process is still alive \param cvs_process Cvs process to be tested \return Zero if process not active, non-zero otherwise */ int cvs_process_is_active(const CvsProcess* cvs_process) { int res; { CStackThreadLock locker; std::vector::iterator i = std::find(open_cvs_process.begin(), open_cvs_process.end(), cvs_process); res = i != open_cvs_process.end() ? 1 : 0; } return res; } /*! Close a process. This kills the process and releases its resources \param cvs_process Cvs process to be killed */ void cvs_process_kill(CvsProcess* cvs_process) { if( cvs_process_is_active(cvs_process) ) { cvs_process_destroy(cvs_process); } } /*! Stop the process \param cvs_process Cvs process to be stopped */ void cvs_process_stop(CvsProcess* cvs_process) { #ifdef WIN32 // On windows we need to kill process "from within" to avoid race condition with MonitorChld thread SetEvent(cvs_process->stopProcessEvent); #else cvs_process_kill(cvs_process); #endif } /*! Called by the application to answer calls from the process \return Non-zero if a message from cvs was handled, zero otherwise */ int cvs_process_give_time(void) { #ifndef WIN32 fd_set rset; int ready; int maxfd = 0; int fd; struct timeval tv; int didone = 0; FD_ZERO(&rset); std::vector::iterator i; for(i = open_cvs_process.begin(); i != open_cvs_process.end(); ++i) { fd = (*i)->my_read; FD_SET(fd, &rset); if( fd > maxfd ) maxfd = fd; } tv.tv_sec = 0; tv.tv_usec = 10000; // was 100000, but this blocks the interactive dialogs e.g. password dialog ready = select(maxfd + 1, &rset, 0L, 0L, &tv); std::vector toFire; if( ready > 0 ) { for(i = open_cvs_process.begin(); i != open_cvs_process.end(); ++i) { fd = (*i)->my_read; if( FD_ISSET(fd, &rset) ) toFire.push_back(*i); } } for(i = toFire.begin(); i != toFire.end(); ++i) { fd = (*i)->my_read; if( FD_ISSET(fd, &rset) ) { cvs_process_recv_message(*i); didone = 1; } } return didone; #else // We don't have to do yield since we use preemptive threads return 0; #endif } /*! Receive messages \param cvs_process Cvs process */ static void cvs_process_recv_message(CvsProcess* cvs_process) { WireMessage msg; cvs_process_push(cvs_process); memset(&msg, 0, sizeof(WireMessage)); if( !wire_read_msg(cvs_process->my_read, &msg) ) { cvs_process_close(cvs_process, TRUE); } else { cvs_process_handle_message(&msg); wire_destroy(&msg); } if( cvs_process_is_active(current_cvs_process) ) { if( !current_cvs_process->open ) { #ifndef WIN32 cvs_process_destroy(current_cvs_process); #endif } else cvs_process_pop(); } } /*! Handle message \param msg Message to be handled */ static void cvs_process_handle_message(WireMessage* msg) { switch(msg->type) { case GP_QUIT: { GPT_QUIT* t = (GPT_QUIT*)msg->data; #ifdef WIN32 // Small delay to work around problems on Win9x (we will have to find the "real" solution in the future though...) Sleep(100); #endif current_cvs_process->callbacks->exit(t->code, current_cvs_process); cvs_process_close(current_cvs_process, FALSE); break; } case GP_GETENV: { GPT_GETENV* t = (GPT_GETENV*)msg->data; cvs_process_push(current_cvs_process); gp_getenv_write(current_cvs_process->my_write, current_cvs_process->callbacks->getenv(t->str, current_cvs_process)); cvs_process_pop(); break; } case GP_CONSOLE: { GPT_CONSOLE* t = (GPT_CONSOLE*)msg->data; if( t->isStderr ) current_cvs_process->callbacks->consoleerr(t->str, t->len, current_cvs_process); else current_cvs_process->callbacks->consoleout(t->str, t->len, current_cvs_process); break; } } } /*! Write data to the cvs process \param fd Pipe \param buf Buffer \param count Buffer size \return TRUE if written, FALSE otherwise */ static int cvs_process_write(pipe_t fd, guint8* buf, gulong count) { gulong bytes; if( current_write_buffer == 0L ) current_write_buffer = process_write_buffer; while( count > 0 ) { if( (current_write_buffer_index + count) >= WRITE_BUFFER_SIZE ) { bytes = WRITE_BUFFER_SIZE - current_write_buffer_index; memcpy(¤t_write_buffer[current_write_buffer_index], buf, bytes); current_write_buffer_index += bytes; if( !wire_flush(fd) ) return FALSE; } else { bytes = count; memcpy(¤t_write_buffer[current_write_buffer_index], buf, bytes); current_write_buffer_index += bytes; } buf += bytes; count -= bytes; } return TRUE; } /*! Flush cvs process data \param fd Pipe \return TRUE if succesfull, FALSE otherwise */ static int cvs_process_flush(pipe_t fd) { int count; int bytes; if( current_write_buffer_index > 0 ) { count = 0; while( count != current_write_buffer_index ) { do { bytes = write(fd, ¤t_write_buffer[count], (current_write_buffer_index - count)); }while( (bytes == -1) && (errno == EAGAIN) ); if( bytes == -1 ) return FALSE; count += bytes; } current_write_buffer_index = 0; } return TRUE; } /*! Puts cvs process to the stack \param cvs_process Cvs process to be put */ static void cvs_process_push(CvsProcess* cvs_process) { CStackThreadLock locker; if( cvs_process ) { current_cvs_process = cvs_process; cvs_process_stack.push_back(current_cvs_process); current_write_buffer_index = current_cvs_process->write_buffer_index; current_write_buffer = current_cvs_process->write_buffer; } else { current_write_buffer_index = 0; current_write_buffer = NULL; } } /*! Take cvs process from the stack */ static void cvs_process_pop() { CStackThreadLock locker; if( current_cvs_process ) { current_cvs_process->write_buffer_index = current_write_buffer_index; cvs_process_stack.pop_back(); } if( !cvs_process_stack.empty() ) { current_cvs_process = cvs_process_stack.back(); current_write_buffer_index = current_cvs_process->write_buffer_index; current_write_buffer = current_cvs_process->write_buffer; } else { current_cvs_process = NULL; current_write_buffer_index = 0; current_write_buffer = NULL; } }