/* * 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 #include #include "glib_extra.h" #if HAVE_SYS_IOCTL_H #include #endif #if HAVE_SYS_FILIO_H /* for FIONREAD on Solaris */ #include #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*/