/*
* Maketool - GTK-based front end for gmake
* Copyright (c) 1999-2003 Greg Banks
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "maketool_task.h"
#include <signal.h>
#include <gdk/gdk.h>
#include "glib_extra.h"
#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#if HAVE_SYS_FILIO_H
/* for FIONREAD on Solaris */
#include <sys/filio.h>
#endif
CVSID("$Id: task.c,v 1.17 2003/10/29 12:39:18 gnb Exp $");
/*
* TODO: GDK is used only for the gdk_input_*() functions, which
* are not immensely complicated and could be reproduced in here
* if this library were to be submitted to the glib maintainers.
*/
static GList *task_all = 0;
static void (*task_work_start_cb)(void) = 0;
static void (*task_work_end_cb)(void) = 0;
static void task_input_func(gpointer user_data, gint source, GdkInputCondition condition);
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
void
task_init(
void (*work_start_cb)(void),
void (*work_end_cb)(void))
{
g_unix_reap_init();
task_work_start_cb = work_start_cb;
task_work_end_cb = work_end_cb;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
Task *
task_create(Task *task, char *command, char **env, TaskOps *ops, int flags)
{
task->refcount = 1;
task->pid = -1;
task->command = command;
task->environ = env;
task->fd = -1;
task->input_tag = 0;
task->enqueued = FALSE;
task->ops = ops;
task->flags = flags;
estring_init(&task->linebuf);
return task;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
void
task_enqueue(Task *task)
{
task->enqueued = TRUE;
task_all = g_list_append(task_all, task);
}
static void
task_destroy(Task *task)
{
g_free(task->command);
if (task->fd != -1)
{
close(task->fd);
gdk_input_remove(task->input_tag);
}
if (task->ops->destroy != 0)
(*task->ops->destroy)(task);
estring_free(&task->linebuf);
g_free(task);
}
void
task_ref(Task *task)
{
task->refcount++;
}
void
task_unref(Task *task)
{
if (--task->refcount == 0)
task_destroy(task);
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*
* This used to be main.c:handle_input(). It splits input
* from the child process's stderr/stderr into lines and
* feeds individual lines to the task input function.
*/
static void
task_linemode_input(Task *task, int len, const char *buf)
{
if (len == 0)
{
/* end of input */
/*
* Handle case where last line of child process'
* output has no terminating '\n'. Beware -
* this last line may contain an error or warning,
* which affects log_num_{errors,warnings}().
*/
if (task->linebuf.length > 0)
(*task->ops->input)(task, task->linebuf.length, task->linebuf.data);
return;
}
/* TODO: use append_chars here to deal with NULs in input stream */
while (len > 0 && *buf)
{
char *p = strchr(buf, '\n');
if (p == 0)
{
/* only a part of a line left - append to task->linebuf */
estring_append_string(&task->linebuf, buf);
return;
}
/* got an end-of-line - isolate the line & feed it to input handler */
*p = '\0';
estring_append_string(&task->linebuf, buf);
if (task->linebuf.length > 0)
(*task->ops->input)(task, task->linebuf.length, task->linebuf.data);
else
(*task->ops->input)(task, 0, "");
estring_truncate(&task->linebuf);
len -= (p - buf);
buf = ++p;
}
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static void
task_reap_func(pid_t pid, int status, struct rusage *usg, gpointer user_data)
{
Task *task = (Task *)user_data;
gboolean was_enqueued = task->enqueued;
#if DEBUG
fprintf(stderr, "Task \"%s\" pid %d ", task->command, (int)pid);
if (WIFEXITED(status))
fprintf(stderr, "exited with code %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
fprintf(stderr, "terminated by signal %d\n", WTERMSIG(status));
else if (WIFSTOPPED(status))
fprintf(stderr, "stopped on signal %d\n", WSTOPSIG(status));
#endif
if (!(WIFEXITED(status) || WIFSIGNALED(status)))
{
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
task->flags |= TASK_PAUSED;
return;
}
if (task->pid != pid)
{
/* it's not paranoia when they're really out to get you */
fprintf(stderr, _("maketool: reaped unexpected pid %d\n"), (int)pid);
}
/* Hack to cause any pending input from the pipe to
* be processed before the reap function tries to use it.
*/
if (task->fd > 0)
task_input_func(user_data, task->fd, GDK_INPUT_READ);
task->pid = -1; /* so task_is_running() == FALSE in reap fn */
task_all = g_list_remove(task_all, task);
task->status = status;
/* flush any pending lines or part-lines */
if (task->flags & TASK_LINEMODE)
task_linemode_input(task, 0, 0);
if (task->ops->reap != 0)
(*task->ops->reap)(task);
task_unref(task);
if (was_enqueued)
{
if (task_all == 0)
{
if (task_work_end_cb != 0)
(*task_work_end_cb)();
}
else
task_start(); /* spawn the next task in the queue */
}
}
gboolean
task_is_successful(const Task *task)
{
return (WIFEXITED(task->status) && WEXITSTATUS(task->status) == 0);
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static void
task_input_func(gpointer user_data, gint source, GdkInputCondition condition)
{
Task *task = (Task *)user_data;
int nremain = 0;
if (source != task->fd || condition != GDK_INPUT_READ)
return;
if (ioctl(task->fd, FIONREAD, &nremain) < 0)
{
perror("ioctl(FIONREAD)");
return;
}
while (nremain > 0)
{
char buf[1025];
int n = read(task->fd, buf, MIN((int)sizeof(buf)-1, nremain));
nremain -= n;
buf[n] = '\0'; /* so we can use str*() calls */
#if DEBUG > 40
fprintf(stderr, "task_input_func(): \"");
fwrite(buf, n, 1, stderr);
fprintf(stderr, "\"\n");
#endif
if (task->flags & TASK_LINEMODE)
task_linemode_input(task, n, buf);
else
(*task->ops->input)(task, n, buf);
}
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static void
task_override_env(Task *task)
{
#if HAVE_PUTENV
char **e;
if ((e = task->environ) != 0)
{
/* TODO: do a merge of the current env & Variables
* in preferences.c when changes are applied.
* That's more efficient but more complex, 'cos
* here putenv() handles the uniquifying for us.
*/
for ( ; *e != 0 ; e++)
putenv(*e);
}
#else
#warning putenv not defined - some functionality will be missing
#endif
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static pid_t
task_spawn_simple(Task *task)
{
pid_t pid;
if ((pid = fork()) < 0)
{
/* error */
perror("fork");
return -1;
}
else if (pid == 0)
{
/* child */
task_override_env(task);
/* Possibly become process group leader */
if (task->flags & TASK_GROUPLEADER)
setpgid(0, 0);
execlp("/bin/sh", "/bin/sh", "-c", task->command, 0);
perror("execlp");
exit(1);
}
else
{
/* parent */
}
return pid;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
#define READ 0
#define WRITE 1
#define STDOUT 1
#define STDERR 2
static pid_t
task_spawn_with_input(Task *task)
{
pid_t pid;
int pipefds[2];
if (pipe(pipefds) < 0)
{
perror("pipe");
return -1;
}
if ((pid = fork()) < 0)
{
/* error */
perror("fork");
return -1;
}
else if (pid == 0)
{
/* child */
close(pipefds[READ]);
dup2(pipefds[WRITE], STDOUT);
dup2(pipefds[WRITE], STDERR);
close(pipefds[WRITE]);
task_override_env(task);
/* Possibly become process group leader */
if (task->flags & TASK_GROUPLEADER)
setpgid(0, 0);
execl("/bin/sh", "/bin/sh", "-c", task->command, 0);
perror("execl");
exit(1);
}
else
{
/* parent */
close(pipefds[WRITE]);
task->fd = pipefds[READ];
task->input_tag = gdk_input_add(task->fd,
GDK_INPUT_READ, task_input_func, (gpointer)task);
}
return pid;
}
#undef READ
#undef WRITE
#undef STDOUT
#undef STDERR
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
gboolean
task_spawn(Task *task)
{
assert(task->command != 0);
if (task->ops->input == 0)
task->pid = task_spawn_simple(task);
else
task->pid = task_spawn_with_input(task);
#if DEBUG
fprintf(stderr, "spawned \"%s\", pid %d\n", task->command, (int)task->pid);
#endif
if (task->pid > 0)
g_unix_add_reap_func(task->pid, task_reap_func, (gpointer)task);
if (task->ops->start != 0)
(*task->ops->start)(task);
return (task->pid != -1);
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
gboolean
task_run(Task *task)
{
if (!task_spawn(task))
return FALSE;
task_ref(task);
while (task->pid != -1)
g_main_iteration(/*block*/TRUE);
task_unref(task);
return TRUE;
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static Task *
task_current(void)
{
Task *curr;
if (task_all == 0)
return 0;
curr = (Task *)task_all->data;
return (curr->pid == -1 ? 0 : curr);
}
void
task_start(void)
{
Task *curr = task_current();
if (curr == 0 &&
task_all != 0)
{
if (task_spawn((Task *)task_all->data))
{
if (task_work_start_cb != 0)
(*task_work_start_cb)();
}
}
}
gboolean
task_is_running(void)
{
return (task_current() != 0);
}
void
task_kill_current(void)
{
Task *curr = task_current();
if (curr != 0)
kill(curr->pid, SIGKILL);
}
#if HAVE_BSD_JOB_CONTROL
void
task_pause_current(void)
{
Task *curr = task_current();
if (curr != 0)
killpg(curr->pid, SIGSTOP);
}
void
task_resume_current(void)
{
Task *curr = task_current();
if (curr != 0)
{
killpg(curr->pid, SIGCONT);
curr->flags &= ~TASK_PAUSED;
}
}
gboolean
task_is_paused(void)
{
Task *curr = task_current();
return (curr != 0 && (curr->flags & TASK_PAUSED));
}
#endif
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*END*/
syntax highlighted by Code2HTML, v. 0.9.1