2 Copyright (C) 2010 Paul Davis
3 Author: Robin Gareus <robin@gareus.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/ioctl.h>
36 #include "system_exec.h"
39 void * interposer_thread (void *arg);
41 SystemExec::SystemExec (std::string c, std::string a)
44 pthread_mutex_init(&write_lock, NULL);
52 stdinP[0] = stdinP[1] = INVALID_HANDLE_VALUE;
53 stdoutP[0] = stdoutP[1] = INVALID_HANDLE_VALUE;
54 stderrP[0] = stderrP[1] = INVALID_HANDLE_VALUE;
60 SystemExec::SystemExec (std::string c, char **a)
63 pthread_mutex_init(&write_lock, NULL);
70 stdinP[0] = stdinP[1] = INVALID_HANDLE_VALUE;
71 stdoutP[0] = stdoutP[1] = INVALID_HANDLE_VALUE;
72 stderrP[0] = stderrP[1] = INVALID_HANDLE_VALUE;
78 SystemExec::~SystemExec ()
82 for (int i=0;envp[i];++i) {
88 for (int i=0;argp[i];++i) {
94 if (w_args) free(w_args);
96 pthread_mutex_destroy(&write_lock);
100 interposer_thread (void *arg) {
101 SystemExec *sex = static_cast<SystemExec *>(arg);
102 sex->output_interposer();
107 #ifdef __WIN32__ /* Windows Process */
109 /* HELPER FUNCTIONS */
111 static void create_pipe (HANDLE *pipe, bool in) {
112 SECURITY_ATTRIBUTES secAtt = { sizeof( SECURITY_ATTRIBUTES ), NULL, TRUE };
115 if (!CreatePipe(&pipe[0], &tmpHandle, &secAtt, 1024 * 1024)) return;
116 if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), &pipe[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) return;
118 if (!CreatePipe(&tmpHandle, &pipe[1], &secAtt, 1024 * 1024)) return;
119 if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), &pipe[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) return;
121 CloseHandle(tmpHandle);
124 static void destroy_pipe (HANDLE pipe[2]) {
125 if (pipe[0] != INVALID_HANDLE_VALUE) {
126 CloseHandle(pipe[0]);
127 pipe[0] = INVALID_HANDLE_VALUE;
129 if (pipe[1] != INVALID_HANDLE_VALUE) {
130 CloseHandle(pipe[1]);
131 pipe[1] = INVALID_HANDLE_VALUE;
135 static BOOL CALLBACK my_terminateApp(HWND hwnd, LPARAM procId)
137 DWORD currentProcId = 0;
138 GetWindowThreadProcessId(hwnd, ¤tProcId);
139 if (currentProcId == (DWORD)procId)
140 PostMessage(hwnd, WM_CLOSE, 0, 0);
147 SystemExec::make_envp() {
148 ;/* environemt is copied over with CreateProcess(...,env=0 ,..) */
152 SystemExec::make_wargs(char **a) {
153 std::string wa = cmd;
154 if (cmd[0] != '"' && cmd[cmd.size()] != '"' && strchr(cmd.c_str(), ' ')) { wa = "\"" + cmd + "\""; }
155 std::replace(cmd.begin(), cmd.end(), '/', '\\' );
157 while (tmp && *tmp) {
163 w_args = strdup(wa.c_str());
167 SystemExec::make_argp(std::string args) {
168 std::string wa = cmd;
169 if (cmd[0] != '"' && cmd[cmd.size()] != '"' && strchr(cmd.c_str(), ' ')) { wa = "\"" + cmd + "\""; }
170 std::replace(cmd.begin(), cmd.end(), '/', '\\' );
173 w_args = strdup(wa.c_str());
177 SystemExec::terminate ()
179 ::pthread_mutex_lock(&write_lock);
182 EnumWindows(my_terminateApp, (LPARAM)pid->dwProcessId);
183 PostThreadMessage(pid->dwThreadId, WM_CLOSE, 0, 0);
186 TerminateProcess(pid->hProcess, 0xf291);
188 CloseHandle(pid->hThread);
189 CloseHandle(pid->hProcess);
190 destroy_pipe(stdinP);
191 destroy_pipe(stdoutP);
192 destroy_pipe(stderrP);
196 ::pthread_mutex_unlock(&write_lock);
200 SystemExec::wait (int options)
202 while (is_running()) {
203 WaitForSingleObject(pid->hProcess, INFINITE);
210 SystemExec::is_running ()
212 return pid?true:false;
216 SystemExec::start (int stderr_mode)
218 char* working_dir = 0;
220 if (pid) { return 0; }
222 pid = new PROCESS_INFORMATION;
223 memset(pid, 0, sizeof(PROCESS_INFORMATION));
225 create_pipe(stdinP, true);
226 create_pipe(stdoutP, false);
228 if (stderr_mode == 2) {
229 /* merge stout & stderr */
230 DuplicateHandle(GetCurrentProcess(), stdoutP[1], GetCurrentProcess(), &stderrP[1], 0, TRUE, DUPLICATE_SAME_ACCESS);
231 } else if (stderr_mode == 1) {
232 //TODO read/flush this pipe or close it...
233 create_pipe(stderrP, false);
235 //TODO: keep stderr of this process mode.
238 bool success = false;
239 STARTUPINFOA startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
240 (unsigned long)CW_USEDEFAULT, (unsigned long)CW_USEDEFAULT,
241 (unsigned long)CW_USEDEFAULT, (unsigned long)CW_USEDEFAULT,
243 STARTF_USESTDHANDLES,
245 stdinP[0], stdoutP[1], stderrP[1]
248 success = CreateProcess(0, w_args,
249 0, 0, /* bInheritHandles = */ TRUE,
250 (CREATE_NO_WINDOW&0) | CREATE_UNICODE_ENVIRONMENT | (0&CREATE_NEW_CONSOLE),
255 if (stdinP[0] != INVALID_HANDLE_VALUE) {
256 CloseHandle(stdinP[0]);
257 stdinP[0] = INVALID_HANDLE_VALUE;
259 if (stdoutP[1] != INVALID_HANDLE_VALUE) {
260 CloseHandle(stdoutP[1]);
261 stdoutP[1] = INVALID_HANDLE_VALUE;
263 if (stderrP[1] != INVALID_HANDLE_VALUE) {
264 CloseHandle(stderrP[1]);
265 stderrP[1] = INVALID_HANDLE_VALUE;
269 CloseHandle(pid->hThread);
270 CloseHandle(pid->hProcess);
271 destroy_pipe(stdinP);
272 destroy_pipe(stdoutP);
273 destroy_pipe(stderrP);
279 int rv = pthread_create(&thread_id_tt, NULL, interposer_thread, this);
291 SystemExec::output_interposer()
295 #if 0 // untested code to set up nonblocking
297 ioctlsocket(stdoutP[0], FIONBIO, &l);
300 #if 0 // for non-blocking pipes..
301 DWORD bytesAvail = 0;
302 PeekNamedPipe(stdoutP[0], 0, 0, 0, &bytesAvail, 0);
303 if (bytesAvail < 1) {Sleep(500); printf("N/A\n"); continue;}
305 if (stdoutP[0] == INVALID_HANDLE_VALUE) break;
306 if (!ReadFile(stdoutP[0], data, BUFSIZ, &bytesRead, 0)) break;
307 if (bytesRead < 1) continue; /* actually not needed; but this is safe. */
309 ReadStdout(data, bytesRead);/* EMIT SIGNAL */
311 Terminated();/* EMIT SIGNAL */
315 SystemExec::close_stdin()
317 if (stdinP[0]!= INVALID_HANDLE_VALUE) FlushFileBuffers(stdinP[0]);
318 if (stdinP[1]!= INVALID_HANDLE_VALUE) FlushFileBuffers(stdinP[1]);
320 destroy_pipe(stdinP);
324 SystemExec::write_to_stdin(std::string d, size_t len)
329 ::pthread_mutex_lock(&write_lock);
337 if (!WriteFile(stdinP[1], data+c, len-c, &r, NULL)) {
338 if (GetLastError() == 0xE8 /*NT_STATUS_INVALID_USER_BUFFER*/) {
342 fprintf(stderr, "SYSTEM-EXEC: stdin write error.\n");
348 ::pthread_mutex_unlock(&write_lock);
353 /* end windows process */
355 /* UNIX/POSIX process */
357 extern char **environ;
359 SystemExec::make_envp() {
361 envp = (char **) calloc(1, sizeof(char*));
362 /* copy current environment */
363 for (i=0;environ[i];++i) {
364 envp[i] = strdup(environ[i]);
365 envp = (char **) realloc(envp, (i+2) * sizeof(char*));
371 SystemExec::make_argp(std::string args) {
376 char *carg = strdup(args.c_str());
378 argp = (char **) malloc((argn + 1) * sizeof(char *));
379 if (argp == (char **) 0) {
384 argp[0] = strdup(cmd.c_str());
386 /* TODO: quotations and escapes
387 * http://stackoverflow.com/questions/1511797/convert-string-to-argv-in-c
389 * It's actually not needed. All relevant invocations specify 'argp' directly.
390 * Only 'xjadeo -L -R' uses this function and that uses neither quotations
391 * nor arguments with white-space.
393 for (cp1 = cp2 = carg; *cp2 != '\0'; ++cp2) {
396 argp[argn++] = strdup(cp1);
398 argp = (char **) realloc(argp, (argn + 1) * sizeof(char *));
402 argp[argn++] = strdup(cp1);
403 argp = (char **) realloc(argp, (argn + 1) * sizeof(char *));
405 argp[argn] = (char *) 0;
412 SystemExec::terminate ()
414 ::pthread_mutex_lock(&write_lock);
423 ::kill(pid, SIGTERM);
429 ::fprintf(stderr, "Process is still running! trying SIGKILL\n");
430 ::kill(pid, SIGKILL);
434 if (thread_active) pthread_join(thread_id_tt, NULL);
435 thread_active = false;
436 ::pthread_mutex_unlock(&write_lock);
440 SystemExec::wait (int options)
443 if (pid==0) return -1;
444 if (pid==::waitpid(pid, &status, options)) {
447 if (errno == ECHILD) {
454 SystemExec::is_running ()
457 if (pid==0) return false;
458 if (::waitpid(pid, &status, WNOHANG)==0) return true;
463 SystemExec::start (int stderr_mode)
466 return 0; // mmh what to return here?
470 if (::pipe(pin) < 0 || ::pipe(pout) < 0 || ::pipe(pok) < 0) {
471 /* Something unexpected went wrong creating a pipe. */
485 /* check if execve was successful. */
489 ssize_t n = ::read(pok[0], &buf, 1 );
491 /* child process returned from execve */
500 } else if ( n==-1 ) {
501 if ( errno==EAGAIN || errno==EINTR )
507 /* child started successfully */
510 /* use fork for output-interposer
511 * it will run in a separated process
513 /* catch stdout thread */
521 /* 2nd child process - catch stdout */
530 #else /* use pthread */
533 int rv = pthread_create(&thread_id_tt, NULL, interposer_thread, this);
542 return 0; /* all systems go - return to main */
545 /* child process - exec external process */
547 ::fcntl(pok[1], F_SETFD, FD_CLOEXEC);
550 if (pin[0] != STDIN_FILENO) {
551 ::dup2(pin[0], STDIN_FILENO);
555 if (pout[1] != STDOUT_FILENO) {
556 ::dup2(pout[1], STDOUT_FILENO);
559 if (stderr_mode == 2) {
560 /* merge STDERR into output */
561 if (pout[1] != STDERR_FILENO) {
562 ::dup2(pout[1], STDERR_FILENO);
564 } else if (stderr_mode == 1) {
566 ::close(STDERR_FILENO);
571 if (pout[1] != STDOUT_FILENO && pout[1] != STDERR_FILENO) {
580 /* chdir to executable dir */
582 directory = strdup(cmd.c_str());
583 if (strrchr(directory, '/') != (char *) 0) {
590 sigset(SIGPIPE, SIG_DFL);
592 signal(SIGPIPE, SIG_DFL);
595 ::execve(argp[0], argp, envp);
596 /* if we reach here something went wrong.. */
598 (void) ::write(pok[1], &buf, 1 );
599 (void) ::close(pok[1]);
605 SystemExec::output_interposer()
612 ioctl(rfd, FIONBIO, &l); // set non-blocking I/O
614 for (;fcntl(rfd, F_GETFL)!=-1;) {
615 r = read(rfd, buf, sizeof(buf));
616 if (r < 0 && (errno == EINTR || errno == EAGAIN)) {
624 std::string rv = std::string(buf,r); // TODO: check allocation strategy
625 ReadStdout(rv, r);/* EMIT SIGNAL */
627 Terminated();/* EMIT SIGNAL */
631 SystemExec::close_stdin()
633 if (pin[1]<0) return;
638 pin[1] = - 1; // mark as closed
642 SystemExec::write_to_stdin(std::string d, size_t len)
646 ::pthread_mutex_lock(&write_lock);
655 r=::write(pin[1], data+c, len-c);
656 if (r < 0 && (errno == EINTR || errno == EAGAIN)) {
661 ::pthread_mutex_unlock(&write_lock);
669 ::pthread_mutex_unlock(&write_lock);
673 #endif // end UNIX process