home *** CD-ROM | disk | FTP | other *** search
- /* @(#)preroff.c 1.6 90/06/11 */
- /* copyright Mike Howard, 1990 - all rights reserved */
-
- #ifdef BSD
- # ifdef SYSV
- this is an error - cannot define both BSD and SYSV
- # endif /* SYSV */
- #endif /* BSD */
-
- #include <stdio.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <ctype.h>
- #include <errno.h>
- #include <string.h>
-
- #ifdef BSD
- # include <sys/types.h>
- # include <sys/time.h>
- #endif /* BSD */
-
- char *shell_path = "/bin/sh";
- char *shell_name = "sh";
- #define SO ".so "
- #define SHELL_START ".shell-start"
- #define SHELL_END ".shell-end"
- #define EXIT_PREROFF ".exit-preroff"
- #define EXEC_CMD ".exec-cmd"
- #define SO_LEN 4
- #define SHELL_START_LEN 12
- #define SHELL_END_LEN 10
- #define EXIT_PREROFF_LEN 13
- #define EXEC_CMD_LEN 9
-
-
- #ifdef _NFILE
- # define STACK_SIZE _NFILE - 5
- #else
- # define STACK_SIZE 15
- #endif
-
- int stack_top;
- struct stack_item {
- struct stack_item *next;
- char *file_name;
- int line_number;
- int fd;
- int pid;
- char *buffer;
- char *next_cp;
- char *last_cp;
- } stack[STACK_SIZE];
-
- int save_fd;
- #define current_fname stack[stack_top].file_name
- #define current_line stack[stack_top].line_number
-
- /* pid of current child process */
- #define child_pid stack[stack_top].pid
- #define current_pid stack[stack_top - 1].pid
-
- #define current_buffer stack[stack_top].buffer
- #define current_next_cp stack[stack_top].next_cp
- #define current_last_cp stack[stack_top].last_cp
-
- #define BUF_SIZE 1024
- char buf[BUF_SIZE];
-
- int verbose;
- int delete_failed_so_flag;
- int timeout = 300; /* everything should send some input within 5 minutes*/
-
- /* dirty.c */
- #define VOID void /* */
- #define VOIDARG void /* */
- /* #define VOID int /* */
- /* #define VOIDARG /* */
-
- #ifdef __STDC__
- int main(int /* argc */ , char ** /* argv */ );
- int open_a_file(char * /* fname */ );
- VOID doit_dirty(VOIDARG);
- VOID gather_command(VOIDARG);
- VOID do_shell_command(VOIDARG);
- VOID do_exec_command(VOIDARG);
- char *collect_args(char * /* bp */ );
- VOID clean_up(int /* sig */ );
- int get_line(VOIDARG);
- VOID read_alarm(VOIDARG);
- VOID kill_current_child(VOIDARG);
- VOID push_name(char * /* fname */ , int /* fd */ );
- VOID pop_name(VOIDARG);
- char *Malloc(unsigned /* size */ );
- char *Realloc(char * /* ptr */ , unsigned /* size */ );
- VOID wait_for_child(VOIDARG);
- VOID err_msg(char * /* s */ );
- #else /* __STDC__ */
- int main( /* int argc, char **argv */ );
- int open_a_file( /* char *fname */ );
- VOID doit_dirty( /* void */ );
- VOID gather_command( /* void */ );
- VOID do_shell_command( /* void */ );
- VOID do_exec_command( /* void */ );
- char *collect_args( /* char *bp */ );
- VOID clean_up( /* int sig */ );
- int get_line( /* void */ );
- VOID read_alarm( /* void */ );
- VOID kill_current_child( /* void */ );
- VOID push_name( /* char *fname, int fd */ );
- VOID pop_name( /* void */ );
- char *Malloc( /* unsigned size */ );
- char *Realloc( /* char *ptr, unsigned size */ );
- VOID wait_for_child( /* void */ );
- VOID err_msg( /* char *s */ );
- #endif /* __STDC__ */
-
- char *hlp[] = {
- "Option Effect",
- "-v(vvv) increment verbosity - for debugging documents",
- "-t timeout change timeout value from default (5 minutes)",
- "-d discard failed .so directives rather than passing",
- " them thru (default)",
- (char *)0};
-
- #define USE_MSG "usage: %s(1.6) [-h | option(s)] [file or '-' ...]\n"
-
- main(argc, argv)
- int argc;
- char **argv;
- {
- int i;
- extern int optind, opterr;
- extern char *optarg;
-
- while ((i = getopt(argc, argv, "hdvt:")) != EOF) {
- switch (i) {
- case 'h':
- for (i=0;hlp[i];i++)
- fprintf(stderr, "%s\n", hlp[i]);
- fprintf(stderr, USE_MSG, argv[0]);
- exit(0);
- case 'd':
- delete_failed_so_flag++;
- break;
- case 't':
- if ((timeout = atoi(optarg)) <= 0)
- timeout = 0;
- break;
- case 'v':
- verbose++;
- break;
- case '?':
- fprintf(stderr, "Illegal argument: %c\n", i);
- fprintf(stderr, USE_MSG, argv[0]);
- exit(1);
- }
- }
-
-
- signal(SIGHUP, clean_up);
- signal(SIGINT, clean_up);
- signal(SIGQUIT, clean_up);
- signal(SIGTERM, clean_up);
- stack_top = 0;
- save_fd = dup(0);
-
- if (argc == optind) {
- push_name("stdin", save_fd);
- doit_dirty();
-
- /* return to the prior file */
- pop_name();
- exit(0);
- }
-
- for (i=optind;i<argc;i++) {
- if (!open_a_file(argv[i])) {
- doit_dirty();
- pop_name();
- }
- }
-
- exit(0);
- }
-
- int open_a_file(fname)
- char *fname;
- {
- int fd;
-
- if (!strcmp(fname, "-")) {
- if (save_fd >= 0) {
- push_name("stdin", save_fd);
- save_fd = -1;
- return 0;
- }
- else {
- err_msg("Attempt to re-scan stdin");
- dup(stack[stack_top].fd);
- close(stack[stack_top].fd);
- return -1;
- }
- }
- else if ((fd = open(fname, O_RDONLY)) < 0) {
- char err_buf[256];
- sprintf(err_buf, "unable to open %s", fname);
- err_msg(err_buf);
- return -1;
- }
-
- push_name(fname, fd);
-
- return 0;
- }
-
- VOID doit_dirty()
- {
- if (verbose)
- fprintf(stderr, "Starting to process %s\n", current_fname);
-
- fflush(stdout);
- while (get_line() > 0) {
- if (verbose > 1)
- fprintf(stderr, "%s:%d: %s", current_fname, current_line,
- buf);
- if (!strncmp(buf, SO, SO_LEN)) {
- char *fname = &buf[4];
- char *cp;
-
- /* find and bound file name */
- while (isspace(*fname))
- fname++;
- for (cp=fname;!isspace(*cp);cp++)
- ;
- *cp = '\0';
-
- if (!open_a_file(fname)) {
- doit_dirty();
- pop_name();
- }
- else if (!delete_failed_so_flag)
- fputs(buf, stdout);
- }
- else if (!strncmp(buf, SHELL_START, SHELL_START_LEN)) {
- gather_command();
- do_shell_command();
- }
- else if (!strncmp(buf, EXEC_CMD, EXEC_CMD_LEN)) {
- do_exec_command();
- }
- else if (!strncmp(buf, EXIT_PREROFF, EXIT_PREROFF_LEN)) {
- break;
- }
- else
- fputs(buf, stdout);
- }
-
- if (verbose)
- fprintf(stderr, "Finished processing %s\n", current_fname);
- }
-
- char *tmp_fname;
-
- VOID gather_command()
- {
- int fd;
-
- /* open a temp file */
- do {
- tmp_fname = tmpnam((char *)0);
- } while ((fd = open(tmp_fname, O_WRONLY | O_CREAT | O_EXCL, 0666)) < 0);
-
- if (verbose > 1)
- write(fd, "set -v\n", 7);
- if (verbose > 2)
- write(fd, "set -x\n", 7);
-
- while (get_line() > 0) {
- if (!strncmp(buf, SHELL_END, SHELL_END_LEN))
- break;
-
- write(fd, buf, strlen(buf));
- }
-
- close(fd);
- }
-
- VOID do_shell_command()
- {
- int pipe_fd[2];
- int fd;
-
- if (pipe(pipe_fd) < 0) {
- err_msg("unable to open pipe to command");
- unlink(tmp_fname);
- tmp_fname = (char *)0;
- return;
- }
-
- if (child_pid = fork()) {
- if (verbose)
- fprintf(stderr, "forking child process: %d\n", child_pid);
-
- close(pipe_fd[1]);
- push_name("shell-process", pipe_fd[0]);
- close(pipe_fd[0]);
-
- doit_dirty();
- wait_for_child();
- pop_name();
- }
- else {
- /* direct output to pipe */
- close(1);
- dup(pipe_fd[1]);
- close(pipe_fd[0]);
- close(pipe_fd[1]);
-
- close(0);
- open("/dev/null", O_RDONLY);
-
- execl(shell_path, shell_name, tmp_fname, (char *)0);
- err_msg("exec of shell failed");
- }
-
- unlink(tmp_fname);
- tmp_fname = (char *)0;
- }
-
- /* the command line(s) are scanned and arguments are found.
- the scanner recognizes '...' and "..." strings - the enclosing
- quote marks are stripped, but no /bin/sh interpretations are performed
- \x escapes are processed by stripping the \ character
- and recognizes \{white-space}*\n as a continuation to the next line
- */
-
- VOID do_exec_command()
- {
- int pipe_fd[2];
- char *args[100];
- int arg_count = 0;
-
- while (args[arg_count] =
- collect_args(arg_count ? (char *)0 : &buf[EXEC_CMD_LEN]))
- arg_count++;
-
- if (!arg_count) {
- err_msg("No arguments for system command");
- return;
- }
-
- if (verbose > 1) {
- int i;
-
- fprintf(stderr, "%s arg dump:\n", EXEC_CMD);
- for (i=0;i<arg_count;i++)
- fprintf(stderr, "arg[%2d]: %s\n", i, args[i]);
- }
-
- if (pipe(pipe_fd) < 0) {
- err_msg("unable to open pipe to command");
- return;
- }
-
- if (child_pid = fork()) {
- if (verbose)
- fprintf(stderr, "forking child process: %d\n", child_pid);
- close(pipe_fd[1]);
- push_name("exec-cmd", pipe_fd[0]);
- close(pipe_fd[0]);
-
- doit_dirty();
- wait_for_child();
- pop_name();
- }
- else {
- int local_errno;
-
- /* direct output to pipe */
- close(1);
- dup(pipe_fd[1]);
- close(pipe_fd[0]);
- close(pipe_fd[1]);
-
- close(0);
- open("/dev/null", O_RDONLY);
-
- execvp(args[0], args);
- local_errno = errno;
- {
- char err_buf[256];
- extern char *sys_errlist[];
-
- sprintf(err_buf, "exec of %s failed\n%d - %s", args[0], local_errno,
- sys_errlist[local_errno]);
- err_msg(err_buf);
- }
- exit(1);
- }
-
- unlink(tmp_fname);
- tmp_fname = (char *)0;
- }
-
- char *collect_args(bp)
- char *bp;
- {
- static char *next_char;
- char *base;
- int c;
- int in_quoted_string;
- char *cp;
- int len;
-
- /* initialize next_char pointers on first call */
- if (bp)
- next_char = bp;
-
- /* strip internal white space and test for end of list */
- do {
- if (verbose > 2)
- fprintf(stderr, "skipping %lx: %c %2x\n", next_char, *next_char, *next_char);
- while (isspace(*next_char))
- next_char++;
- /* if there is no next token then we are done */
- if (!*next_char)
- return (char *)0;
-
- /* if we see \ \n seqence, then this is a continuation line */
- if (next_char[0] == '\\' && next_char[1] == '\n') {
- if (get_line() <= 0)
- return (char *)0;
- next_char = buf;
- }
- } while (isspace(*next_char));
-
- /* allocate sufficient room */
- base =
- cp = Malloc(strlen(next_char));
-
- /* detect quoted string */
- if ((c = *next_char) == '\'' || c == '"') {
- next_char++;
- in_quoted_string = c;
- }
- else
- in_quoted_string = 0;
-
- /* collect this argument */
- while (1) {
- if (verbose > 2)
- fprintf(stderr, "scanning %lx: %c %2x\n", next_char, *next_char, *next_char);
- switch (c = *next_char++) {
- case '\'':
- case '"':
- if (in_quoted_string) {
- if (c == in_quoted_string)
- in_quoted_string = 0;
- else
- *cp++ = c;
- }
- else
- in_quoted_string = c;
- break;
- case '\\':
- if ((*cp++ = *next_char++) == '\n') {
- cp--;
- if (get_line() <= 0) {
- *cp = '\0';
- return Realloc(base, strlen(base));
- }
- len = cp - base;
- base = Realloc(base, strlen(base) + strlen(buf));
- cp = base + len;
- next_char = buf;
- }
- break;
- case ' ':
- case '\t':
- if (in_quoted_string) {
- *cp++ = c;
- }
- else {
- *cp = '\0';
- return Realloc(base, strlen(base));
- }
- break;
- case '\n':
- if (!in_quoted_string) {
- *cp = '\0';
- return Realloc(base, strlen(base));
- }
- *cp++ = '\n';
- if (get_line() <= 0) {
- *cp = '\0';
- return Realloc(base, strlen(base));
- }
- len = cp - base;
- base = Realloc(base, strlen(base) + strlen(buf));
- cp = base + len;
- next_char = buf;
- break;
- case '\0':
- err_msg("null encountered in .exec-cmd arg list");
- *cp = '\0';
- return Realloc(base, strlen(base));
- default:
- *cp++ = c;
- break;
- }
- }
- }
-
- VOID clean_up(sig)
- int sig;
- {
- if (tmp_fname)
- unlink(tmp_fname);
-
- switch (sig) {
- case SIGQUIT:
- abort();
- default:
- exit(sig);
- }
- }
-
- #ifdef BSD
- int get_line()
- {
- char *cp = buf;
- char *ncp = current_next_cp;
- char *lcp = current_last_cp;
- int len;
- int local_errno;
- fd_set read_fdset;
- struct timeval select_timeout;
-
- while (1) {
- while (ncp < lcp) {
- if ((*cp++ = *ncp++) == '\n') {
- current_next_cp = ncp;
- current_line++;
- *cp = '\0';
- return cp - buf;
- }
- }
-
- /* test for ready-to-read */
- FD_ZERO(&read_fdset);
- FD_SET(0, &read_fdset);
- select_timeout.tv_sec = timeout;
- select_timeout.tv_usec = 0;
- if (select(1, &read_fdset, (fd_set *)0, (fd_set *)0, &timeout) <= 0) {
- int old_timeout = timeout;
-
- err_msg("Input timeout - probable infinite loop");
- timeout = 5;
- signal(SIGALRM, kill_current_child);
- alarm(timeout);
- kill_current_child();
- wait_for_child();
- timeout = old_timeout;
-
- local_errno = errno;
- alarm(0);
- signal(SIGALRM, SIG_IGN);
- }
-
- /* input ready - so read stdin */
- len = read(0, current_buffer, BUF_SIZE);
-
- /* initialize input buffer pointers */
- ncp =
- current_next_cp = current_buffer;
- lcp =
- current_last_cp = current_buffer + len;
-
- if (len < 0 && local_errno == EINTR) {
- fprintf(stderr, "errno: %d: %s, %d\n", local_errno,
- current_fname, current_line);
- errno = 0;
- }
-
- if (len <= 0) {
- *cp = '\0';
- return cp - buf;
- }
- }
- }
-
- #else /* BSD */
-
- int get_line()
- {
- char *cp = buf;
- char *ncp = current_next_cp;
- char *lcp = current_last_cp;
- int len;
- int local_errno;
-
- while (1) {
- while (ncp < lcp) {
- if ((*cp++ = *ncp++) == '\n') {
- current_next_cp = ncp;
- current_line++;
- *cp = '\0';
- return cp - buf;
- }
- }
-
- signal(SIGALRM, read_alarm);
- alarm(timeout);
- len = read(0, current_buffer, BUF_SIZE);
- local_errno = errno;
- alarm(0);
- signal(SIGALRM, SIG_IGN);
-
- ncp =
- current_next_cp = current_buffer;
- lcp =
- current_last_cp = current_buffer + len;
-
- if (len < 0 && local_errno == EINTR) {
- int old_timeout = timeout;
-
- err_msg("Input timeout - probable infinite loop");
- timeout = 5;
- signal(SIGALRM, kill_current_child);
- alarm(timeout);
- kill_current_child();
- wait_for_child();
- timeout = old_timeout;
- }
- if (verbose > 1 && local_errno) {
- fprintf(stderr, "errno: %d: %s, %d\n", local_errno,
- current_fname, current_line);
- errno = 0;
- }
-
- if (len <= 0) {
- *cp = '\0';
- return cp - buf;
- }
- }
- }
-
- VOID read_alarm()
- {
- return;
- }
- #endif /* BSD */
-
- /* this routine attempts to kill `current_pid' with a SIGTERM. If
- that fails,(causing an alarm signal) it sends a SIGKILL */
-
- VOID kill_current_child()
- {
- static int last_pid_killed = 0;
-
- if (verbose)
- fprintf(stderr, "Alarm Signal - sending %d to %d\n",
- last_pid_killed == current_pid ? SIGKILL : SIGTERM, current_pid);
- if (current_pid) {
- kill(current_pid, last_pid_killed == current_pid ? SIGKILL : SIGTERM);
- last_pid_killed = current_pid;
- }
- signal(SIGALRM, kill_current_child);
- alarm(timeout);
-
- return;
- }
-
- VOID push_name(fname, fd)
- char *fname;
- int fd;
- {
- if (stack_top >= STACK_SIZE) {
- char err_buf[80];
-
- sprintf(err_buf, "attempt to open more than %d inputs\n", STACK_SIZE -1);
- err_msg(err_buf);
- return;
- }
-
- /* redirect stdin - saving old file descriptor */
- stack[stack_top].fd = dup(0);
- close(0);
- dup(fd);
- close(fd);
-
- /* push new file name and initialize line counter */
- stack[++stack_top].file_name = strcpy(Malloc(strlen(fname)), fname);
- stack[stack_top].line_number = 0;
- if (!stack[stack_top].buffer)
- stack[stack_top].buffer = Malloc(BUF_SIZE);
- stack[stack_top].next_cp =
- stack[stack_top].last_cp = stack[stack_top].buffer;
- }
-
- /* pops stack and restores stdin */
-
- VOID pop_name()
- {
- if (stack_top < 0) {
- err_msg("fatal error - pop_name()\n");
- abort();
- }
- free(stack[stack_top--].file_name);
- close(0);
- if (stack_top >= 0) {
- dup(stack[stack_top].fd);
- close(stack[stack_top].fd);
- }
- }
-
- /* like malloc EXCEPT that it abort()s if malloc() fails and it allocates
- size+1 bytes */
-
- char *Malloc(size)
- unsigned size;
- {
- char *malloc();
- char *ret;
-
- if (ret = malloc(++size))
- return ret;
-
- err_msg("Memory Allocation Failure in Malloc");
- abort();
- }
-
- /* like realloc EXCEPT that it abort()s if malloc() fails and it allocates
- size+1 bytes */
-
- char *Realloc(ptr, size)
- char *ptr;
- unsigned size;
- {
- char *realloc();
- char *ret;
-
- if (ret = realloc(ptr, ++size))
- return ret;
-
- err_msg("Memory Allocation Failure in Realloc");
- abort();
- }
-
- /* This is messy - I'm not all that sure I trust it.
-
- The hair comes in when we have a couple of child processes running.
- Exprience says that they may return their pid's to the wait() call
- out of order, so we have to test all valid wait returns to see
- if they were some of our children and clear their slot in the stack.
- Also, wait_for_child() has to make sure that the child it is waiting
- for has not already gone away.
-
- for the curious: this sort of thing happens when a child process
- produces output which contains .shell-start/end or .exec-cmd directives:
- the first child spits out the output and then dies.
- preroff reads the stream and then fires up a new child to process
- the directive - the first child's output is still in the pipe.
- the second child croaks and the doit_dirty() exits - which starts
- the wait_for_child() loop - which receives the pid's of the dead
- processes in the order they died, but not in the order of completing
- processing their output.
- */
-
- VOID wait_for_child()
- {
- int wait_ret;
- int status;
- int counter = 0;
- int i;
-
- if (!current_pid)
- return;
-
- signal(SIGALRM, kill_current_child);
- alarm(timeout);
- while (1) {
- wait_ret = wait(&status);
- if (verbose)
- fprintf(stderr, "wait_for_child: wait returned %d\n", wait_ret);
- if (wait_ret == current_pid) {
- current_pid = 0;
- break;
- }
- if (wait_ret > 0)
- for (i=0;i<stack_top;i++) {
- if (wait_ret == stack[i].pid) {
- stack[i].pid = 0;
- break;
- }
- }
-
-
- /* if it was an interupt, then assume it was an alarm */
- if (wait_ret == -1 && counter++ > 2 && errno == EINTR)
- break;
-
- if (counter > 4)
- break;
- }
-
- signal(SIGALRM, SIG_IGN);
- alarm(0);
- }
-
- VOID err_msg(s)
- char *s;
- {
- int i;
-
- fprintf(stderr, "%s\nStack Trace\n", s ? s : "Error Detected");
-
- for (i=stack_top;i>0;i--)
- fprintf(stderr, "line %4d: %s \n", stack[i].line_number,
- stack[i].file_name);
- }
-