/* * Copyright (c) 1992, Brian Berliner and Jeff Polk * Copyright (c) 1989-1992, Brian Berliner * * You may distribute under the terms of the GNU General Public License as * specified in the README file that comes with the CVS 1.3 kit. * * Various useful functions for the CVS support code. */ #include "cvs.h" #ifdef _MINIX #undef POSIX /* Minix 1.6 doesn't support POSIX.1 sigaction yet */ #endif #ifndef VPRINTF_MISSING #if __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 #ifndef lint static char rcsid[] = "@(#)subr.c 1.52 92/03/31"; #endif #if __STDC__ static void run_add_arg (char *s); static void run_init_prog (void); #else static void run_add_arg (); static void run_init_prog (); #endif /* __STDC__ */ extern char *getlogin (); extern char *strtok (); /* * Copies "from" to "to". mallocs a buffer large enough to hold the entire * file and does one read/one write to do the copy. This is reasonable, * since source files are typically not too large. */ void copy_file (from, to) char *from; char *to; { struct stat sb; struct utimbuf t; int fdin, fdout; char *buf; if (trace) (void) fprintf (stderr, "-> copy(%s,%s)\n", from, to); if (noexec) return; if ((fdin = open (from, O_RDONLY)) < 0) error (1, errno, "cannot open %s for copying", from); if (fstat (fdin, &sb) < 0) error (1, errno, "cannot fstat %s", from); if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0) error (1, errno, "cannot create %s for copying", to); if (sb.st_size > 0) { buf = xmalloc ((int) sb.st_size); if (read (fdin, buf, (int) sb.st_size) != (int) sb.st_size) error (1, errno, "cannot read file %s for copying", from); if (write (fdout, buf, (int) sb.st_size) != (int) sb.st_size #ifndef FSYNC_MISSING || fsync (fdout) == -1 #endif ) { error (1, errno, "cannot write file %s for copying", to); } free (buf); } (void) close (fdin); if (close (fdout) < 0) error (1, errno, "cannot close %s", to); /* now, set the times for the copied file to match those of the original */ t.actime = sb.st_atime; t.modtime = sb.st_mtime; (void) utime (to, &t); } /* * Returns non-zero if the argument file is a directory, or is a symbolic * link which points to a directory. */ int isdir (file) char *file; { struct stat sb; if (stat (file, &sb) < 0) return (0); return (S_ISDIR (sb.st_mode)); } /* * Returns non-zero if the argument file is a symbolic link. */ int islink (file) char *file; { #ifdef S_ISLNK struct stat sb; if (lstat (file, &sb) < 0) return (0); return (S_ISLNK (sb.st_mode)); #else return (0); #endif } /* * Returns non-zero if the argument file exists. */ int isfile (file) char *file; { struct stat sb; if (stat (file, &sb) < 0) return (0); return (1); } /* * Returns non-zero if the argument file is readable. * XXX - must be careful if "cvs" is ever made setuid! */ int isreadable (file) char *file; { return (access (file, R_OK) != -1); } /* * Returns non-zero if the argument file is writable * XXX - muct be careful if "cvs" is ever made setuid! */ int iswritable (file) char *file; { return (access (file, W_OK) != -1); } /* * Open a file and die if it fails */ FILE * open_file (name, mode) char *name; char *mode; { FILE *fp; if ((fp = fopen (name, mode)) == NULL) error (1, errno, "cannot open %s", name); return (fp); } /* * Open a file if allowed and return. */ FILE * Fopen (name, mode) char *name; char *mode; { if (trace) (void) fprintf (stderr, "-> fopen(%s,%s)\n", name, mode); if (noexec) return (NULL); return (fopen (name, mode)); } /* * Make a directory and die if it fails */ void make_directory (name) char *name; { struct stat buf; if (stat (name, &buf) == 0) { if (S_ISDIR (buf.st_mode)) { if (access (name, (R_OK | W_OK | X_OK)) == 0) { error (0, 0, "Directory %s already exists", name); return; } else { error (0, 0, "Directory %s already exists but is protected from you", name); } } else error (0, 0, "%s already exists but is not a directory", name); } if (!noexec && mkdir (name, 0777) < 0) error (1, errno, "cannot make directory %s", name); } /* * Make a path to the argument directory, printing a message if something * goes wrong. */ void make_directories (name) char *name; { char *cp; if (noexec) return; if (mkdir (name, 0777) == 0 || errno == EEXIST) return; if (errno != ENOENT) { error (0, errno, "cannot make path to %s", name); return; } if ((cp = rindex (name, '/')) == NULL) return; *cp = '\0'; make_directories (name); *cp++ = '/'; if (*cp == '\0') return; (void) mkdir (name, 0777); } /* * malloc some data and die if it fails */ char * xmalloc (bytes) int bytes; { char *cp; if (bytes <= 0) error (1, 0, "bad malloc size %d", bytes); if ((cp = malloc ((unsigned) bytes)) == NULL) error (1, 0, "malloc failed"); return (cp); } /* * realloc data and die if it fails [I've always wanted to have "realloc" do * a "malloc" if the argument is NULL, but you can't depend on it. Here, I * can *force* it. */ char * xrealloc (ptr, bytes) char *ptr; int bytes; { char *cp; if (!ptr) return (xmalloc (bytes)); if (bytes <= 0) error (1, 0, "bad realloc size %d", bytes); if ((cp = realloc (ptr, (unsigned) bytes)) == NULL) error (1, 0, "realloc failed"); return (cp); } /* * Duplicate a string, calling xmalloc to allocate some dynamic space */ char * xstrdup (str) char *str; { char *s; if (str == NULL) return ((char *) NULL); s = xmalloc (strlen (str) + 1); (void) strcpy (s, str); return (s); } /* * Change the mode of a file, either adding write permissions, or removing * all write permissions. Adding write permissions honors the current umask * setting. */ void xchmod (fname, writable) char *fname; int writable; { struct stat sb; int mode, oumask; if (stat (fname, &sb) < 0) { if (!noexec) error (0, errno, "cannot stat %s", fname); return; } if (writable) { oumask = umask (0); (void) umask (oumask); mode = sb.st_mode | ((S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask); } else { mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH); } if (trace) (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname, mode); if (noexec) return; if (chmod (fname, mode) < 0) error (0, errno, "cannot change mode of file %s", fname); } /* * Rename a file and die if it fails */ void rename_file (from, to) char *from; char *to; { if (trace) (void) fprintf (stderr, "-> rename(%s,%s)\n", from, to); if (noexec) return; if (rename (from, to) < 0) error (1, errno, "cannot rename file %s to %s", from, to); } /* * link a file, if possible. */ int link_file (from, to) char *from, *to; { if (trace) (void) fprintf (stderr, "-> link(%s,%s)\n", from, to); if (noexec) return (0); return (link (from, to)); } /* * unlink a file, if possible. */ int unlink_file (f) char *f; { if (trace) (void) fprintf (stderr, "-> unlink(%s)\n", f); if (noexec) return (0); return (unlink (f)); } /* * Compare "file1" to "file2". Return non-zero if they don't compare exactly. * * mallocs a buffer large enough to hold the entire file and does two reads to * load the buffer and calls bcmp to do the cmp. This is reasonable, since * source files are typically not too large. */ int xcmp (file1, file2) char *file1; char *file2; { register char *buf1, *buf2; struct stat sb; off_t size; int ret, fd1, fd2; if ((fd1 = open (file1, O_RDONLY)) < 0) error (1, errno, "cannot open file %s for comparing", file1); if ((fd2 = open (file2, O_RDONLY)) < 0) error (1, errno, "cannot open file %s for comparing", file2); if (fstat (fd1, &sb) < 0) error (1, errno, "cannot fstat %s", file1); size = sb.st_size; if (fstat (fd2, &sb) < 0) error (1, errno, "cannot fstat %s", file2); if (size == sb.st_size) { if (size == 0) ret = 0; else { buf1 = xmalloc ((int) size); buf2 = xmalloc ((int) size); if (read (fd1, buf1, (int) size) != (int) size) error (1, errno, "cannot read file %s cor comparing", file1); if (read (fd2, buf2, (int) size) != (int) size) error (1, errno, "cannot read file %s for comparing", file2); ret = bcmp (buf1, buf2, (int) size); free (buf1); free (buf2); } } else ret = 1; (void) close (fd1); (void) close (fd2); return (ret); } /* * Recover the space allocated by Find_Names() and line2argv() */ void free_names (pargc, argv) int *pargc; char *argv[]; { register int i; for (i = 0; i < *pargc; i++) { /* only do through *pargc */ free (argv[i]); } *pargc = 0; /* and set it to zero when done */ } /* * Convert a line into argc/argv components and return the result in the * arguments as passed. Use free_names() to return the memory allocated here * back to the free pool. */ void line2argv (pargc, argv, line) int *pargc; char *argv[]; char *line; { char *cp; *pargc = 0; for (cp = strtok (line, " \t"); cp; cp = strtok ((char *) NULL, " \t")) { argv[*pargc] = xstrdup (cp); (*pargc)++; } } /* * Returns the number of dots ('.') found in an RCS revision number */ int numdots (s) char *s; { char *cp; int dots = 0; for (cp = s; *cp; cp++) { if (*cp == '.') dots++; } return (dots); } /* * Get the caller's login from his uid. If the real uid is "root" try LOGNAME * USER or getlogin(). If getlogin() and getpwuid() both fail, return * the uid as a string. */ char * getcaller () { static char uidname[20]; struct passwd *pw; char *name; int uid; uid = getuid (); if (uid == 0) { /* super-user; try getlogin() to distinguish */ if (((name = getenv("LOGNAME")) || (name = getenv("USER")) || (name = getlogin ())) && *name) return (name); } if ((pw = (struct passwd *) getpwuid (uid)) == NULL) { (void) sprintf (uidname, "uid%d", uid); return (uidname); } return (pw->pw_name); } /* * 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 (VPRINTF_MISSING) && __STDC__ void run_setup (char *fmt,...) #else void run_setup (fmt, va_alist) char *fmt; va_dcl #endif { #ifndef VPRINTF_MISSING 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 */ #ifndef VPRINTF_MISSING 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) char *s; { run_add_arg (s); } /* VARARGS */ #if !defined (VPRINTF_MISSING) && __STDC__ void run_args (char *fmt,...) #else void run_args (fmt, va_alist) char *fmt; va_dcl #endif { #ifndef VPRINTF_MISSING va_list args; #endif run_init_prog (); /* process the varargs into run_prog */ #ifndef VPRINTF_MISSING 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) 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 = -1; int rerrno = 0; int pid, w; #ifdef POSIX sigset_t sigset_mask, sigset_omask; struct sigaction act, iact, qact; #else #ifdef BSD_SIGNALS int mask; struct sigvec vec, ivec, qvec; #else SIGTYPE (*istat) (), (*qstat) (); #endif #endif if (trace) { (void) fprintf (stderr, "-> system("); 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; } } /* The output files, if any, are now created. Do the fork and dups */ #ifdef VFORK_MISSING pid = fork (); #else pid = vfork (); #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); _exit (127); } else if (pid == -1) { rerrno = errno; goto out; } /* the parent. Ignore some signals for now */ #ifdef POSIX 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) { bzero ((char *) &vec, 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 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) { status = -1; rerrno = errno; } else if (WIFEXITED (status)) status = WEXITSTATUS (status); else if (WIFSIGNALED (status)) { if (WTERMSIG (status) == SIGPIPE) error (1, 0, "broken pipe"); status = 2; } else status = 1; /* restore the signals */ #ifdef POSIX 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 (status); } 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 * Popen (cmd, mode) char *cmd, *mode; { if (trace) (void) fprintf (stderr, "-> Popen(%s,%s)\n", cmd, mode); if (noexec) return (NULL); return (popen (cmd, mode)); } #ifdef lint #ifndef __GNUC__ /* ARGSUSED */ time_t get_date (date, now) char *date; struct timeb *now; { time_t foo = 0; return (foo); } #endif #endif