/* run.c --- routines for executing subprocesses. This file is part of GNU CVS. GNU CVS 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "cvs.h" #ifdef HAVE_VPRINTF #if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__) #include #define VA_START(args, lastarg) va_start(args, lastarg) #else #include #define VA_START(args, lastarg) va_start(args) #endif #else #define va_alist a1, a2, a3, a4, a5, a6, a7, a8 #define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; #endif static void run_add_arg PROTO((const char *s)); static void run_init_prog PROTO((void)); extern char *strtok (); /* * To exec a program under CVS, first call run_setup() to setup any initial * arguments. The options to run_setup are essentially like printf(). The * arguments will be parsed into whitespace separated words and added to the * global run_argv list. * * Then, optionally call run_arg() for each additional argument that you'd like * to pass to the executed program. * * Finally, call run_exec() to execute the program with the specified arguments. * The execvp() syscall will be used, so that the PATH is searched correctly. * File redirections can be performed in the call to run_exec(). */ static char *run_prog; static char **run_argv; static int run_argc; static int run_argc_allocated; /* VARARGS */ #if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)) void run_setup (const char *fmt,...) #else void run_setup (fmt, va_alist) char *fmt; va_dcl #endif { #ifdef HAVE_VPRINTF va_list args; #endif char *cp; int i; run_init_prog (); /* clean out any malloc'ed values from run_argv */ for (i = 0; i < run_argc; i++) { if (run_argv[i]) { free (run_argv[i]); run_argv[i] = (char *) 0; } } run_argc = 0; /* process the varargs into run_prog */ #ifdef HAVE_VPRINTF VA_START (args, fmt); (void) vsprintf (run_prog, fmt, args); va_end (args); #else (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8); #endif /* put each word into run_argv, allocating it as we go */ for (cp = strtok (run_prog, " \t"); cp; cp = strtok ((char *) NULL, " \t")) run_add_arg (cp); } void run_arg (s) const char *s; { run_add_arg (s); } /* VARARGS */ #if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)) void run_args (const char *fmt,...) #else void run_args (fmt, va_alist) char *fmt; va_dcl #endif { #ifdef HAVE_VPRINTF va_list args; #endif run_init_prog (); /* process the varargs into run_prog */ #ifdef HAVE_VPRINTF VA_START (args, fmt); (void) vsprintf (run_prog, fmt, args); va_end (args); #else (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8); #endif /* and add the (single) argument to the run_argv list */ run_add_arg (run_prog); } static void run_add_arg (s) const char *s; { /* allocate more argv entries if we've run out */ if (run_argc >= run_argc_allocated) { run_argc_allocated += 50; run_argv = (char **) xrealloc ((char *) run_argv, run_argc_allocated * sizeof (char **)); } if (s) run_argv[run_argc++] = xstrdup (s); else run_argv[run_argc] = (char *) 0; /* not post-incremented on purpose! */ } static void run_init_prog () { /* make sure that run_prog is allocated once */ if (run_prog == (char *) 0) run_prog = xmalloc (10 * 1024); /* 10K of args for _setup and _arg */ } int run_exec (stin, stout, sterr, flags) char *stin; char *stout; char *sterr; int flags; { int shin, shout, sherr; int mode_out, mode_err; int status; int rc = -1; int rerrno = 0; int pid, w; #ifdef POSIX_SIGNALS sigset_t sigset_mask, sigset_omask; struct sigaction act, iact, qact; #else #ifdef BSD_SIGNALS int mask; struct sigvec vec, ivec, qvec; #else RETSIGTYPE (*istat) (), (*qstat) (); #endif #endif if (trace) { #ifdef SERVER_SUPPORT (void) fprintf (stderr, "%c-> system(", (server_active) ? 'S' : ' '); #else (void) fprintf (stderr, "-> system("); #endif run_print (stderr); (void) fprintf (stderr, ")\n"); } if (noexec && (flags & RUN_REALLY) == 0) return (0); /* make sure that we are null terminated, since we didn't calloc */ run_add_arg ((char *) 0); /* setup default file descriptor numbers */ shin = 0; shout = 1; sherr = 2; /* set the file modes for stdout and stderr */ mode_out = mode_err = O_WRONLY | O_CREAT; mode_out |= ((flags & RUN_STDOUT_APPEND) ? O_APPEND : O_TRUNC); mode_err |= ((flags & RUN_STDERR_APPEND) ? O_APPEND : O_TRUNC); if (stin && (shin = open (stin, O_RDONLY)) == -1) { rerrno = errno; error (0, errno, "cannot open %s for reading (prog %s)", stin, run_argv[0]); goto out0; } if (stout && (shout = open (stout, mode_out, 0666)) == -1) { rerrno = errno; error (0, errno, "cannot open %s for writing (prog %s)", stout, run_argv[0]); goto out1; } if (sterr && (flags & RUN_COMBINED) == 0) { if ((sherr = open (sterr, mode_err, 0666)) == -1) { rerrno = errno; error (0, errno, "cannot open %s for writing (prog %s)", sterr, run_argv[0]); goto out2; } } /* Make sure we don't flush this twice, once in the subprocess. */ fflush (stdout); fflush (stderr); /* The output files, if any, are now created. Do the fork and dups */ #ifdef HAVE_VFORK pid = vfork (); #else pid = fork (); #endif if (pid == 0) { if (shin != 0) { (void) dup2 (shin, 0); (void) close (shin); } if (shout != 1) { (void) dup2 (shout, 1); (void) close (shout); } if (flags & RUN_COMBINED) (void) dup2 (1, 2); else if (sherr != 2) { (void) dup2 (sherr, 2); (void) close (sherr); } /* dup'ing is done. try to run it now */ (void) execvp (run_argv[0], run_argv); error (0, errno, "cannot exec %s", run_argv[0]); _exit (127); } else if (pid == -1) { rerrno = errno; goto out; } /* the parent. Ignore some signals for now */ #ifdef POSIX_SIGNALS if (flags & RUN_SIGIGNORE) { act.sa_handler = SIG_IGN; (void) sigemptyset (&act.sa_mask); act.sa_flags = 0; (void) sigaction (SIGINT, &act, &iact); (void) sigaction (SIGQUIT, &act, &qact); } else { (void) sigemptyset (&sigset_mask); (void) sigaddset (&sigset_mask, SIGINT); (void) sigaddset (&sigset_mask, SIGQUIT); (void) sigprocmask (SIG_SETMASK, &sigset_mask, &sigset_omask); } #else #ifdef BSD_SIGNALS if (flags & RUN_SIGIGNORE) { memset ((char *) &vec, 0, sizeof (vec)); vec.sv_handler = SIG_IGN; (void) sigvec (SIGINT, &vec, &ivec); (void) sigvec (SIGQUIT, &vec, &qvec); } else mask = sigblock (sigmask (SIGINT) | sigmask (SIGQUIT)); #else istat = signal (SIGINT, SIG_IGN); qstat = signal (SIGQUIT, SIG_IGN); #endif #endif /* wait for our process to die and munge return status */ #ifdef POSIX_SIGNALS while ((w = waitpid (pid, &status, 0)) == -1 && errno == EINTR) ; #else while ((w = wait (&status)) != pid) { if (w == -1 && errno != EINTR) break; } #endif if (w == -1) { rc = -1; rerrno = errno; } #ifndef VMS /* status is return status */ else if (WIFEXITED (status)) rc = WEXITSTATUS (status); else if (WIFSIGNALED (status)) { if (WTERMSIG (status) == SIGPIPE) error (1, 0, "broken pipe"); rc = 2; } else rc = 1; #else /* VMS */ rc = WEXITSTATUS (status); #endif /* VMS */ /* restore the signals */ #ifdef POSIX_SIGNALS if (flags & RUN_SIGIGNORE) { (void) sigaction (SIGINT, &iact, (struct sigaction *) NULL); (void) sigaction (SIGQUIT, &qact, (struct sigaction *) NULL); } else (void) sigprocmask (SIG_SETMASK, &sigset_omask, (sigset_t *) NULL); #else #ifdef BSD_SIGNALS if (flags & RUN_SIGIGNORE) { (void) sigvec (SIGINT, &ivec, (struct sigvec *) NULL); (void) sigvec (SIGQUIT, &qvec, (struct sigvec *) NULL); } else (void) sigsetmask (mask); #else (void) signal (SIGINT, istat); (void) signal (SIGQUIT, qstat); #endif #endif /* cleanup the open file descriptors */ out: if (sterr) (void) close (sherr); out2: if (stout) (void) close (shout); out1: if (stin) (void) close (shin); out0: if (rerrno) errno = rerrno; return (rc); } void run_print (fp) FILE *fp; { int i; for (i = 0; i < run_argc; i++) { (void) fprintf (fp, "'%s'", run_argv[i]); if (i != run_argc - 1) (void) fprintf (fp, " "); } } FILE * run_popen (cmd, mode) const char *cmd; const char *mode; { if (trace) #ifdef SERVER_SUPPORT (void) fprintf (stderr, "%c-> run_popen(%s,%s)\n", (server_active) ? 'S' : ' ', cmd, mode); #else (void) fprintf (stderr, "-> run_popen(%s,%s)\n", cmd, mode); #endif if (noexec) return (NULL); return (popen (cmd, mode)); } extern int evecvp PROTO((char *file, char **argv)); int piped_child (command, tofdp, fromfdp) char **command; int *tofdp; int *fromfdp; { int pid; int to_child_pipe[2]; int from_child_pipe[2]; if (pipe (to_child_pipe) < 0) error (1, errno, "cannot create pipe"); if (pipe (from_child_pipe) < 0) error (1, errno, "cannot create pipe"); pid = fork (); if (pid < 0) error (1, errno, "cannot fork"); if (pid == 0) { if (dup2 (to_child_pipe[0], STDIN_FILENO) < 0) error (1, errno, "cannot dup2"); if (close (to_child_pipe[1]) < 0) error (1, errno, "cannot close"); if (close (from_child_pipe[0]) < 0) error (1, errno, "cannot close"); if (dup2 (from_child_pipe[1], STDOUT_FILENO) < 0) error (1, errno, "cannot dup2"); execvp (command[0], command); error (1, errno, "cannot exec"); } if (close (to_child_pipe[0]) < 0) error (1, errno, "cannot close"); if (close (from_child_pipe[1]) < 0) error (1, errno, "cannot close"); *tofdp = to_child_pipe[1]; *fromfdp = from_child_pipe[0]; return pid; } void close_on_exec (fd) int fd; { #if defined (FD_CLOEXEC) && defined (F_SETFD) if (fcntl (fd, F_SETFD, 1)) error (1, errno, "can't set close-on-exec flag on %d", fd); #endif } /* * dir = 0 : main proc writes to new proc, which writes to oldfd * dir = 1 : main proc reads from new proc, which reads from oldfd * * Returns: a file descriptor. On failure (i.e., the exec fails), * then filter_stream_through_program() complains and dies. */ int filter_stream_through_program (oldfd, dir, prog, pidp) int oldfd, dir; char **prog; pid_t *pidp; { int p[2], newfd; pid_t newpid; if (pipe (p)) error (1, errno, "cannot create pipe"); newpid = fork (); if (pidp) *pidp = newpid; switch (newpid) { case -1: error (1, errno, "cannot fork"); case 0: /* child */ if (dir) { /* write to new pipe */ close (p[0]); dup2 (oldfd, 0); dup2 (p[1], 1); } else { /* read from new pipe */ close (p[1]); dup2 (p[0], 0); dup2 (oldfd, 1); } /* Should I be blocking some signals here? */ execvp (prog[0], prog); error (1, errno, "couldn't exec %s", prog[0]); default: /* parent */ close (oldfd); if (dir) { /* read from new pipe */ close (p[1]); newfd = p[0]; } else { /* write to new pipe */ close (p[0]); newfd = p[1]; } close_on_exec (newfd); return newfd; } }