diff options
Diffstat (limited to 'contrib/cvs/src/run.c')
-rw-r--r-- | contrib/cvs/src/run.c | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/contrib/cvs/src/run.c b/contrib/cvs/src/run.c new file mode 100644 index 0000000..98ae911 --- /dev/null +++ b/contrib/cvs/src/run.c @@ -0,0 +1,578 @@ +/* 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. */ + +#include "cvs.h" + +#ifndef HAVE_UNISTD_H +extern int execvp PROTO((char *file, char **argv)); +#endif + +static void run_add_arg PROTO((const char *s)); + +extern char *strtok (); + +/* + * To exec a program under CVS, first call run_setup() to setup initial + * arguments. The argument to run_setup 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_argv; +static int run_argc; +static size_t run_argc_allocated; + + + +void +run_arg_free_p (int argc, char **argv) +{ + int i; + for (i = 0; i < argc; i++) + free (argv[i]); +} + + + +/* VARARGS */ +void +run_setup (prog) + const char *prog; +{ + char *cp; + char *run_prog; + + /* clean out any malloc'ed values from run_argv */ + run_arg_free_p (run_argc, run_argv); + run_argc = 0; + + run_prog = xstrdup (prog); + + /* 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); + free (run_prog); +} + +void +run_arg (s) + const char *s; +{ + run_add_arg (s); +} + + + +void +run_add_arg_p (iargc, iarg_allocated, iargv, s) + int *iargc; + size_t *iarg_allocated; + char ***iargv; + const char *s; +{ + /* allocate more argv entries if we've run out */ + if (*iargc >= *iarg_allocated) + { + *iarg_allocated += 50; + *iargv = xrealloc (*iargv, *iarg_allocated * sizeof (char **)); + } + + if (s) + (*iargv)[(*iargc)++] = xstrdup (s); + else + (*iargv)[*iargc] = NULL; /* not post-incremented on purpose! */ +} + + + +static void +run_add_arg (s) + const char *s; +{ + run_add_arg_p (&run_argc, &run_argc_allocated, &run_argv, s); +} + + + +int +run_exec (stin, stout, sterr, flags) + const char *stin; + const char *stout; + const 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 + cvs_outerr (server_active ? "S" : " ", 1); +#endif + cvs_outerr ("-> system(", 0); + run_print (stderr); + cvs_outerr (")\n", 0); + } + 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. */ + cvs_flushout(); + cvs_flusherr(); + + /* The output files, if any, are now created. Do the fork and dups. + + We use vfork not so much for a performance boost (the + performance boost, if any, is modest on most modern unices), + but for the sake of systems without a memory management unit, + which find it difficult or impossible to implement fork at all + (e.g. Amiga). The other solution is spawn (see + windows-NT/run.c). */ + +#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); + } + +#ifdef SETXID_SUPPORT + /* + ** This prevents a user from creating a privileged shell + ** from the text editor when the SETXID_SUPPORT option is selected. + */ + if (!strcmp (run_argv[0], Editor) && setegid (getgid ())) + { + error (0, errno, "cannot set egid to gid"); + _exit (127); + } +#endif + + /* 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); + else + /* ensure things are received by the parent in the correct order + * relative to the protocol pipe + */ + cvs_flusherr(); + out2: + if (stout) + (void) close (shout); + else + /* ensure things are received by the parent in the correct order + * relative to the protocol pipe + */ + cvs_flushout(); + out1: + if (stin) + (void) close (shin); + + out0: + if (rerrno) + errno = rerrno; + return rc; +} + + + +void +run_print (fp) + FILE *fp; +{ + int i; + void (*outfn) PROTO ((const char *, size_t)); + + if (fp == stderr) + outfn = cvs_outerr; + else if (fp == stdout) + outfn = cvs_output; + else + { + error (1, 0, "internal error: bad argument to run_print"); + /* Solely to placate gcc -Wall. + FIXME: it'd be better to use a function named `fatal' that + is known never to return. Then kludges wouldn't be necessary. */ + outfn = NULL; + } + + for (i = 0; i < run_argc; i++) + { + (*outfn) ("'", 1); + (*outfn) (run_argv[i], 0); + (*outfn) ("'", 1); + if (i != run_argc - 1) + (*outfn) (" ", 1); + } +} + +/* Return value is NULL for error, or if noexec was set. If there was an + error, return NULL and I'm not sure whether errno was set (the Red Hat + Linux 4.1 popen manpage was kind of vague but discouraging; and the noexec + case complicates this even aside from popen behavior). */ + +FILE * +run_popen (cmd, mode) + const char *cmd; + const char *mode; +{ + if (trace) + (void) fprintf (stderr, "%s-> run_popen(%s,%s)\n", + CLIENT_SERVER_STR, cmd, mode); + if (noexec) + return (NULL); + + return (popen (cmd, mode)); +} + + + +/* Work around an OpenSSH problem: it can put its standard file + descriptors into nonblocking mode, which will mess us up if we + share file descriptions with it. The simplest workaround is + to create an intervening process between OpenSSH and the + actual stderr. */ + +static void +work_around_openssh_glitch (void) +{ + pid_t pid; + int stderr_pipe[2]; + struct stat sb; + + /* Do nothing unless stderr is a file that is affected by + nonblocking mode. */ + if (!(fstat (STDERR_FILENO, &sb) == 0 + && (S_ISFIFO (sb.st_mode) || S_ISSOCK (sb.st_mode) + || S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode)))) + return; + + if (pipe (stderr_pipe) < 0) + error (1, errno, "cannot create pipe"); + pid = fork (); + if (pid < 0) + error (1, errno, "cannot fork"); + if (pid != 0) + { + /* Still in child of original process. Act like "cat -u". */ + char buf[1 << 13]; + ssize_t inbytes; + pid_t w; + int status; + + if (close (stderr_pipe[1]) < 0) + error (1, errno, "cannot close pipe"); + + while ((inbytes = read (stderr_pipe[0], buf, sizeof buf)) != 0) + { + size_t outbytes = 0; + + if (inbytes < 0) + { + if (errno == EINTR) + continue; + error (1, errno, "reading from pipe"); + } + + do + { + ssize_t w = write (STDERR_FILENO, + buf + outbytes, inbytes - outbytes); + if (w < 0) + { + if (errno == EINTR) + w = 0; + if (w < 0) + _exit (1); + } + outbytes += w; + } + while (inbytes != outbytes); + } + + /* Done processing output from grandchild. Propagate + its exit status back to the parent. */ + while ((w = waitpid (pid, &status, 0)) == -1 && errno == EINTR) + continue; + if (w < 0) + error (1, errno, "waiting for child"); + if (!WIFEXITED (status)) + { + if (WIFSIGNALED (status)) + raise (WTERMSIG (status)); + error (1, errno, "child did not exit cleanly"); + } + _exit (WEXITSTATUS (status)); + } + + /* Grandchild of original process. */ + if (close (stderr_pipe[0]) < 0) + error (1, errno, "cannot close pipe"); + + if (stderr_pipe[1] != STDERR_FILENO) + { + if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0) + error (1, errno, "cannot dup2 pipe"); + if (close (stderr_pipe[1]) < 0) + error (1, errno, "cannot close pipe"); + } +} + + + +int +piped_child (command, tofdp, fromfdp, fix_stderr) + const char **command; + int *tofdp; + int *fromfdp; + int fix_stderr; +{ + 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"); + +#ifdef USE_SETMODE_BINARY + setmode (to_child_pipe[0], O_BINARY); + setmode (to_child_pipe[1], O_BINARY); + setmode (from_child_pipe[0], O_BINARY); + setmode (from_child_pipe[1], O_BINARY); +#endif + + 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 pipe"); + if (close (to_child_pipe[1]) < 0) + error (1, errno, "cannot close pipe"); + if (close (from_child_pipe[0]) < 0) + error (1, errno, "cannot close pipe"); + if (dup2 (from_child_pipe[1], STDOUT_FILENO) < 0) + error (1, errno, "cannot dup2 pipe"); + + if (fix_stderr) + work_around_openssh_glitch (); + + /* Okay to cast out const below - execvp don't return nohow. */ + execvp ((char *)command[0], (char **)command); + error (1, errno, "cannot exec %s", command[0]); + } + if (close (to_child_pipe[0]) < 0) + error (1, errno, "cannot close pipe"); + if (close (from_child_pipe[1]) < 0) + error (1, errno, "cannot close pipe"); + + *tofdp = to_child_pipe[1]; + *fromfdp = from_child_pipe[0]; + return pid; +} + + +void +close_on_exec (fd) + int fd; +{ +#ifdef F_SETFD + if (fcntl (fd, F_SETFD, 1) == -1) + error (1, errno, "can't set close-on-exec flag on %d", fd); +#endif +} |