home *** CD-ROM | disk | FTP | other *** search
- /* 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. */
-
- /* 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"
-
- 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)
- (void) fprintf (stderr, "%s-> copy(%s,%s)\n",
- CLIENT_SERVER_STR, from, to);
- if (noexec)
- return;
-
- /* If the file to be copied is a link or a device, then just create
- the new link or device appropriately. */
- if (islink (from))
- {
- char *source = xreadlink (from);
- symlink (source, to);
- free (source);
- return;
- }
-
- if (isdevice (from))
- {
- #if defined(HAVE_MKNOD) && defined(HAVE_ST_RDEV)
- if (stat (from, &sb) < 0)
- error (1, errno, "cannot stat %s", from);
- mknod (to, sb.st_mode, sb.st_rdev);
- #else
- error (1, 0, "cannot copy device files on this system (%s)", from);
- #endif
- }
- else
- {
- /* Not a link or a device... probably a regular file. */
- if ((fdin = open (from, O_RDONLY, 0)) < 0) /* olsen */
- 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);
-
- #ifdef AMIGA
- (void) chmod (to, sb.st_mode | S_IREAD | S_IWRITE | S_IDELETE);
- #endif /* AMIGA */
- }
-
- /* 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 (CVS_LSTAT (file, &sb) < 0)
- return (0);
- return (S_ISLNK (sb.st_mode));
- #else
- return (0);
- #endif
- }
-
- /*
- * Returns non-zero if the argument file is a block or
- * character special device.
- */
- int
- isdevice (file)
- const char *file;
- {
- struct stat sb;
-
- if (CVS_LSTAT (file, &sb) < 0)
- return (0);
- #ifdef S_ISBLK
- if (S_ISBLK (sb.st_mode))
- return 1;
- #endif
- #ifdef S_ISCHR
- if (S_ISCHR (sb.st_mode))
- return 1;
- #endif
- return 0;
- }
-
- /*
- * 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);
- }
-
- /* Create directory NAME if it does not already exist; fatal error for
- other errors. Returns 0 if directory was created; 1 if it already
- existed. */
- int
- mkdir_if_needed (name)
- char *name;
- {
- if (mkdir (name, 0777) < 0)
- {
- if (!(errno == EEXIST
- || (errno == EACCES && isdir (name))))
- error (1, errno, "cannot make directory %s", name);
- return 1;
- }
- return 0;
- }
-
- /*
- * Change the mode of a file, either adding write permissions, or removing
- * all write permissions. Either change honors the current umask setting.
- *
- * Don't do anything if PreservePermissions is set to `yes'. This may
- * have unexpected consequences for some uses of xchmod.
- */
- void
- xchmod (fname, writable)
- char *fname;
- int writable;
- {
- struct stat sb;
- mode_t mode, oumask;
-
- if (preserve_perms)
- return;
-
- 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)
- (void) fprintf (stderr, "%s-> chmod(%s,%o)\n",
- CLIENT_SERVER_STR, fname,
- (unsigned int) 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)
- const char *from;
- const char *to;
- {
- if (trace)
- (void) fprintf (stderr, "%s-> rename(%s,%s)\n",
- CLIENT_SERVER_STR, from, to);
- if (noexec)
- return;
-
- if (rename (from, to) < 0)
- error (1, errno, "cannot rename file %s to %s", from, to);
- }
-
- /*
- * unlink a file, if possible.
- */
- int
- unlink_file (f)
- const char *f;
- {
- if (trace)
- (void) fprintf (stderr, "%s-> unlink(%s)\n",
- CLIENT_SERVER_STR, f);
- 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).
- */
- #ifndef AMIGA
- int
- unlink_file_dir (f)
- const char *f;
- {
- struct stat sb;
-
- if (trace
- #ifdef SERVER_SUPPORT
- /* This is called by the server parent process in contexts where
- it is not OK to send output (e.g. after we sent "ok" to the
- client). */
- && !server_active
- #endif
- )
- (void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f);
-
- 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 stat() to see if it is OK to call unlink(). This
- doesn't quite work--if someone creates a directory between the
- call to stat() and the call to unlink(), we'll still corrupt
- the filesystem. Where is the Unix Haters Handbook when you need
- it? */
- if (stat (f, &sb) < 0)
- {
- if (existence_error (errno))
- {
- /* The file or directory doesn't exist anyhow. */
- return -1;
- }
- }
- else if (S_ISDIR (sb.st_mode))
- return deep_remove_dir (f);
-
- return unlink (f);
- }
- #endif /* AMIGA */
-
- /* 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;
-
- if (rmdir (path) != 0)
- {
- if (errno == ENOTEMPTY
- || errno == EEXIST
- /* Ugly workaround for ugly AIX 4.1 (and 3.2) header bug
- (it defines ENOTEMPTY and EEXIST to 17 but actually
- returns 87). */
- || (ENOTEMPTY == 17 && EEXIST == 17 && errno == 87))
- {
- if ((dirp = opendir (path)) == NULL)
- /* If unable to open the directory return
- * an error
- */
- return -1;
-
- errno = 0;
- while ((dp = readdir (dirp)) != NULL)
- {
- char *buf;
-
- if (strcmp (dp->d_name, ".") == 0 ||
- strcmp (dp->d_name, "..") == 0)
- continue;
-
- buf = xmalloc (strlen (path) + strlen (dp->d_name) + 5);
- 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);
- free (buf);
- return -1;
- }
- }
- else
- {
- if (unlink (buf) != 0)
- {
- closedir(dirp);
- free (buf);
- return -1;
- }
- }
- free (buf);
-
- errno = 0;
- }
- if (errno != 0)
- {
- int save_errno = errno;
- closedir (dirp);
- errno = save_errno;
- return -1;
- }
- closedir (dirp);
- return rmdir (path);
- }
- else
- return -1;
- }
-
- /* 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 (size_t)(bp - buf); /* olsen */
- }
-
-
- /*
- * Compare "file1" to "file2". Return non-zero if they don't compare exactly.
- * If FILE1 and FILE2 are special files, compare their salient characteristics
- * (i.e. major/minor device numbers, links, etc.
- */
- int
- xcmp (file1, file2)
- const char *file1;
- const char *file2;
- {
- char *buf1, *buf2;
- struct stat sb1, sb2;
- int fd1, fd2;
- int ret;
-
- if (CVS_LSTAT (file1, &sb1) < 0)
- error (1, errno, "cannot lstat %s", file1);
- if (CVS_LSTAT (file2, &sb2) < 0)
- error (1, errno, "cannot lstat %s", file2);
-
- /* If FILE1 and FILE2 are not the same file type, they are unequal. */
- if ((sb1.st_mode & S_IFMT) != (sb2.st_mode & S_IFMT))
- return 1;
-
- /* If FILE1 and FILE2 are symlinks, they are equal if they point to
- the same thing. */
- if (S_ISLNK (sb1.st_mode) && S_ISLNK (sb2.st_mode))
- {
- int result;
- buf1 = xreadlink (file1);
- buf2 = xreadlink (file2);
- result = (strcmp (buf1, buf2) == 0);
- free (buf1);
- free (buf2);
- return result;
- }
-
- #ifndef AMIGA
- /* If FILE1 and FILE2 are devices, they are equal if their device
- numbers match. */
- if (S_ISBLK (sb1.st_mode) || S_ISCHR (sb1.st_mode))
- {
- #ifdef HAVE_ST_RDEV
- if (sb1.st_rdev == sb2.st_rdev)
- return 0;
- else
- return 1;
- #else
- error (1, 0, "cannot compare device files on this system (%s and %s)",
- file1, file2);
- #endif
- }
- #endif /* AMIGA */
-
- if ((fd1 = open (file1, O_RDONLY, 0)) < 0) /* olsen */
- error (1, errno, "cannot open file %s for comparing", file1);
- if ((fd2 = open (file2, O_RDONLY, 0)) < 0) /* olsen */
- error (1, errno, "cannot open file %s for comparing", 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);
- }
-
- #ifndef AMIGA
- /* Generate a unique temporary filename. Returns a pointer to a newly
- malloc'd string containing the name. Returns successfully or not at
- all. */
- /* There are at least three functions for generating temporary
- filenames. We use tempnam (SVID 3) if possible, else mktemp (BSD
- 4.3), and as last resort tmpnam (POSIX). Reason is that tempnam and
- mktemp both allow to specify the directory in which the temporary
- file will be created. */
- #ifdef HAVE_TEMPNAM
- char *
- cvs_temp_name ()
- {
- char *retval;
-
- retval = tempnam (Tmpdir, "cvs");
- if (retval == NULL)
- error (1, errno, "cannot generate temporary filename");
- /* tempnam returns a pointer to a newly malloc'd string, so there's
- no need for a xstrdup */
- return retval;
- }
- #else
- char *
- cvs_temp_name ()
- {
- # ifdef HAVE_MKTEMP
- char *value;
- char *retval;
- char *fmt; /* olsen */
-
- value = xmalloc (strlen (Tmpdir) + 40);
- fmt = "%s/%s"; /* olsen */
-
- #ifdef AMIGA
- {
- int len;
-
- len = strlen(Tmpdir);
- if(len > 0 && (Tmpdir[len-1] != ':' && Tmpdir[len-1] != '/'))
- fmt = "%s/%s";
- else
- fmt = "%s%s";
- }
- #endif /* AMIGA */
-
- sprintf (value, fmt, Tmpdir, "cvsXXXXXX" ); /* olsen */
- retval = mktemp (value);
-
- if (retval == NULL)
- error (1, errno, "cannot generate temporary filename");
- return value;
- # else
- char value[L_tmpnam + 1];
- char *retval;
-
- retval = tmpnam (value);
- if (retval == NULL)
- error (1, errno, "cannot generate temporary filename");
- return xstrdup (value);
- # endif
- }
- #endif
- #endif /* AMIGA */
-
- #ifndef AMIGA
- /* 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] == '/';
- }
- #endif /* AMIGA */
-
- /*
- * Return a string (dynamically allocated) with the name of the file to which
- * LINK is symlinked.
- */
- char *
- xreadlink (link)
- const char *link;
- {
- char *file = NULL;
- char *tfile;
- int buflen = 128;
- int link_name_len;
-
- if (!islink (link))
- return NULL;
-
- /* Get the name of the file to which `from' is linked.
- FIXME: what portability issues arise here? Are readlink &
- ENAMETOOLONG defined on all systems? -twp */
- do
- {
- file = xrealloc (file, buflen);
- link_name_len = readlink (link, file, buflen - 1);
- buflen *= 2;
- }
- while (link_name_len < 0 && errno == ENAMETOOLONG);
-
- if (link_name_len < 0)
- error (1, errno, "cannot readlink %s", link);
-
- file[link_name_len] = '\0';
-
- tfile = xstrdup (file);
- free (file);
-
- return tfile;
- }
-
- #ifndef AMIGA
- /* Return a pointer into PATH's last component. */
- char *
- last_component (path)
- char *path;
- {
- char *last = strrchr (path, '/');
-
- if (last && (last != path))
- return last + 1;
- else
- return path;
- }
- #endif /* AMIGA */
-
- /* Return the home directory. Returns a pointer to storage
- managed by this function or its callees (currently getenv).
- This function will return the same thing every time it is
- called. Returns NULL if there is no home directory.
-
- Note that for a pserver server, this may return root's home
- directory. What typically happens is that upon being started from
- inetd, before switching users, the code in cvsrc.c calls
- get_homedir which remembers root's home directory in the static
- variable. Then the switch happens and get_homedir might return a
- directory that we don't even have read or execute permissions for
- (which is bad, when various parts of CVS try to read there). One
- fix would be to make the value returned by get_homedir only good
- until the next call (which would free the old value). Another fix
- would be to just always malloc our answer, and let the caller free
- it (that is best, because some day we may need to be reentrant).
-
- The workaround is to put -f in inetd.conf which means that
- get_homedir won't get called until after the switch in user ID.
-
- The whole concept of a "home directory" on the server is pretty
- iffy, although I suppose some people probably are relying on it for
- .cvsrc and such, in the cases where it works. */
- char *
- get_homedir ()
- {
- static char *home = NULL;
- char *env = getenv ("HOME");
- struct passwd *pw;
-
- if (home != NULL)
- return home;
-
- if (env)
- home = env;
- else if ((pw = (struct passwd *) getpwuid (getuid ()))
- && pw->pw_dir)
- home = xstrdup (pw->pw_dir);
- else
- return 0;
-
- return home;
- }
-
- #ifndef AMIGA
- /* 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]);
- }
- #endif /* AMIGA */
-
- #ifdef SERVER_SUPPORT
- /* Case-insensitive string compare. I know that some systems
- have such a routine, but I'm not sure I see any reasons for
- dealing with the hair of figuring out whether they do (I haven't
- looked into whether this is a performance bottleneck; I would guess
- not). */
- int
- cvs_casecmp (str1, str2)
- char *str1;
- char *str2;
- {
- char *p;
- char *q;
- int pqdiff;
-
- p = str1;
- q = str2;
- while ((pqdiff = tolower (*p) - tolower (*q)) == 0)
- {
- if (*p == '\0')
- return 0;
- ++p;
- ++q;
- }
- return pqdiff;
- }
-
- /* Case-insensitive file open. As you can see, this is an expensive
- call. We don't regard it as our main strategy for dealing with
- case-insensitivity. Returns errno code or 0 for success. Puts the
- new file in *FP. NAME and MODE are as for fopen. If PATHP is not
- NULL, then put a malloc'd string containing the pathname as found
- into *PATHP. *PATHP is only set if the return value is 0.
-
- Might be cleaner to separate the file finding (which just gives
- *PATHP) from the file opening (which the caller can do). For one
- thing, might make it easier to know whether to put NAME or *PATHP
- into error messages. */
- int
- fopen_case (name, mode, fp, pathp)
- char *name;
- char *mode;
- FILE **fp;
- char **pathp;
- {
- struct dirent *dp;
- DIR *dirp;
- char *dir;
- char *fname;
- char *found_name;
- int retval;
-
- /* Separate NAME into directory DIR and filename within the directory
- FNAME. */
- dir = xstrdup (name);
- fname = strrchr (dir, '/');
- if (fname == NULL)
- error (1, 0, "internal error: relative pathname in fopen_case");
- *fname++ = '\0';
-
- found_name = NULL;
- dirp = CVS_OPENDIR (dir);
- if (dirp == NULL)
- {
- if (existence_error (errno))
- {
- /* This can happen if we are looking in the Attic and the Attic
- directory does not exist. Return the error to the caller;
- they know what to do with it. */
- retval = errno;
- goto out;
- }
- else
- {
- /* Give a fatal error; that way the error message can be
- more specific than if we returned the error to the caller. */
- error (1, errno, "cannot read directory %s", dir);
- }
- }
- errno = 0;
- while ((dp = readdir (dirp)) != NULL)
- {
- if (cvs_casecmp (dp->d_name, fname) == 0)
- {
- if (found_name != NULL)
- error (1, 0, "%s is ambiguous; could mean %s or %s",
- fname, dp->d_name, found_name);
- found_name = xstrdup (dp->d_name);
- }
- }
- if (errno != 0)
- error (1, errno, "cannot read directory %s", dir);
- closedir (dirp);
-
- if (found_name == NULL)
- {
- *fp = NULL;
- retval = ENOENT;
- }
- else
- {
- char *p;
-
- /* Copy the found name back into DIR. We are assuming that
- found_name is the same length as fname, which is true as
- long as the above code is just ignoring case and not other
- aspects of filename syntax. */
- p = dir + strlen (dir);
- *p++ = '/';
- strcpy (p, found_name);
- *fp = fopen (dir, mode);
- if (*fp == NULL)
- retval = errno;
- else
- retval = 0;
- }
-
- if (pathp == NULL)
- free (dir);
- else if (retval != 0)
- free (dir);
- else
- *pathp = dir;
- free (found_name);
- out:
- return retval;
- }
- #endif /* SERVER_SUPPORT */
-