/*! * \file lib/gis/spawn.c * * \brief GIS Library - Handles process spawning. * * (C) 2001-2014 by the GRASS Development Team * * This program is free software under the GNU General Public License * (>=v2). Read the file COPYING that comes with GRASS for details. * * \author Glynn Clements * * \date 2004-2006 */ #include #include #include #include #include #include #include #include #include #ifndef __MINGW32__ #include #else #include #endif #include #include #include #include /** \def MAX_ARGS Maximum number of arguments */ /** \def MAX_BINDINGS Maximum number of bindings */ /** \def MAX_SIGNALS Maximum number of signals */ /** \def MAX_REDIRECTS Maximum number of redirects */ #define MAX_ARGS 256 #define MAX_BINDINGS 256 #define MAX_SIGNALS 32 #define MAX_REDIRECTS 32 /** * \brief Spawns a new process. * * A more useful alternative to G_system(), which takes the * arguments of command as parameters. * * \param[in] command command to execute * \return -1 on error * \return process status on success */ struct redirect { int dst_fd; int src_fd; const char *file; int mode; }; struct signal { int which; int action; int signum; int valid; #ifndef __MINGW32__ struct sigaction old_act; sigset_t old_mask; #endif }; struct binding { const char *var; const char *val; }; struct spawn { const char *args[MAX_ARGS]; int num_args; struct redirect redirects[MAX_REDIRECTS]; int num_redirects; struct signal signals[MAX_SIGNALS]; int num_signals; struct binding bindings[MAX_BINDINGS]; int num_bindings; int background; const char *directory; }; static void parse_arglist(struct spawn *sp, va_list va); static void parse_argvec(struct spawn *sp, const char **va); #ifdef __MINGW32__ struct buffer { char *str; size_t len; size_t size; }; static const int INCREMENT = 50; static void clear(struct buffer *b) { b->len = 0; b->str[b->len] = '\0'; } static void init(struct buffer *b) { b->str = G_malloc(1); b->size = 1; clear(b); } static char *release(struct buffer *b) { char *p = b->str; b->str = NULL; b->size = 0; b->len = 0; return p; } static void finish(struct buffer *b) { if (b->str) G_free(b->str); release(b); } static void ensure(struct buffer *b, size_t n) { if (b->size <= b->len + n + 1) { b->size = b->len + n + INCREMENT; b->str = G_realloc(b->str, b->size); } } static void append(struct buffer *b, const char *str) { size_t n = strlen(str); ensure(b, n); memcpy(&b->str[b->len], str, n); b->len += n; b->str[b->len] = '\0'; } static void append_char(struct buffer *b, char c) { ensure(b, 1); b->str[b->len] = c; b->len++; b->str[b->len] = '\0'; } static void escape_arg(struct buffer *result, const char *arg) { struct buffer buf; int quote, j; init(&buf); quote = arg[0] == '\0' || strchr(arg, ' ') || strchr(arg, '\t'); if (quote) append_char(result, '\"'); for (j = 0; arg[j]; j++) { int c = arg[j]; int k; switch (c) { case '\\': append_char(&buf, '\\'); break; case '\"': for (k = 0; k < buf.len; k++) append(result, "\\\\"); clear(&buf); append(result, "\\\""); break; default: if (buf.len > 0) { append(result, buf.str); clear(&buf); } append_char(result, c); } } if (buf.len > 0) append(result, buf.str); if (quote) { append(result, buf.str); append_char(result, '\"'); } finish(&buf); } static char *check_program(const char *pgm, const char *dir, const char *ext) { char pathname[GPATH_MAX]; sprintf(pathname, "%s%s%s%s", dir, *dir ? "\\" : "", pgm, ext); return access(pathname, 0) == 0 ? G_store(pathname) : NULL; } static char *find_program_ext(const char *pgm, const char *dir, char **pathext) { char *result; int i; if (result = check_program(pgm, dir, ""), result) return result; for (i = 0; pathext[i]; i++) { const char *ext = pathext[i]; if (result = check_program(pgm, dir, ext), result) return result; } return NULL; } static char *find_program_dir_ext(const char *pgm, char **path, char **pathext) { char *result = NULL; int i; if (strchr(pgm, '\\') || strchr(pgm, '/')) { if (result = find_program_ext(pgm, "", pathext), result) return result; } else { if (result = find_program_ext(pgm, ".", pathext), result) return result; for (i = 0; path[i]; i++) { const char *dir = path[i]; if (result = find_program_ext(pgm, dir, pathext), result) return result; } } return NULL; } static char *find_program(const char *pgm) { char **path = G_tokenize(getenv("PATH"), ";"); char **pathext = G_tokenize(getenv("PATHEXT"), ";"); char *result = find_program_dir_ext(pgm, path, pathext); G_free_tokens(path); G_free_tokens(pathext); return result; } static char *make_command_line(int shell, const char *cmd, const char **argv) { struct buffer result; int i; init(&result); if (shell) { const char *comspec = getenv("COMSPEC"); append(&result, comspec ? comspec : "cmd.exe"); append(&result, " /c \""); escape_arg(&result, cmd); } for (i = shell ? 1 : 0; argv[i]; i++) { if (result.len > 0) append_char(&result, ' '); escape_arg(&result, argv[i]); } append(&result, "\""); return release(&result); } static char *make_environment(const char **envp) { struct buffer result; int i; init(&result); for (i = 0; envp[i]; i++) { const char *env = envp[i]; append(&result, env); append_char(&result, '\0'); } return release(&result); } static HANDLE get_handle(int fd) { HANDLE h1, h2; if (fd < 0) return INVALID_HANDLE_VALUE; h1 = (HANDLE) _get_osfhandle(fd); if (!DuplicateHandle(GetCurrentProcess(), h1, GetCurrentProcess(), &h2, 0, TRUE, DUPLICATE_SAME_ACCESS)) return INVALID_HANDLE_VALUE; return h2; } static int win_spawn(const char *cmd, const char **argv, const char **envp, const char *cwd, HANDLE handles[3], int background, int shell) { char *args = make_command_line(shell, cmd, argv); char *env = make_environment(envp); char *program = shell ? NULL : find_program(cmd); STARTUPINFO si; PROCESS_INFORMATION pi; BOOL result; DWORD exitcode; int i; if (!shell) { G_debug(3, "win_spawn: program = %s", program); if (!program) { G_free(args); G_free(env); return -1; } } G_debug(3, "win_spawn: args = %s", args); memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags |= STARTF_USESTDHANDLES; si.hStdInput = handles[0]; si.hStdOutput = handles[1]; si.hStdError = handles[2]; result = CreateProcess( program, /* lpApplicationName */ args, /* lpCommandLine */ NULL, /* lpProcessAttributes */ NULL, /* lpThreadAttributes */ 1, /* bInheritHandles */ 0, /* dwCreationFlags */ env, /* lpEnvironment */ cwd, /* lpCurrentDirectory */ &si, /* lpStartupInfo */ &pi /* lpProcessInformation */ ); G_free(args); G_free(env); G_free(program); if (!result) { G_warning(_("CreateProcess() failed: error = %d"), GetLastError()); return -1; } CloseHandle(pi.hThread); for (i = 0; i < 3; i++) if (handles[i] != INVALID_HANDLE_VALUE) CloseHandle(handles[i]); if (!background) { WaitForSingleObject(pi.hProcess, INFINITE); if (!GetExitCodeProcess(pi.hProcess, &exitcode)) return -1; CloseHandle(pi.hProcess); return (int) exitcode; } CloseHandle(pi.hProcess); return pi.dwProcessId; } static void do_redirects(struct redirect *redirects, int num_redirects, HANDLE handles[3]) { int i; for (i = 0; i < 3; i++) handles[i] = get_handle(i); for (i = 0; i < num_redirects; i++) { struct redirect *r = &redirects[i]; if (r->dst_fd < 0 || r->dst_fd > 2) { if (r->file || r->src_fd >= 0) G_warning(_("G_spawn: unable to redirect descriptor %d"), r->dst_fd); continue; } if (r->file) { r->src_fd = open(r->file, r->mode, 0666); if (r->src_fd < 0) { G_warning(_("G_spawn: unable to open file %s"), r->file); _exit(127); } handles[r->dst_fd] = get_handle(r->src_fd); close(r->src_fd); } else if (r->src_fd >= 0) { handles[r->dst_fd] = get_handle(r->src_fd); } else { if (r->dst_fd < 3) { CloseHandle(handles[r->dst_fd]); handles[r->dst_fd] = INVALID_HANDLE_VALUE; } close(r->dst_fd); } } } static void add_binding(const char **env, int *pnum, const struct binding *b) { char *str = G_malloc(strlen(b->var) + strlen(b->val) + 2); int n = *pnum; int i; sprintf(str, "%s=%s", b->var, b->val); for (i = 0; i < n; i++) if (G_strcasecmp(env[i], b->var) == 0) { env[i] = str; return; } env[n++] = str; *pnum = n; } static const char **do_bindings(const struct binding *bindings, int num_bindings) { const char **newenv; int i, n; for (i = 0; _environ[i]; i++) ; n = i; newenv = G_malloc((num_bindings + n + 1) * sizeof(char *)); for (i = 0; i < n; i++) newenv[i] = _environ[i]; for (i = 0; i < num_bindings; i++) add_binding(newenv, &n, &bindings[i]); newenv[num_bindings + n] = NULL; return newenv; } static int do_spawn(struct spawn *sp, const char *command) { HANDLE handles[3]; const char **env; int status; do_redirects(sp->redirects, sp->num_redirects, handles); env = do_bindings(sp->bindings, sp->num_bindings); status = win_spawn(command, sp->args, env, sp->directory, handles, sp->background, 1); if (!sp->background && status < 0) G_warning(_("G_spawn: unable to execute command")); return status; } #else /* __MINGW32__ */ static int undo_signals(const struct signal *signals, int num_signals, int which) { int error = 0; int i; for (i = num_signals - 1; i >= 0; i--) { const struct signal *s = &signals[i]; if (s->which != which) continue; if (!s->valid) continue; switch (s->action) { case SSA_IGNORE: case SSA_DEFAULT: if (sigaction(s->signum, &s->old_act, NULL) < 0) { G_warning(_("G_spawn: unable to restore signal %d"), s->signum); error = 1; } break; case SSA_BLOCK: case SSA_UNBLOCK: if (sigprocmask(SIG_UNBLOCK, &s->old_mask, NULL) < 0) { G_warning(_("G_spawn: unable to restore signal %d"), s->signum); error = 1; } break; } } return !error; } static int do_signals(struct signal *signals, int num_signals, int which) { struct sigaction act; sigset_t mask; int error = 0; int i; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; for (i = 0; i < num_signals; i++) { struct signal *s = &signals[i]; if (s->which != which) continue; switch (s->action) { case SSA_IGNORE: act.sa_handler = SIG_IGN; if (sigaction(s->signum, &act, &s->old_act) < 0) { G_warning(_("G_spawn: unable to reset signal %d"), s->signum); error = 1; } else s->valid = 1; break; case SSA_DEFAULT: act.sa_handler = SIG_DFL; if (sigaction(s->signum, &act, &s->old_act) < 0) { G_warning(_("G_spawn: unable to ignore signal %d"), s->signum); error = 1; } else s->valid = 1; break; case SSA_BLOCK: sigemptyset(&mask); sigaddset(&mask, s->signum); if (sigprocmask(SIG_BLOCK, &mask, &s->old_mask) < 0) { G_warning(_("G_spawn: unable to block signal %d"), s->signum); error = 1; } break; case SSA_UNBLOCK: sigemptyset(&mask); sigaddset(&mask, s->signum); if (sigprocmask(SIG_UNBLOCK, &mask, &s->old_mask) < 0) { G_warning(_("G_spawn: unable to unblock signal %d"), s->signum); error = 1; } else s->valid = 1; break; } } return !error; } static void do_redirects(struct redirect *redirects, int num_redirects) { int i; for (i = 0; i < num_redirects; i++) { struct redirect *r = &redirects[i]; if (r->file) { r->src_fd = open(r->file, r->mode, 0666); if (r->src_fd < 0) { G_warning(_("G_spawn: unable to open file %s"), r->file); _exit(127); } if (dup2(r->src_fd, r->dst_fd) < 0) { G_warning(_("G_spawn: unable to duplicate descriptor %d to %d"), r->src_fd, r->dst_fd); _exit(127); } close(r->src_fd); } else if (r->src_fd >= 0) { if (dup2(r->src_fd, r->dst_fd) < 0) { G_warning(_("G_spawn: unable to duplicate descriptor %d to %d"), r->src_fd, r->dst_fd); _exit(127); } } else close(r->dst_fd); } } static void do_bindings(const struct binding *bindings, int num_bindings) { int i; for (i = 0; i < num_bindings; i++) { const struct binding *b = &bindings[i]; char *str = G_malloc(strlen(b->var) + strlen(b->val) + 2); sprintf(str, "%s=%s", b->var, b->val); putenv(str); } } static int do_spawn(struct spawn *sp, const char *command) { int status = -1; pid_t pid; if (!do_signals(sp->signals, sp->num_signals, SST_PRE)) return status; pid = fork(); if (pid < 0) { G_warning(_("Unable to create a new process: %s"), strerror(errno)); undo_signals(sp->signals, sp->num_signals, SST_PRE); return status; } if (pid == 0) { if (!undo_signals(sp->signals, sp->num_signals, SST_PRE)) _exit(127); if (!do_signals(sp->signals, sp->num_signals, SST_CHILD)) _exit(127); if (sp->directory) if (chdir(sp->directory) < 0) { G_warning(_("Unable to change directory to %s"), sp->directory); _exit(127); } do_redirects(sp->redirects, sp->num_redirects); do_bindings(sp->bindings, sp->num_bindings); execvp(command, (char **)sp->args); G_warning(_("Unable to execute command '%s': %s"), command, strerror(errno)); _exit(127); } do_signals(sp->signals, sp->num_signals, SST_POST); if (sp->background) status = (int)pid; else { pid_t n; do n = waitpid(pid, &status, 0); while (n == (pid_t) - 1 && errno == EINTR); if (n != pid) status = -1; else { if (WIFEXITED(status)) status = WEXITSTATUS(status); else if (WIFSIGNALED(status)) status = WTERMSIG(status); else status = -0x100; } } undo_signals(sp->signals, sp->num_signals, SST_POST); undo_signals(sp->signals, sp->num_signals, SST_PRE); return status; } #endif /* __MINGW32__ */ static void begin_spawn(struct spawn *sp) { sp->num_args = 0; sp->num_redirects = 0; sp->num_signals = 0; sp->num_bindings = 0; sp->background = 0; sp->directory = NULL; } #define NEXT_ARG(var, type) ((type) *(var)++) #define NEXT_ARG_INT(var) (int)((intptr_t) *(var)++) static void parse_argvec(struct spawn *sp, const char **va) { for (;;) { const char *arg = NEXT_ARG(va, const char *); const char *var, *val; if (!arg) { sp->args[sp->num_args++] = NULL; break; } else if (arg == SF_REDIRECT_FILE) { sp->redirects[sp->num_redirects].dst_fd = NEXT_ARG_INT(va); sp->redirects[sp->num_redirects].src_fd = -1; sp->redirects[sp->num_redirects].mode = NEXT_ARG_INT(va); sp->redirects[sp->num_redirects].file = NEXT_ARG(va, const char *); sp->num_redirects++; } else if (arg == SF_REDIRECT_DESCRIPTOR) { sp->redirects[sp->num_redirects].dst_fd = NEXT_ARG_INT(va); sp->redirects[sp->num_redirects].src_fd = NEXT_ARG_INT(va); sp->redirects[sp->num_redirects].file = NULL; sp->num_redirects++; } else if (arg == SF_CLOSE_DESCRIPTOR) { sp->redirects[sp->num_redirects].dst_fd = NEXT_ARG_INT(va); sp->redirects[sp->num_redirects].src_fd = -1; sp->redirects[sp->num_redirects].file = NULL; sp->num_redirects++; } else if (arg == SF_SIGNAL) { sp->signals[sp->num_signals].which = NEXT_ARG_INT(va); sp->signals[sp->num_signals].action = NEXT_ARG_INT(va); sp->signals[sp->num_signals].signum = NEXT_ARG_INT(va); sp->signals[sp->num_signals].valid = 0; sp->num_signals++; } else if (arg == SF_VARIABLE) { var = NEXT_ARG(va, const char *); val = getenv(var); sp->args[sp->num_args++] = val ? val : ""; } else if (arg == SF_BINDING) { sp->bindings[sp->num_bindings].var = NEXT_ARG(va, const char *); sp->bindings[sp->num_bindings].val = NEXT_ARG(va, const char *); sp->num_bindings++; } else if (arg == SF_BACKGROUND) { sp->background = 1; } else if (arg == SF_DIRECTORY) { sp->directory = NEXT_ARG(va, const char *); } else if (arg == SF_ARGVEC) { parse_argvec(sp, NEXT_ARG(va, const char **)); } else sp->args[sp->num_args++] = arg; } } static void parse_arglist(struct spawn *sp, va_list va) { for (;;) { const char *arg = va_arg(va, const char *); const char *var, *val; if (!arg) { sp->args[sp->num_args++] = NULL; break; } else if (arg == SF_REDIRECT_FILE) { sp->redirects[sp->num_redirects].dst_fd = va_arg(va, int); sp->redirects[sp->num_redirects].src_fd = -1; sp->redirects[sp->num_redirects].mode = va_arg(va, int); sp->redirects[sp->num_redirects].file = va_arg(va, const char *); sp->num_redirects++; } else if (arg == SF_REDIRECT_DESCRIPTOR) { sp->redirects[sp->num_redirects].dst_fd = va_arg(va, int); sp->redirects[sp->num_redirects].src_fd = va_arg(va, int); sp->redirects[sp->num_redirects].file = NULL; sp->num_redirects++; } else if (arg == SF_CLOSE_DESCRIPTOR) { sp->redirects[sp->num_redirects].dst_fd = va_arg(va, int); sp->redirects[sp->num_redirects].src_fd = -1; sp->redirects[sp->num_redirects].file = NULL; sp->num_redirects++; } else if (arg == SF_SIGNAL) { sp->signals[sp->num_signals].which = va_arg(va, int); sp->signals[sp->num_signals].action = va_arg(va, int); sp->signals[sp->num_signals].signum = va_arg(va, int); sp->signals[sp->num_signals].valid = 0; sp->num_signals++; } else if (arg == SF_VARIABLE) { var = va_arg(va, char *); val = getenv(var); sp->args[sp->num_args++] = val ? val : ""; } else if (arg == SF_BINDING) { sp->bindings[sp->num_bindings].var = va_arg(va, const char *); sp->bindings[sp->num_bindings].val = va_arg(va, const char *); sp->num_bindings++; } else if (arg == SF_BACKGROUND) { sp->background = 1; } else if (arg == SF_DIRECTORY) { sp->directory = va_arg(va, const char *); } else if (arg == SF_ARGVEC) { parse_argvec(sp, va_arg(va, const char **)); } else sp->args[sp->num_args++] = arg; } } /** * \brief Spawn new process based on command. * * This is a more advanced version of G_spawn(). * * \param[in] command * \param[in] args arguments * \return -1 on error * \return process status on success */ int G_vspawn_ex(const char *command, const char **args) { struct spawn sp; begin_spawn(&sp); parse_argvec(&sp, args); return do_spawn(&sp, command); } /** * \brief Spawn new process based on command. * * This is a more advanced version of G_spawn(). * * \param[in] command * \return -1 on error * \return process status on success */ int G_spawn_ex(const char *command, ...) { struct spawn sp; va_list va; begin_spawn(&sp); va_start(va, command); parse_arglist(&sp, va); va_end(va); return do_spawn(&sp, command); } /** * \brief Spawn new process based on command. * * \param[in] command * \return -1 on error * \return process status on success */ int G_spawn(const char *command, ...) { const char *args[MAX_ARGS]; int num_args = 0, i; va_list va; int status = -1; va_start(va, command); for (i = 0; ; i++) { const char *arg = va_arg(va, const char *); args[num_args++] = arg; if (!arg) break; } va_end(va); status = G_spawn_ex( command, #ifndef __MINGW32__ SF_SIGNAL, SST_PRE, SSA_IGNORE, SIGINT, SF_SIGNAL, SST_PRE, SSA_IGNORE, SIGQUIT, SF_SIGNAL, SST_PRE, SSA_BLOCK, SIGCHLD, #endif SF_ARGVEC, args, NULL); return status; } int G_wait(int i_pid) { #ifdef __MINGW32__ DWORD rights = PROCESS_QUERY_INFORMATION | SYNCHRONIZE; HANDLE hProcess = OpenProcess(rights, FALSE, (DWORD) i_pid); DWORD exitcode; if (!hProcess) return -1; WaitForSingleObject(hProcess, INFINITE); if (!GetExitCodeProcess(hProcess, &exitcode)) exitcode = (DWORD) -1; CloseHandle(hProcess); return (int) exitcode; #else pid_t pid = (pid_t) i_pid; int status = -1; pid_t n; do n = waitpid(pid, &status, 0); while (n == (pid_t) - 1 && errno == EINTR); if (n != pid) return -1; else { if (WIFEXITED(status)) return WEXITSTATUS(status); else if (WIFSIGNALED(status)) return WTERMSIG(status); else return -0x100; } #endif }