/* Copyright (C) 2010 Paul Davis Copyright (C) 2010-2014 Robin Gareus Copyright (C) 2005-2008 Lennart Poettering This program 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 2 of the License, or (at your option) any later version. This program 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #ifndef COMPILER_MSVC #include #endif #ifdef PLATFORM_WINDOWS #include #else #include #include #include #include #include #include #include #endif #include #define USE_VFORK #include "pbd/file_utils.h" #include "pbd/search_path.h" #include "pbd/system_exec.h" using namespace std; using namespace PBD; static void * interposer_thread (void *arg); #ifndef PLATFORM_WINDOWS /* POSIX Process only */ static void close_fd (int& fd) { if (fd >= 0) ::close (fd); fd = -1; } #endif #if (!defined PLATFORM_WINDOWS && defined NO_VFORK) /* * This function was part of libasyncns. * LGPL v2.1 * Copyright 2005-2008 Lennart Poettering */ static int close_allv(const int except_fds[]) { struct rlimit rl; int fd; #ifdef __linux__ DIR *d; assert(except_fds); if ((d = opendir("/proc/self/fd"))) { struct dirent *de; while ((de = readdir(d))) { int found; long l; char *e = NULL; int i; if (de->d_name[0] == '.') continue; errno = 0; l = strtol(de->d_name, &e, 10); if (errno != 0 || !e || *e) { closedir(d); errno = EINVAL; return -1; } fd = (int) l; if ((long) fd != l) { closedir(d); errno = EINVAL; return -1; } if (fd < 3) continue; if (fd == dirfd(d)) continue; found = 0; for (i = 0; except_fds[i] >= 0; i++) if (except_fds[i] == fd) { found = 1; break; } if (found) continue; if (close(fd) < 0) { int saved_errno; saved_errno = errno; closedir(d); errno = saved_errno; return -1; } } closedir(d); return 0; } #endif if (getrlimit(RLIMIT_NOFILE, &rl) < 0) return -1; for (fd = 0; fd < (int) rl.rlim_max; fd++) { int i; if (fd <= 3) continue; for (i = 0; except_fds[i] >= 0; i++) if (except_fds[i] == fd) continue; if (close(fd) < 0 && errno != EBADF) return -1; } return 0; } #endif /* not on windows, nor vfork */ void SystemExec::init () { pthread_mutex_init(&write_lock, NULL); thread_active=false; pid = 0; pin[1] = -1; nicelevel = 0; envp = NULL; #ifdef PLATFORM_WINDOWS stdinP[0] = stdinP[1] = INVALID_HANDLE_VALUE; stdoutP[0] = stdoutP[1] = INVALID_HANDLE_VALUE; stderrP[0] = stderrP[1] = INVALID_HANDLE_VALUE; w_args = NULL; #endif } SystemExec::SystemExec (std::string c, std::string a) : cmd(c) { init (); argp = NULL; make_envp(); make_argp(a); } SystemExec::SystemExec (std::string c, char **a) : cmd(c) , argp(a) { init (); #ifdef PLATFORM_WINDOWS make_wargs(a); #endif make_envp(); } SystemExec::SystemExec (std::string command, const std::map subs) { init (); make_argp_escaped(command, subs); #ifdef PLATFORM_WINDOWS if (argp[0] && strlen (argp[0]) > 0) { std::string wa = argp[0]; // only add quotes to command if required.. if (argp[0][0] != '"' && argp[0][strlen(argp[0])-1] != '"' && strchr(argp[0], ' ')) { wa = "\""; wa += argp[0]; wa += "\""; } // ...but always quote all args for (int i = 1; argp[i]; ++i) { std::string tmp (argp[i]); while (tmp.find("\"") != std::string::npos) tmp.replace(tmp.find("\""), 1, "\\\""); wa += " \""; wa += tmp; wa += '"'; } w_args = strdup(wa.c_str()); } #else if (find_file (Searchpath (Glib::getenv ("PATH")), argp[0], cmd)) { // argp[0] exists in $PATH` - set it to the actual path where it was found free (argp[0]); argp[0] = strdup(cmd.c_str ()); } // else argp[0] not found in path - leave it as-is, it might be an absolute path // Glib::find_program_in_path () is only available in Glib >= 2.28 // cmd = Glib::find_program_in_path (argp[0]); #endif make_envp(); } void SystemExec::make_argp_escaped(std::string command, const std::map subs) { int inquotes = 0; int n = 0; size_t i = 0; std::string arg = ""; argp = (char **) malloc(sizeof(char *)); for (i = 0; i <= command.length(); i++) { // include terminating '\0' char c = command.c_str()[i]; if (inquotes) { if (c == '"') { inquotes = 0; } else { // still in quotes - just copy arg += c; } } else switch (c) { case '%' : c = command.c_str()[++i]; if (c == '%' || c == '\0') { // "%%", "%" at end-of-string => "%" arg += '%'; } else { // search subs for string to substitute for char std::map::const_iterator s = subs.find(c); if (s != subs.end()) { // found substitution arg += s->second; } else { // not a valid substitution, just copy arg += '%'; arg += c; } } break; case '\\': c = command.c_str()[++i]; switch (c) { case ' ' : case '"' : arg += c; break; // "\\", "\" at end-of-string => "\" case '\0': case '\\': arg += '\\'; break; default : arg += '\\'; arg += c; break; } break; case '"' : inquotes = 1; break; case ' ' : case '\t': case '\0': if (arg.length() > 0) { // if there wasn't already a space or tab, start a new parameter argp = (char **) realloc(argp, (n + 2) * sizeof(char *)); argp[n++] = strdup (arg.c_str()); arg = ""; } break; default : arg += c; break; } } argp[n] = NULL; } SystemExec::~SystemExec () { terminate (); if (envp) { for (int i=0;envp[i];++i) { free(envp[i]); } free (envp); } if (argp) { for (int i=0;argp[i];++i) { free(argp[i]); } free (argp); } #ifdef PLATFORM_WINDOWS if (w_args) free(w_args); #endif pthread_mutex_destroy(&write_lock); } static void * interposer_thread (void *arg) { SystemExec *sex = static_cast(arg); sex->output_interposer(); pthread_exit(0); return 0; } string SystemExec::to_s () const { #ifdef PLATFORM_WINDOWS return string (w_args ? w_args : ""); #else stringstream out; if (argp) { for (int i = 0; argp[i]; ++i) { out << argp[i] << " "; } } return out.str(); #endif } #ifdef PLATFORM_WINDOWS /* Windows Process */ /* HELPER FUNCTIONS */ static void create_pipe (HANDLE *pipe, bool in) { SECURITY_ATTRIBUTES secAtt = { sizeof( SECURITY_ATTRIBUTES ), NULL, TRUE }; HANDLE tmpHandle; if (in) { if (!CreatePipe(&pipe[0], &tmpHandle, &secAtt, 1024 * 1024)) return; if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), &pipe[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) return; } else { if (!CreatePipe(&tmpHandle, &pipe[1], &secAtt, 1024 * 1024)) return; if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), &pipe[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) return; } CloseHandle(tmpHandle); } static void destroy_pipe (HANDLE pipe[2]) { if (pipe[0] != INVALID_HANDLE_VALUE) { CloseHandle(pipe[0]); pipe[0] = INVALID_HANDLE_VALUE; } if (pipe[1] != INVALID_HANDLE_VALUE) { CloseHandle(pipe[1]); pipe[1] = INVALID_HANDLE_VALUE; } } static BOOL CALLBACK my_terminateApp(HWND hwnd, LPARAM procId) { DWORD currentProcId = 0; GetWindowThreadProcessId(hwnd, ¤tProcId); if (currentProcId == (DWORD)procId) PostMessage(hwnd, WM_CLOSE, 0, 0); return TRUE; } /* PROCESS API */ void SystemExec::make_envp() { ;/* environemt is copied over with CreateProcess(...,env=0 ,..) */ } void SystemExec::make_wargs(char **a) { std::string wa = cmd; if (cmd[0] != '"' && cmd[cmd.size()] != '"' && strchr(cmd.c_str(), ' ')) { wa = "\"" + cmd + "\""; } std::replace(cmd.begin(), cmd.end(), '/', '\\' ); char **tmp = ++a; while (tmp && *tmp) { wa.append(" \""); wa.append(*tmp); if (strlen(*tmp) > 0 && (*tmp)[strlen(*tmp) - 1] == '\\') { wa.append("\\"); } wa.append("\""); tmp++; } w_args = strdup(wa.c_str()); } void SystemExec::make_argp(std::string args) { std::string wa = cmd; if (cmd[0] != '"' && cmd[cmd.size()] != '"' && strchr(cmd.c_str(), ' ')) { wa = "\"" + cmd + "\""; } std::replace(cmd.begin(), cmd.end(), '/', '\\' ); wa.append(" "); wa.append(args); w_args = strdup(wa.c_str()); } void SystemExec::terminate () { ::pthread_mutex_lock(&write_lock); close_stdin(); if (pid) { /* terminate */ EnumWindows(my_terminateApp, (LPARAM)pid->dwProcessId); PostThreadMessage(pid->dwThreadId, WM_CLOSE, 0, 0); /* kill ! */ TerminateProcess(pid->hProcess, 0xf291); CloseHandle(pid->hThread); CloseHandle(pid->hProcess); destroy_pipe(stdinP); destroy_pipe(stdoutP); destroy_pipe(stderrP); delete pid; pid=0; } ::pthread_mutex_unlock(&write_lock); } int SystemExec::wait (int options) { while (is_running()) { WaitForSingleObject(pid->hProcess, 40); } return 0; } bool SystemExec::is_running () { if (!pid) return false; DWORD exit_code; if (GetExitCodeProcess(pid->hProcess, &exit_code)) { if (exit_code == STILL_ACTIVE) return true; } return false; } int SystemExec::start (int stderr_mode, const char * /*vfork_exec_wrapper*/) { char* working_dir = 0; if (pid) { return 0; } pid = new PROCESS_INFORMATION; memset(pid, 0, sizeof(PROCESS_INFORMATION)); create_pipe(stdinP, true); create_pipe(stdoutP, false); if (stderr_mode == 2) { /* merge stout & stderr */ DuplicateHandle(GetCurrentProcess(), stdoutP[1], GetCurrentProcess(), &stderrP[1], 0, TRUE, DUPLICATE_SAME_ACCESS); } else if (stderr_mode == 1) { //TODO read/flush this pipe or close it... create_pipe(stderrP, false); } else { //TODO: keep stderr of this process mode. } bool success = false; STARTUPINFOA startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0, (unsigned long)CW_USEDEFAULT, (unsigned long)CW_USEDEFAULT, (unsigned long)CW_USEDEFAULT, (unsigned long)CW_USEDEFAULT, 0, 0, 0, STARTF_USESTDHANDLES, 0, 0, 0, stdinP[0], stdoutP[1], stderrP[1] }; success = CreateProcess(0, w_args, 0, 0, /* bInheritHandles = */ TRUE, (CREATE_NO_WINDOW&0) | CREATE_UNICODE_ENVIRONMENT | (0&CREATE_NEW_CONSOLE), /*env = */ 0, working_dir, &startupInfo, pid); if (stdinP[0] != INVALID_HANDLE_VALUE) { CloseHandle(stdinP[0]); stdinP[0] = INVALID_HANDLE_VALUE; } if (stdoutP[1] != INVALID_HANDLE_VALUE) { CloseHandle(stdoutP[1]); stdoutP[1] = INVALID_HANDLE_VALUE; } if (stderrP[1] != INVALID_HANDLE_VALUE) { CloseHandle(stderrP[1]); stderrP[1] = INVALID_HANDLE_VALUE; } if (!success) { CloseHandle(pid->hThread); CloseHandle(pid->hProcess); destroy_pipe(stdinP); destroy_pipe(stdoutP); destroy_pipe(stderrP); delete pid; pid=0; return -1; } int rv = pthread_create(&thread_id_tt, NULL, interposer_thread, this); thread_active=true; if (rv) { thread_active=false; terminate(); return -2; } Sleep(20); return 0; } void SystemExec::output_interposer() { DWORD bytesRead = 0; char data[BUFSIZ]; #if 0 // untested code to set up nonblocking unsigned long l = 1; ioctlsocket(stdoutP[0], FIONBIO, &l); #endif while(1) { #if 0 // for non-blocking pipes.. DWORD bytesAvail = 0; PeekNamedPipe(stdoutP[0], 0, 0, 0, &bytesAvail, 0); if (bytesAvail < 1) {Sleep(500); printf("N/A\n"); continue;} #endif if (stdoutP[0] == INVALID_HANDLE_VALUE) break; if (!ReadFile(stdoutP[0], data, BUFSIZ, &bytesRead, 0)) { DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) continue; break; } if (bytesRead < 1) continue; /* actually not needed; but this is safe. */ data[bytesRead] = 0; ReadStdout(data, bytesRead);/* EMIT SIGNAL */ } Terminated();/* EMIT SIGNAL */ pthread_exit(0); } void SystemExec::close_stdin() { if (stdinP[0]!= INVALID_HANDLE_VALUE) FlushFileBuffers(stdinP[0]); if (stdinP[1]!= INVALID_HANDLE_VALUE) FlushFileBuffers(stdinP[1]); Sleep(200); destroy_pipe(stdinP); } int SystemExec::write_to_stdin(std::string d, size_t len) { const char *data; DWORD r,c; ::pthread_mutex_lock(&write_lock); data=d.c_str(); if (len == 0) { len=(d.length()); } c=0; while (c < len) { if (!WriteFile(stdinP[1], data+c, len-c, &r, NULL)) { if (GetLastError() == 0xE8 /*NT_STATUS_INVALID_USER_BUFFER*/) { Sleep(100); continue; } else { fprintf(stderr, "SYSTEM-EXEC: stdin write error.\n"); break; } } c += r; } ::pthread_mutex_unlock(&write_lock); return c; } /* end windows process */ #else /* UNIX/POSIX process */ extern char **environ; void SystemExec::make_envp() { int i=0; envp = (char **) calloc(1, sizeof(char*)); /* copy current environment */ for (i=0;environ[i];++i) { envp[i] = strdup(environ[i]); envp = (char **) realloc(envp, (i+2) * sizeof(char*)); } envp[i] = 0; } void SystemExec::make_argp(std::string args) { int argn = 1; char *cp1; char *cp2; char *carg = strdup(args.c_str()); argp = (char **) malloc((argn + 1) * sizeof(char *)); if (argp == (char **) 0) { free(carg); return; // FATAL } argp[0] = strdup(cmd.c_str()); /* TODO: quotations and escapes * http://stackoverflow.com/questions/1511797/convert-string-to-argv-in-c * * It's actually not needed. All relevant invocations specify 'argp' directly. * Only 'xjadeo -L -R' uses this function and that uses neither quotations * nor arguments with white-space. */ for (cp1 = cp2 = carg; *cp2 != '\0'; ++cp2) { if (*cp2 == ' ') { *cp2 = '\0'; argp[argn++] = strdup(cp1); cp1 = cp2 + 1; argp = (char **) realloc(argp, (argn + 1) * sizeof(char *)); } } if (cp2 != cp1) { argp[argn++] = strdup(cp1); argp = (char **) realloc(argp, (argn + 1) * sizeof(char *)); } argp[argn] = (char *) 0; free(carg); } void SystemExec::terminate () { ::pthread_mutex_lock(&write_lock); /* close stdin in an attempt to get the child to exit cleanly. */ close_stdin(); if (pid) { ::usleep(200000); sched_yield(); wait(WNOHANG); } /* if pid is non-zero, the child task is still executing (i.e. it did * not exit in response to stdin being closed). try to kill it. */ if (pid) { ::kill(pid, SIGTERM); ::usleep(250000); sched_yield(); wait(WNOHANG); } /* if pid is non-zero, the child task is STILL executing after being * sent SIGTERM. Act tough ... send SIGKILL */ if (pid) { ::fprintf(stderr, "Process is still running! trying SIGKILL\n"); ::kill(pid, SIGKILL); } wait(); if (thread_active) pthread_join(thread_id_tt, NULL); thread_active = false; assert(pid == 0); ::pthread_mutex_unlock(&write_lock); } int SystemExec::wait (int options) { int status=0; int ret; if (pid==0) return -1; ret = waitpid (pid, &status, options); if (ret == pid) { if (WEXITSTATUS(status) || WIFSIGNALED(status)) { pid=0; } } else { if (ret != 0) { if (errno == ECHILD) { /* no currently running children, reset pid */ pid=0; } } /* else the process is still running */ } return status; } bool SystemExec::is_running () { int status=0; if (pid==0) return false; if (::waitpid(pid, &status, WNOHANG)==0) return true; return false; } int SystemExec::start (int stderr_mode, const char *vfork_exec_wrapper) { if (is_running()) { return 0; // mmh what to return here? } int r; if (::pipe(pin) < 0 || ::pipe(pout) < 0 || ::pipe(pok) < 0) { /* Something unexpected went wrong creating a pipe. */ return -1; } #ifndef NO_VFORK r = ::vfork(); #else r = ::fork(); #endif if (r < 0) { /* failed to fork */ return -2; } if (r > 0) { /* main */ pid=r; /* check if execve was successful. */ close_fd(pok[1]); char buf; for ( ;; ) { ssize_t n = ::read(pok[0], &buf, 1 ); if ( n==1 ) { /* child process returned from execve */ pid=0; close_fd(pok[0]); close_fd(pok[1]); close_fd(pin[1]); close_fd(pin[0]); close_fd(pout[1]); close_fd(pout[0]); return -3; } else if ( n==-1 ) { if ( errno==EAGAIN || errno==EINTR ) continue; } break; } close_fd(pok[0]); /* child started successfully */ close_fd(pout[1]); close_fd(pin[0]); int rv = pthread_create(&thread_id_tt, NULL, interposer_thread, this); thread_active=true; if (rv) { thread_active=false; terminate(); return -2; } return 0; /* all systems go - return to main */ } #ifdef NO_VFORK /* child process - exec external process */ close_fd(pok[0]); ::fcntl(pok[1], F_SETFD, FD_CLOEXEC); close_fd(pin[1]); if (pin[0] != STDIN_FILENO) { ::dup2(pin[0], STDIN_FILENO); } close_fd(pin[0]); close_fd(pout[0]); if (pout[1] != STDOUT_FILENO) { ::dup2(pout[1], STDOUT_FILENO); } if (stderr_mode == 2) { /* merge STDERR into output */ if (pout[1] != STDERR_FILENO) { ::dup2(pout[1], STDERR_FILENO); } } else if (stderr_mode == 1) { /* ignore STDERR */ ::close(STDERR_FILENO); } else { /* keep STDERR */ } if (pout[1] != STDOUT_FILENO && pout[1] != STDERR_FILENO) { close_fd(pout[1]); } if (nicelevel !=0) { ::nice(nicelevel); } #ifdef HAVE_SIGSET sigset(SIGPIPE, SIG_DFL); #else signal(SIGPIPE, SIG_DFL); #endif if (!vfork_exec_wrapper) { error << _("Cannot start external process, no vfork wrapper") << endmsg; return -1; } int good_fds[2] = { pok[1], -1 }; close_allv(good_fds); ::execve(argp[0], argp, envp); /* if we reach here something went wrong.. */ char buf = 0; (void) ::write(pok[1], &buf, 1 ); close_fd(pok[1]); exit(-1); return -1; #else /* XXX this should be done before vfork() * calling malloc here only increases the time vfork() blocks */ int argn = 0; for (int i=0;argp[i];++i) { argn++; } char **argx = (char **) malloc((argn + 10) * sizeof(char *)); argx[0] = strdup(vfork_exec_wrapper); // XXX #define FDARG(NUM, FDN) \ argx[NUM] = (char*) calloc(6, sizeof(char)); snprintf(argx[NUM], 6, "%d", FDN); FDARG(1, pok[0]) FDARG(2, pok[1]) FDARG(3, pin[0]) FDARG(4, pin[1]) FDARG(5, pout[0]) FDARG(6, pout[1]) FDARG(7, stderr_mode) FDARG(8, nicelevel) for (int i=0;argp[i];++i) { argx[9+i] = argp[i]; } argx[argn+9] = NULL; ::execve(argx[0], argx, envp); /* if we reach here something went wrong.. */ char buf = 0; (void) ::write(pok[1], &buf, 1 ); close_fd(pok[1]); exit(-1); return -1; #endif } void SystemExec::output_interposer() { int rfd=pout[0]; char buf[BUFSIZ]; ssize_t r; unsigned long l = 1; ioctl(rfd, FIONBIO, &l); // set non-blocking I/O for (;fcntl(rfd, F_GETFL)!=-1;) { r = read(rfd, buf, sizeof(buf)); if (r < 0 && (errno == EINTR || errno == EAGAIN)) { fd_set rfds; struct timeval tv; FD_ZERO(&rfds); FD_SET(rfd, &rfds); tv.tv_sec = 0; tv.tv_usec = 10000; int rv = select(1, &rfds, NULL, NULL, &tv); if (rv == -1) { break; } continue; } if (r <= 0) { break; } buf[r]=0; std::string rv = std::string(buf,r); // TODO: check allocation strategy ReadStdout(rv, r);/* EMIT SIGNAL */ } Terminated();/* EMIT SIGNAL */ pthread_exit(0); } void SystemExec::close_stdin() { if (pin[1]<0) return; close_fd(pin[0]); close_fd(pin[1]); close_fd(pout[0]); close_fd(pout[1]); } int SystemExec::write_to_stdin(std::string d, size_t len) { const char *data; ssize_t r; size_t c; ::pthread_mutex_lock(&write_lock); data=d.c_str(); if (len == 0) { len=(d.length()); } c=0; while (c < len) { for (;;) { r=::write(pin[1], data+c, len-c); if (r < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if ((size_t) r != (len-c)) { ::pthread_mutex_unlock(&write_lock); return c; } break; } c += r; } fsync(pin[1]); ::pthread_mutex_unlock(&write_lock); return c; } #endif // end UNIX process