diff options
Diffstat (limited to 'contrib/cvs/src/filesubr.c')
-rw-r--r-- | contrib/cvs/src/filesubr.c | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/contrib/cvs/src/filesubr.c b/contrib/cvs/src/filesubr.c new file mode 100644 index 0000000..086da83 --- /dev/null +++ b/contrib/cvs/src/filesubr.c @@ -0,0 +1,662 @@ +/* filesubr.c --- subroutines for dealing with files + Jim Blandy <jimb@cyclic.com> + + 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. */ + +/* These functions were moved out of subr.c because they need different + definitions under operating systems (like, say, Windows NT) with different + file system semantics. */ + +#include "cvs.h" + +/* + * I don't know of a convenient way to test this at configure time, or else + * I'd certainly do it there. + */ +#if defined(NeXT) +#define LOSING_TMPNAM_FUNCTION +#endif + +static int deep_remove_dir PROTO((const char *path)); + +/* + * Copies "from" to "to". + */ +void +copy_file (from, to) + const char *from; + const char *to; +{ + struct stat sb; + struct utimbuf t; + int fdin, fdout; + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> copy(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> copy(%s,%s)\n", from, to); +#endif + 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) + { + char buf[BUFSIZ]; + int n; + + for (;;) + { + n = read (fdin, buf, sizeof(buf)); + if (n == -1) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + error (1, errno, "cannot read file %s for copying", from); + } + else if (n == 0) + break; + + if (write(fdout, buf, n) != n) { + error (1, errno, "cannot write file %s for copying", to); + } + } + +#ifdef HAVE_FSYNC + if (fsync (fdout)) + error (1, errno, "cannot fsync file %s after copying", to); +#endif + } + + if (close (fdin) < 0) + error (0, errno, "cannot close %s", from); + 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 */ + memset ((char *) &t, 0, sizeof (t)); + t.actime = sb.st_atime; + t.modtime = sb.st_mtime; + (void) utime (to, &t); +} + +/* FIXME-krp: these functions would benefit from caching the char * & + stat buf. */ + +/* + * Returns non-zero if the argument file is a directory, or is a symbolic + * link which points to a directory. + */ +int +isdir (file) + const 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) + const 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) + const char *file; +{ + return isaccessible(file, F_OK); +} + +/* + * Returns non-zero if the argument file is readable. + */ +int +isreadable (file) + const char *file; +{ + return isaccessible(file, R_OK); +} + +/* + * Returns non-zero if the argument file is writable. + */ +int +iswritable (file) + const char *file; +{ + return isaccessible(file, W_OK); +} + +/* + * Returns non-zero if the argument file is accessable according to + * mode. If compiled with SETXID_SUPPORT also works if cvs has setxid + * bits set. + */ +int +isaccessible (file, mode) + const char *file; + const int mode; +{ +#ifdef SETXID_SUPPORT + struct stat sb; + int umask = 0; + int gmask = 0; + int omask = 0; + int uid; + + if (stat(file, &sb) == -1) + return 0; + if (mode == F_OK) + return 1; + + uid = geteuid(); + if (uid == 0) /* superuser */ + { + if (mode & X_OK) + return sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH); + else + return 1; + } + + if (mode & R_OK) + { + umask |= S_IRUSR; + gmask |= S_IRGRP; + omask |= S_IROTH; + } + if (mode & W_OK) + { + umask |= S_IWUSR; + gmask |= S_IWGRP; + omask |= S_IWOTH; + } + if (mode & X_OK) + { + umask |= S_IXUSR; + gmask |= S_IXGRP; + omask |= S_IXOTH; + } + + if (sb.st_uid == uid) + return (sb.st_mode & umask) == umask; + else if (sb.st_gid == getegid()) + return (sb.st_mode & gmask) == gmask; + else + return (sb.st_mode & omask) == omask; +#else + return access(file, mode) == 0; +#endif +} + +/* + * Open a file and die if it fails + */ +FILE * +open_file (name, mode) + const char *name; + const char *mode; +{ + FILE *fp; + + if ((fp = fopen (name, mode)) == NULL) + error (1, errno, "cannot open %s", name); + return (fp); +} + +/* + * Make a directory and die if it fails + */ +void +make_directory (name) + const char *name; +{ + struct stat sb; + + if (stat (name, &sb) == 0 && (!S_ISDIR (sb.st_mode))) + 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) + const char *name; +{ + char *cp; + + if (noexec) + return; + + if (mkdir (name, 0777) == 0 || errno == EEXIST) + return; + if (! existence_error (errno)) + { + error (0, errno, "cannot make path to %s", name); + return; + } + if ((cp = strrchr (name, '/')) == NULL) + return; + *cp = '\0'; + make_directories (name); + *cp++ = '/'; + if (*cp == '\0') + return; + (void) mkdir (name, 0777); +} + +/* + * Change the mode of a file, either adding write permissions, or removing + * all write permissions. Either change honors the current umask setting. + */ +void +xchmod (fname, writable) + char *fname; + int writable; +{ + struct stat sb; + mode_t mode, oumask; + + if (stat (fname, &sb) < 0) + { + if (!noexec) + error (0, errno, "cannot stat %s", fname); + return; + } + oumask = umask (0); + (void) umask (oumask); + if (writable) + { + mode = sb.st_mode | (~oumask + & (((sb.st_mode & S_IRUSR) ? S_IWUSR : 0) + | ((sb.st_mode & S_IRGRP) ? S_IWGRP : 0) + | ((sb.st_mode & S_IROTH) ? S_IWOTH : 0))); + } + else + { + mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask; + } + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> chmod(%s,%o)\n", + (server_active) ? 'S' : ' ', fname, + (unsigned int) mode); +#else + (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname, + (unsigned int) mode); +#endif + 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) + const char *from; + const char *to; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> rename(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> rename(%s,%s)\n", from, to); +#endif + if (noexec) + return; + + if (rename (from, to) < 0) + error (1, errno, "cannot rename file %s to %s", from, to); +} + +/* + * link a file, if possible. Warning: the Windows NT version of this + * function just copies the file, so only use this function in ways + * that can deal with either a link or a copy. + */ +int +link_file (from, to) + const char *from; + const char *to; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> link(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> link(%s,%s)\n", from, to); +#endif + if (noexec) + return (0); + + return (link (from, to)); +} + +/* + * unlink a file, if possible. + */ +int +unlink_file (f) + const char *f; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> unlink(%s)\n", + (server_active) ? 'S' : ' ', f); +#else + (void) fprintf (stderr, "-> unlink(%s)\n", f); +#endif + if (noexec) + return (0); + + return (unlink (f)); +} + +/* + * Unlink a file or dir, if possible. If it is a directory do a deep + * removal of all of the files in the directory. Return -1 on error + * (in which case errno is set). + */ +int +unlink_file_dir (f) + const char *f; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n", + (server_active) ? 'S' : ' ', f); +#else + (void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f); +#endif + if (noexec) + return (0); + + /* For at least some unices, if root tries to unlink() a directory, + instead of doing something rational like returning EISDIR, + the system will gleefully go ahead and corrupt the filesystem. + So we first call isdir() to see if it is OK to call unlink(). This + doesn't quite work--if someone creates a directory between the + call to isdir() and the call to unlink(), we'll still corrupt + the filesystem. Where is the Unix Haters Handbook when you need + it? */ + if (isdir(f)) + return deep_remove_dir(f); + else + { + if (unlink (f) != 0) + return -1; + } + /* We were able to remove the file from the disk */ + return 0; +} + +/* Remove a directory and everything it contains. Returns 0 for + * success, -1 for failure (in which case errno is set). + */ + +static int +deep_remove_dir (path) + const char *path; +{ + DIR *dirp; + struct dirent *dp; + char buf[PATH_MAX]; + + if (rmdir (path) != 0 && (errno == ENOTEMPTY || errno == EEXIST)) + { + if ((dirp = opendir (path)) == NULL) + /* If unable to open the directory return + * an error + */ + return -1; + + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") == 0 || + strcmp (dp->d_name, "..") == 0) + continue; + + sprintf (buf, "%s/%s", path, dp->d_name); + + /* See comment in unlink_file_dir explanation of why we use + isdir instead of just calling unlink and checking the + status. */ + if (isdir(buf)) + { + if (deep_remove_dir(buf)) + { + closedir(dirp); + return -1; + } + } + else + { + if (unlink (buf) != 0) + { + closedir(dirp); + return -1; + } + } + } + closedir (dirp); + return rmdir (path); + } + + /* Was able to remove the directory return 0 */ + return 0; +} + +/* Read NCHARS bytes from descriptor FD into BUF. + Return the number of characters successfully read. + The number returned is always NCHARS unless end-of-file or error. */ +static size_t +block_read (fd, buf, nchars) + int fd; + char *buf; + size_t nchars; +{ + char *bp = buf; + size_t nread; + + do + { + nread = read (fd, bp, nchars); + if (nread == (size_t)-1) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + return (size_t)-1; + } + + if (nread == 0) + break; + + bp += nread; + nchars -= nread; + } while (nchars != 0); + + return bp - buf; +} + + +/* + * Compare "file1" to "file2". Return non-zero if they don't compare exactly. + */ +int +xcmp (file1, file2) + const char *file1; + const char *file2; +{ + char *buf1, *buf2; + struct stat sb1, sb2; + int fd1, fd2; + int ret; + + 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, &sb1) < 0) + error (1, errno, "cannot fstat %s", file1); + if (fstat (fd2, &sb2) < 0) + error (1, errno, "cannot fstat %s", file2); + + /* A generic file compare routine might compare st_dev & st_ino here + to see if the two files being compared are actually the same file. + But that won't happen in CVS, so we won't bother. */ + + if (sb1.st_size != sb2.st_size) + ret = 1; + else if (sb1.st_size == 0) + ret = 0; + else + { + /* FIXME: compute the optimal buffer size by computing the least + common multiple of the files st_blocks field */ + size_t buf_size = 8 * 1024; + size_t read1; + size_t read2; + + buf1 = xmalloc (buf_size); + buf2 = xmalloc (buf_size); + + do + { + read1 = block_read (fd1, buf1, buf_size); + if (read1 == (size_t)-1) + error (1, errno, "cannot read file %s for comparing", file1); + + read2 = block_read (fd2, buf2, buf_size); + if (read2 == (size_t)-1) + error (1, errno, "cannot read file %s for comparing", file2); + + /* assert (read1 == read2); */ + + ret = memcmp(buf1, buf2, read1); + } while (ret == 0 && read1 == buf_size); + + free (buf1); + free (buf2); + } + + (void) close (fd1); + (void) close (fd2); + return (ret); +} + +#ifdef LOSING_TMPNAM_FUNCTION +char *tmpnam(char *s) +{ + static char value[L_tmpnam+1]; + + if (s){ + strcpy(s,"/tmp/cvsXXXXXX"); + mktemp(s); + return s; + }else{ + strcpy(value,"/tmp/cvsXXXXXX"); + mktemp(s); + return value; + } +} +#endif + +/* Return non-zero iff FILENAME is absolute. + Trivial under Unix, but more complicated under other systems. */ +int +isabsolute (filename) + const char *filename; +{ + return filename[0] == '/'; +} + + +/* Return a pointer into PATH's last component. */ +char * +last_component (path) + char *path; +{ + char *last = strrchr (path, '/'); + + if (last) + return last + 1; + else + return path; +} + +/* Return the home directory. Returns a pointer to storage + managed by this function or its callees (currently getenv). */ +char * +get_homedir () +{ + return getenv ("HOME"); +} + +/* See cvs.h for description. On unix this does nothing, because the + shell expands the wildcards. */ +void +expand_wild (argc, argv, pargc, pargv) + int argc; + char **argv; + int *pargc; + char ***pargv; +{ + int i; + *pargc = argc; + *pargv = (char **) xmalloc (argc * sizeof (char *)); + for (i = 0; i < argc; ++i) + (*pargv)[i] = xstrdup (argv[i]); +} |