Loading...
Searching...
No Matches
spawn.c
Go to the documentation of this file.
/*!
* \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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#ifndef _WIN32
#include <sys/wait.h>
#else
#include <windows.h>
#endif
#include <grass/config.h>
#include <grass/gis.h>
#include <grass/glocale.h>
#include <grass/spawn.h>
/** \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 <b>command</b> 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 _WIN32
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 _WIN32
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];
snprintf(pathname, sizeof(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)
{
size_t bufsize = strlen(b->var) + strlen(b->val) + 2;
char *str = G_malloc(bufsize);
int n = *pnum;
int i;
snprintf(str, bufsize, "%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];
size_t bufsize = strlen(b->var) + strlen(b->val) + 2;
char *str = G_malloc(bufsize);
snprintf(str, bufsize, "%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 <b>command</b>.
*
* 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 <b>command</b>.
*
* 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 <b>command</b>.
*
* \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;
va_list va;
int status = -1;
va_start(va, command);
for (;;) {
const char *arg = va_arg(va, const char *);
args[num_args++] = arg;
if (!arg)
break;
}
va_end(va);
status =
G_spawn_ex(command,
#ifndef _WIN32
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 _WIN32
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
}
G_free
void G_free(void *buf)
Free allocated memory.
Definition alloc.c:147
init
void init(double work[])
Definition as177.c:61
NULL
#define NULL
Definition ccmath.h:32
TRUE
#define TRUE
Definition dbfopen.c:56
FALSE
#define FALSE
Definition dbfopen.c:55
G_debug
int G_debug(int level, const char *msg,...)
Print debugging message.
Definition debug.c:66
b
double b
Definition driver/set_window.c:5
r
double r
Definition driver/set_window.c:5
G_warning
void G_warning(const char *msg,...)
Print a warning message to stderr.
Definition gis/error.c:203
file
#define file
NEXT_ARG
#define NEXT_ARG(var, type)
Definition spawn.c:732
MAX_ARGS
#define MAX_ARGS
Definition spawn.c:43
G_vspawn_ex
int G_vspawn_ex(const char *command, const char **args)
Spawn new process based on command.
Definition spawn.c:879
G_wait
int G_wait(int i_pid)
Definition spawn.c:950
G_spawn
int G_spawn(const char *command,...)
Spawn new process based on command.
Definition spawn.c:920
MAX_BINDINGS
#define MAX_BINDINGS
Definition spawn.c:44
G_spawn_ex
int G_spawn_ex(const char *command,...)
Spawn new process based on command.
Definition spawn.c:899
NEXT_ARG_INT
#define NEXT_ARG_INT(var)
Definition spawn.c:733
MAX_REDIRECTS
#define MAX_REDIRECTS
Definition spawn.c:46
MAX_SIGNALS
#define MAX_SIGNALS
Definition spawn.c:45
G_strcasecmp
int G_strcasecmp(const char *x, const char *y)
String compare ignoring case (upper or lower).
Definition strings.c:47
G_store
char * G_store(const char *s)
Copy string to allocated memory.
Definition strings.c:87
path
Definition path.h:15
G_free_tokens
void G_free_tokens(char **tokens)
Free memory allocated to tokens.
Definition token.c:197
G_tokenize
char ** G_tokenize(const char *buf, const char *delim)
Tokenize string.
Definition token.c:47