/* filesubr.c --- subroutines for dealing with files Jim Blandy 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" #ifndef lint static const char rcsid[] = "$CVSid:$"; USE(rcsid); #endif /* * 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, mode); #else (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname, 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. */ 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); if (unlink (f) != 0) { /* under NEXTSTEP errno is set to return EPERM if * the file is a directory,or if the user is not * allowed to read or write to the file. * [This is probably a bug in the O/S] * other systems will return EISDIR to indicate * that the path is a directory. */ if (errno == EISDIR || errno == EPERM) return deep_remove_dir (f); else /* The file wasn't a directory and some other * error occured */ 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 ) { 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); if (unlink (buf) != 0 ) { if (errno == EISDIR || errno == EPERM) { if (deep_remove_dir (buf)) { closedir (dirp); return -1; } } else { /* buf isn't a directory, or there are * some sort of permision problems */ 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; }