/* tubo.c (0.22) */ /* A program independent forking object module for gtk based programs. * * Copyright 2000-2002(C) Edscott Wilson Garcia under GNU GPL * * 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. */ #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifndef HAVE_SYS_TYPES_H #error "This program requieres the header file." #endif #ifndef HAVE_SYS_WAIT_H #error "This program requieres the header file." #endif #ifndef HAVE_SIGNAL_H #error "This program requieres the header file." #endif /* signal handling */ #ifndef HAVE_SIGNAL #ifndef HAVE_SIGACTION #error "This program needs signals to work!" #endif #endif #include #include #include #include #include #include #include #include #include #include /* GTK */ #include #include #include #include /* public stuff */ #include "tubo.h" /* private stuff */ #define READ_CONDITION (G_IO_IN | G_IO_HUP | G_IO_ERR) #define WRITE_CONDITION (G_IO_OUT | G_IO_ERR) #define EXCEPTION_CONDITION (G_IO_PRI) /* memcpy is necesary */ #ifdef __GNUC__ /* memcpy is a GNU extension.*/ #define MEMCPY memcpy #else static void *MEMCPY(void *dest, const void *src, size_t n) { char *destC, *srcC; size_t i; destC = (char *)dest; srcC = (char *)src; for(i = 0; i < n; i++) destC[i] = srcC[i]; return dest; } #endif typedef struct { GdkInputFunction function; GdkInputCondition condition; gpointer data; } TuboIOClosure; typedef struct fork_structure { pid_t childPID; pid_t PID; int tubo[3][3]; /* user fork function: */ void (*fork_function) (void *); void (*fork_finished_function) (pid_t); /* user parse functions: */ int operate_stdin; int (*operate_stdout) (int, void *); int (*operate_stderr) (int, void *); } fork_struct; #ifdef DEBUG #endif static void tubo_io_destroy (gpointer data) { TuboIOClosure *closure = data; g_free (closure);closure=NULL; } static gboolean tubo_io_invoke (GIOChannel *source, GIOCondition condition, gpointer data) { TuboIOClosure *closure = data; GdkInputCondition gdk_cond = 0; if (condition & READ_CONDITION) gdk_cond |= GDK_INPUT_READ; if (condition & WRITE_CONDITION) gdk_cond |= GDK_INPUT_WRITE; if (condition & EXCEPTION_CONDITION) gdk_cond |= GDK_INPUT_EXCEPTION; if (closure->condition & gdk_cond) closure->function (closure->data, g_io_channel_unix_get_fd (source), gdk_cond); return TRUE; } gint tubo_input_add (gint source, GdkInputCondition condition, GdkInputFunction function, gpointer data) { guint result; TuboIOClosure *closure = g_new (TuboIOClosure, 1); GIOChannel *channel; GIOCondition cond = 0; closure->function = function; closure->condition = condition; closure->data = data; if (condition & GDK_INPUT_READ) cond |= READ_CONDITION; if (condition & GDK_INPUT_WRITE) cond |= WRITE_CONDITION; if (condition & GDK_INPUT_EXCEPTION) cond |= EXCEPTION_CONDITION; channel = g_io_channel_unix_new (source); result = g_io_add_watch_full (channel, G_PRIORITY_DEFAULT, cond, tubo_io_invoke, closure, tubo_io_destroy); g_io_channel_unref (channel); return result; } static fork_struct *TuboChupoFaros(fork_struct * forkO) { int i; #ifdef DEBUG fprintf(stderr, "Chupando faros\n"); #endif if(!forkO) { return NULL; } for(i = 0; i < 3; i++) { if(forkO->tubo[i][0] > 0) { close(forkO->tubo[i][0]); } if(forkO->tubo[i][1] > 0) { close(forkO->tubo[i][1]); } if(forkO->tubo[i][2] > 0) { #ifdef DEBUG fprintf(stderr, "removing input signal %d\n", i); #endif g_source_remove(forkO->tubo[i][2]); } } free(forkO); return NULL; } #define TUBO_BLK_SIZE 256 /* 256 is the size of my cpu internal cache */ static char line[TUBO_BLK_SIZE]; int get_line(gint src) { int i; memset(line, 0, TUBO_BLK_SIZE); for(i = 0; i < TUBO_BLK_SIZE - 1; i++) { if(!read(src, line + i, 1)) break; /*printf("%c",line[i]);fflush(NULL); */ if(*(line + i) == '\n') { *(line + i + 1) = (char)0; return 0; } } if(i) return i; /* something has been read */ return -1; } static gboolean TuboInput(gpointer data, gint src, GdkInputCondition condition) { int i; int (*user_parse_function) (int, char *); if(!data) return FALSE; user_parse_function = (int (*)(int, char *))((long)data); #ifdef DEBUG /*printf("DBG: input found at %d\n", src);*/ fflush(NULL); #endif { int retval; fd_set rfds; struct timeval tv; FD_ZERO(&rfds); FD_SET(src, &rfds); /* Wait up to one microsecond. */ tv.tv_sec = 0; tv.tv_usec = 1; retval = select(1 + src, &rfds, NULL, NULL, &tv); if(!retval) { /*printf("DBG: this is a gdk bug workaround *********.\n"); */ return TRUE; } } i = get_line(src); if(i < 0) return FALSE; (*user_parse_function) (i, (void *)line); return TRUE; } static gint TuboWaitDone(gpointer fork_object) { pid_t PID; void (*user_end_function) (pid_t); fork_struct *forkO; forkO = (fork_struct *) ((long)fork_object); PID = forkO->PID; user_end_function = forkO->fork_finished_function; /* printf("wait timeoutdone childpid=%d\n",forkO->childPID); */ if(forkO->childPID) return TRUE; /* what if the pipe has data? */ if(TuboInput((gpointer) (forkO->operate_stdout), forkO->tubo[1][0], GDK_INPUT_READ)) return TRUE; if(TuboInput((gpointer) (forkO->operate_stderr), forkO->tubo[1][0], GDK_INPUT_READ)) return TRUE; TuboChupoFaros(forkO); if(user_end_function) (*user_end_function) (PID); return FALSE; } static gint TuboWait(gpointer fork_object) { fork_struct *forkO; int status; forkO = (fork_struct *) ((long)fork_object); #ifdef __FreeBSD__ if (kill(forkO->childPID,SIGCONT) == 0) return TRUE; #endif waitpid(forkO->childPID, &status, WNOHANG); if(WIFEXITED(status) || WIFSIGNALED(status)) { forkO->childPID = 0; return FALSE; } return TRUE; } static void TuboSemaforo(int sig) { switch (sig) { case SIGUSR1: break; /* green light */ case SIGTERM: _exit(1); break; default: break; } return; } void *Tubo(void (*fork_function) (void *), void *fork_function_data, void (*fork_finished_function) (pid_t), int operate_stdin, int (*operate_stdout) (int, void *), int (*operate_stderr) (int, void *)) { int i; fork_struct tmpfork, *newfork = NULL; #ifndef HAVE_SIGNAL struct sigaction act; sigemptyset(&act.sa_mask); #ifdef SA_RESTART act.sa_flags = SA_RESTART; #else act.sa_flags = 0; #endif act.sa_handler = TuboSemaforo; #endif if((!operate_stdout) && (!operate_stderr)) { printf("DBG: Using Tubo with NULL functions for stdout and stderr is quite useless except for debugging purposes!\n"); #ifdef NO_MAMES TuboChupoFaros(newfork); /* no mames condition */ return NULL; #endif } for(i = 0; i < 3; i++) { tmpfork.tubo[i][0] = tmpfork.tubo[i][1] = -1; tmpfork.tubo[i][2] = 0; if(pipe(tmpfork.tubo[i]) == -1) { TuboChupoFaros(NULL); return NULL; } } tmpfork.operate_stdin = operate_stdin; tmpfork.operate_stdout = operate_stdout; tmpfork.operate_stderr = operate_stderr; tmpfork.fork_function = fork_function; tmpfork.fork_finished_function = fork_finished_function; /* signal(SIGUSR1) has to be done before fork, to avoid race */ #ifdef HAVE_SIGNAL signal(SIGUSR1, TuboSemaforo); #else sigaction(SIGUSR1,&act,NULL); #endif tmpfork.PID = tmpfork.childPID = fork(); if(tmpfork.childPID) { /* the parent */ /* INPUT PIPES *************** */ #ifdef DEBUG printf("The parent process has forked\n"); #endif newfork = (fork_struct *) malloc(sizeof(fork_struct)); if(!newfork) { /* red light to child */ perror("malloc"); #ifdef DEBUG printf("The parent process is sending a red light\n"); #endif kill(tmpfork.childPID, SIGTERM); TuboChupoFaros(NULL); return NULL; } MEMCPY((void *)newfork, (void *)(&tmpfork), sizeof(fork_struct)); close(newfork->tubo[0][0]); /* not used */ if(operate_stdout) { newfork->tubo[1][2] = tubo_input_add(newfork->tubo[1][0], GDK_INPUT_READ, (GdkInputFunction) TuboInput, (gpointer) operate_stdout); } if(operate_stderr) { newfork->tubo[2][2] = tubo_input_add(newfork->tubo[2][0], GDK_INPUT_READ, (GdkInputFunction) TuboInput, (gpointer) operate_stderr); } /* OUTPUT PIPES ************** */ if(!operate_stdin) close(newfork->tubo[0][1]); /* not used */ close(newfork->tubo[1][1]); /* not used */ close(newfork->tubo[2][1]); /* not used */ /* the wait */ g_timeout_add_full(0, 260, (GtkFunction) TuboWait, (gpointer) (newfork), NULL); /* what to do when child is kaput. */ g_timeout_add_full(0, 520, (GtkFunction) TuboWaitDone, (gpointer) (newfork), NULL); /*send greenlight to child: ok to continue */ usleep(500000); /* race: child must be at pause before sending signal */ #ifdef DEBUG printf("The parent process is sending a green light\n"); #endif kill(newfork->childPID, SIGUSR1); return newfork; /* back to user's program flow */ } else { /* the child */ /* waitfor green light. */ #ifdef HAVE_SIGNAL signal(SIGTERM, TuboSemaforo); #else sigaction(SIGTERM,&act,NULL); #endif #ifdef DEBUG printf("The child process is waiting for the green light\n"); #endif pause(); #ifdef DEBUG printf("The child has received the green light\n"); #endif newfork = (fork_struct *) malloc(sizeof(fork_struct)); if(!newfork) { _exit(1); } MEMCPY((void *)newfork, (void *)(&tmpfork), sizeof(fork_struct)); /* INPUT PIPES */ if(operate_stdin) { dup2(newfork->tubo[0][0], 0); /* stdin */ } else close(newfork->tubo[0][0]); /* not used */ close(newfork->tubo[1][0]); /* not used */ close(newfork->tubo[2][0]); /* not used */ /* OUTPUT PIPES */ close(newfork->tubo[0][1]); /* not used */ if(operate_stdout) dup2(newfork->tubo[1][1], 1); /* stdout */ else close(newfork->tubo[1][1]); /* not used */ if(operate_stdout) dup2(newfork->tubo[2][1], 2); /* stderr */ else close(newfork->tubo[2][1]); /* not used */ /* user fork function */ if(newfork->fork_function) (*(newfork->fork_function)) (fork_function_data); /* if it returns, clean up before sinking */ TuboChupoFaros(newfork); _exit(1); } } int TuboWrite(void *forkObject, void *data, int n) { /* if n!=0 --> binary data */ int size; fork_struct *forkO; forkO = (fork_struct *) forkObject; if(!forkO) return 0; if(!data) return 0; if(!n) size = n; else size = strlen((char *)data); write(forkO->tubo[0][1], data, size); /* watchout for closed pipes */ return 1; } void *TuboCancel(void *forkObject, void (*cleanup) (void)) { int i; fork_struct *forkO; forkO = (fork_struct *) forkObject; if(!forkO) { return NULL; } #ifdef DEBUG fprintf(stderr, "cancelling fork object\n"); #endif for(i = 0; i < 3; i++) { if(forkO->tubo[i][2] > 0) { /* remove input callbacks */ #ifdef DEBUG fprintf(stderr, "removing input signal %d\n", i); #endif g_source_remove(forkO->tubo[i][2]); } if(forkO->tubo[i][0] > 0) { /* close input pipes */ close(forkO->tubo[i][0]); } if(forkO->tubo[i][1] > 0) { /* close output pipes */ close(forkO->tubo[i][1]); } } forkO->fork_finished_function = NULL; forkO->operate_stdin = FALSE; forkO->operate_stdout = NULL; forkO->operate_stderr = NULL; if(forkO->childPID){ kill(forkO->childPID, SIGTERM); kill(forkO->childPID, SIGKILL); } if(cleanup) (*cleanup) (); /*note: fork object freed by TuboWaitDone() function */ return NULL; } pid_t TuboPID(gpointer fork_object) { fork_struct *forkO; if (!fork_object) return 0; forkO = (fork_struct *) ((long)fork_object); return (forkO->PID); }