/* ** 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 */ /* * plugin.c from the GIMP modified for the cvsgui project by : * Alexandre Parenteau --- November 1999 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STRINGS_H #include #endif #include #include #include #include #include #include #include #include #ifndef WIN32 # include # include # include # include #endif #include #include #include #if TARGET_RT_MAC_MACHO # define STD_STL std:: // probable transitory problem with MWERKS on Mach-O #else # define STD_STL std:: #endif #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); 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_STL deque cvs_process_stack; static STD_STL vector open_cvs_process; static CvsProcess *current_cvs_process = NULL; static int current_write_buffer_index = 0; static char *current_write_buffer = NULL; static char process_write_buffer[WRITE_BUFFER_SIZE]; /* Buffer for writing, only for the process */ #ifdef WIN32 // use the runtime to initialize/delete a critical section class CDummyInit { public: CDummyInit() { ::InitializeCriticalSection(&m_destroylock); ::InitializeCriticalSection(&m_stacklock); } ~CDummyInit() { ::DeleteCriticalSection(&m_destroylock); ::DeleteCriticalSection(&m_stacklock); } static CRITICAL_SECTION m_destroylock; static CRITICAL_SECTION m_stacklock; } sDummyObject; CRITICAL_SECTION CDummyInit::m_destroylock; CRITICAL_SECTION CDummyInit::m_stacklock; #endif #ifdef WIN32 static int write(pipe_t fd, const void *buf, int count) { DWORD dwrite; if(!WriteFile(fd,buf,count,&dwrite,NULL)) { errno = EINVAL; return -1; } return dwrite; } #endif #ifndef WIN32 struct CDestroyThreadLock { public: inline CDestroyThreadLock() { } inline ~CDestroyThreadLock() { } }; struct CStackThreadLock { public: inline CStackThreadLock() { } inline ~CStackThreadLock() { } }; #else struct CDestroyThreadLock { public: inline CDestroyThreadLock() { EnterCriticalSection(&CDummyInit::m_destroylock); } inline ~CDestroyThreadLock() { LeaveCriticalSection(&CDummyInit::m_destroylock); } }; struct CStackThreadLock { public: inline CStackThreadLock() { EnterCriticalSection(&CDummyInit::m_stacklock); } inline ~CStackThreadLock() { LeaveCriticalSection(&CDummyInit::m_stacklock); } }; #endif 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; } 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; } static void _gp_quit_destroy (WireMessage *msg) { free(msg->data); } 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; } 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)) return; } static void _gp_getenv_destroy (WireMessage *msg) { GPT_GETENV *t = (GPT_GETENV *)msg->data; free(t->str); free(t); } 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; } 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)) return; } static void _gp_console_destroy (WireMessage *msg) { GPT_CONSOLE *t = (GPT_CONSOLE *)msg->data; free(t->str); free(t); } 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); } 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; } 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; } 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 int gp_console_write (pipe_t fd, const char *str, int len, int isStderr) { WireMessage msg; GPT_CONSOLE *t = (GPT_CONSOLE *)malloc(sizeof(GPT_CONSOLE)); 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; } void cvs_process_init () { /* initialize the gimp protocol library and set the read and * write handlers. */ gp_init (); wire_set_writer (cvs_process_write); wire_set_flusher (cvs_process_flush); } static CvsProcess* cvs_process_new (char *path, 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; #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 (path); 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; #ifdef WIN32 memset(cvs_process->threads, 0, sizeof(cvs_process->threads)); memset(cvs_process->threadsID, 0, sizeof(cvs_process->threadsID)); #endif return cvs_process; } 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(i = 0; i < 4; i++) { if( cvs_process->threads[i] && CloseHandle(cvs_process->threads[i]) ) { cvs_process->threads[i] = NULL; } } #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. Try 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; } } #define BUFFSIZE 1024 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); } return(1); } 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); } return(1); } static DWORD WaitChld( CvsProcess *process ) { WaitForSingleObject(process->pid, INFINITE); cvs_process_destroy(process); return(1); } 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(1); } #endif #ifdef _0_ CvsProcess * cvs_process_test (char *name, int argc, char **argv, CvsProcessCallbacks *callbacks) { CvsProcess *cvs_process = cvs_process_new(name, argc, argv); if (!cvs_process) return 0L; cvs_process->callbacks = callbacks; #ifndef WIN32 cvs_process->my_read = 0; cvs_process->my_write = 0; cvs_process->his_read = 0; cvs_process->his_write = 0; /* Remember the file descriptors for the pipes. */ sprintf (cvs_process->args[2], "0"); sprintf (cvs_process->args[3], "0"); /* Fork another process. We'll 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); 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; STARTUPINFO si; PROCESS_INFORMATION pi; 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 App 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(!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", cvs_process->his_read); sprintf (cvs_process->args[3], "%d", cvs_process->his_write); command = build_command(cvs_process->argc, cvs_process->args); if(command == 0L) goto error; // Lets Redirect Console StdHandles and set the start options memset(&si,0,sizeof(STARTUPINFO)); si.cb=sizeof(STARTUPINFO); si.dwFlags=hasTty ? 0 : STARTF_USESTDHANDLES; si.hStdInput =stdChildIn; si.hStdOutput=stdChildOut; si.hStdError =stdChildErr; if(!hasTty) { si.dwFlags|=STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWMINNOACTIVE; } if(!hasTty) { 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 = CreateProcess ( NULL, command, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, currentDirectory, &si, &pi); if(!hasTty) { SetStdHandle(STD_INPUT_HANDLE, stdoldIn); SetStdHandle(STD_OUTPUT_HANDLE, stdoldOut); SetStdHandle(STD_ERROR_HANDLE, stdoldErr); } if(!resCreate) goto error; cvs_process->pid = pi.hProcess; cnt = 0; // now open-up threads which will serve the pipes threadsFunc[cnt++] = (LPTHREAD_START_ROUTINE)ServeProtocol; threadsFunc[cnt++] = (LPTHREAD_START_ROUTINE)WaitChld; if(!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 unneccesary Handles CloseHandle(cvs_process->his_read); cvs_process->his_read = 0; CloseHandle(cvs_process->his_write); cvs_process->his_write = 0; if(!hasTty) { CloseHandle(stdChildIn); CloseHandle(stdChildOut); CloseHandle(stdChildErr); } CloseHandle(pi.hThread); 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; } #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" 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((char *)SIGTT_ERR, strlen(SIGTT_ERR)); } sigtt_cvs_process = NULL; } #endif CvsProcess * cvs_process_open (char *name, int argc, char **argv, CvsProcessCallbacks *callbacks, int hasTty, const char* currentDirectory) { CvsProcess *cvs_process = cvs_process_new(name, argc, argv); if (!cvs_process) return 0L; cvs_process->callbacks = callbacks; #ifndef WIN32 int my_read[2]; int my_write[2]; /* 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); /* TTimo * add the relevant commands to spawn in a terminal if necessary */ if (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'll 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) { // fork failed cvs_process_destroy (cvs_process); 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; STARTUPINFO si; PROCESS_INFORMATION pi; 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 App 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(!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", cvs_process->his_read); sprintf (cvs_process->args[3], "%d", cvs_process->his_write); command = build_command(cvs_process->argc, cvs_process->args); if(command == 0L) goto error; // Lets Redirect Console StdHandles and set the start options memset(&si,0,sizeof(STARTUPINFO)); si.cb=sizeof(STARTUPINFO); si.dwFlags=hasTty ? 0 : STARTF_USESTDHANDLES; si.hStdInput =stdChildIn; si.hStdOutput=stdChildOut; si.hStdError =stdChildErr; if(!hasTty) { si.dwFlags|=STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWMINNOACTIVE; } if(!hasTty) { 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 = CreateProcess ( NULL, command, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, currentDirectory, &si, &pi); if(!hasTty) { SetStdHandle(STD_INPUT_HANDLE, stdoldIn); SetStdHandle(STD_OUTPUT_HANDLE, stdoldOut); SetStdHandle(STD_ERROR_HANDLE, stdoldErr); } if(!resCreate) goto error; cvs_process->pid = pi.hProcess; cnt = 0; // now open-up threads which will serve the pipes threadsFunc[cnt++] = (LPTHREAD_START_ROUTINE)ServeProtocol; threadsFunc[cnt++] = (LPTHREAD_START_ROUTINE)WaitChld; if(!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 unneccesary Handles CloseHandle(cvs_process->his_read); cvs_process->his_read = 0; CloseHandle(cvs_process->his_write); cvs_process->his_write = 0; if(!hasTty) { CloseHandle(stdChildIn); CloseHandle(stdChildOut); CloseHandle(stdChildErr); } CloseHandle(pi.hThread); 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; } 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; // this is bad to do that, cvs won't have a chance // to raise the KILL signal (sigh...) if (kill_it && cvs_process->pid) { TerminateProcess(cvs_process->pid, -1); } 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_STL vector::iterator i = STD_STL find(open_cvs_process.begin(), open_cvs_process.end(), cvs_process); if(i != open_cvs_process.end()) open_cvs_process.erase(i); } } } int cvs_process_is_active(CvsProcess *cvs_process) { int res; { CStackThreadLock locker; STD_STL vector::iterator i = STD_STL find(open_cvs_process.begin(), open_cvs_process.end(), cvs_process); res = i != open_cvs_process.end() ? 1 : 0; } return res; } void cvs_process_kill(CvsProcess *cvs_process) { if(cvs_process_is_active(cvs_process)) { cvs_process_destroy(cvs_process); } } 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_STL 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_STL 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 } static void cvs_process_recv_message (CvsProcess *p) { WireMessage msg; cvs_process_push (p); memset (&msg, 0, sizeof (WireMessage)); if (!wire_read_msg (p->my_read, &msg)) { cvs_process_close (p, 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 (); } } static void cvs_process_handle_message (WireMessage *msg) { switch (msg->type) { case GP_QUIT: { GPT_QUIT *t = (GPT_QUIT *)msg->data; current_cvs_process->callbacks->exit(t->code); 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)); 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); else current_cvs_process->callbacks->consoleout(t->str, t->len); break; } } } 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; } 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; } 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; } } 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; } }