/* $OpenBSD: filesys.c,v 1.11 2009/10/27 23:59:42 deraadt Exp $ */ /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "defs.h" /* * This file contains functions dealing with getting info * about mounted filesystems. */ jmp_buf env; /* * Given a pathname, find the fullest component that exists. * If statbuf is not NULL, set it to point at our stat buffer. */ char * find_file(char *pathname, struct stat *statbuf, int *isvalid) { static char last_pathname[MAXPATHLEN]; static char file[MAXPATHLEN + 3]; static struct stat filestat; char *p; /* * Mark the statbuf as invalid to start with. */ *isvalid = 0; /* * If this is the same pathname as the last time, and * the file buffer is valid and we're doing the same stat() * or lstat(), then set statbuf to the last filestat and * return the last file we found. */ if (strcmp(pathname, last_pathname) == 0 && file[0]) { if (statbuf) statbuf = &filestat; if (strcmp(pathname, file) == 0) *isvalid = 1; return(file); } if (strlen(pathname) > sizeof(file) + 3) { error("%s: Name to large for buffer.", pathname); return(NULL); } /* * Save for next time */ (void) strlcpy(last_pathname, pathname, sizeof(last_pathname)); if (*pathname == '/') (void) strlcpy(file, pathname, sizeof(file)); else { /* * Ensure we have a directory (".") in our path * so we have something to stat in case the file * does not exist. */ (void) strlcpy(file, "./", sizeof(file)); (void) strlcat(file, pathname, sizeof(file)); } while (lstat(file, &filestat) != 0) { /* * Trim the last part of the pathname to try next level up */ if (errno == ENOENT) { /* * Trim file name to get directory name. * Normally we want to change /dir1/dir2/file * into "/dir1/dir2/." */ if ((p = (char *) strrchr(file, '/')) != NULL) { if (strcmp(p, "/.") == 0) { *p = CNULL; } else { *++p = '.'; *++p = CNULL; } } else { /* * Couldn't find anything, so give up. */ debugmsg(DM_MISC, "Cannot find dir of `%s'", pathname); return(NULL); } continue; } else { error("%s: lstat failed: %s", pathname, SYSERR); return(NULL); } } if (statbuf) bcopy((char *) &filestat, (char *) statbuf, sizeof(filestat)); /* * Trim the "/." that we added. */ p = &file[strlen(file) - 1]; if (*p == '.') *p-- = CNULL; for ( ; p && *p && *p == '/' && p != file; --p) *p = CNULL; /* * If this file is a symlink we really want the parent directory * name in case the symlink points to another filesystem. */ if (S_ISLNK(filestat.st_mode)) if ((p = (char *) strrchr(file, '/')) && *p+1) { /* Is this / (root)? */ if (p == file) file[1] = CNULL; else *p = CNULL; } if (strcmp(pathname, file) == 0) *isvalid = 1; return((file && *file) ? file : NULL); } #if defined(NFS_CHECK) || defined(RO_CHECK) /* * Find the device that "filest" is on in the "mntinfo" linked list. */ mntent_t * findmnt(struct stat *filest, struct mntinfo *mntinfo) { struct mntinfo *mi; for (mi = mntinfo; mi; mi = mi->mi_nxt) { if (mi->mi_mnt->me_flags & MEFLAG_IGNORE) continue; if (filest->st_dev == mi->mi_statb->st_dev) return(mi->mi_mnt); } return(NULL); } /* * Is "mnt" a duplicate of any of the mntinfo->mi_mnt elements? */ int isdupmnt(mntent_t *mnt, struct mntinfo *mntinfo) { struct mntinfo *m; for (m = mntinfo; m; m = m->mi_nxt) if (strcmp(m->mi_mnt->me_path, mnt->me_path) == 0) return(1); return(0); } /* * Alarm clock */ void wakeup(int dummy) { debugmsg(DM_CALL, "wakeup() in filesys.c called"); longjmp(env, 1); } /* * Make a linked list of mntinfo structures. * Use "mi" as the base of the list if it's non NULL. */ struct mntinfo * makemntinfo(struct mntinfo *mi) { FILE *mfp; static struct mntinfo *mntinfo; struct mntinfo *newmi, *m; struct stat mntstat; mntent_t *mnt; int timeo = 310; if (!(mfp = setmountent(MOUNTED_FILE, "r"))) { message(MT_NERROR, "%s: setmntent failed: %s", MOUNTED_FILE, SYSERR); return(NULL); } (void) signal(SIGALRM, wakeup); (void) alarm(timeo); if (setjmp(env)) { message(MT_NERROR, "Timeout getting mount info"); return(NULL); } mntinfo = mi; while ((mnt = getmountent(mfp)) != NULL) { debugmsg(DM_MISC, "mountent = '%s' (%s)", mnt->me_path, mnt->me_type); /* * Make sure we don't already have it for some reason */ if (isdupmnt(mnt, mntinfo)) continue; /* * Get stat info */ if (stat(mnt->me_path, &mntstat) != 0) { message(MT_WARNING, "%s: Cannot stat filesystem: %s", mnt->me_path, SYSERR); continue; } /* * Create new entry */ newmi = (struct mntinfo *) xcalloc(1, sizeof(struct mntinfo)); newmi->mi_mnt = newmountent(mnt); newmi->mi_statb = (struct stat *) xcalloc(1, sizeof(struct stat)); bcopy((char *) &mntstat, (char *) newmi->mi_statb, sizeof(struct stat)); /* * Add entry to list */ if (mntinfo) { for (m = mntinfo; m->mi_nxt; m = m->mi_nxt) continue; m->mi_nxt = newmi; } else mntinfo = newmi; } (void) alarm(0); (void) endmountent(mfp); return(mntinfo); } /* * Given a name like /usr/src/etc/foo.c returns the mntent * structure for the file system it lives in. * * If "statbuf" is not NULL it is used as the stat buffer too avoid * stat()'ing the file again back in server.c. */ mntent_t * getmntpt(char *pathname, struct stat *statbuf, int *isvalid) { static struct mntinfo *mntinfo = NULL; static struct stat filestat; struct stat *pstat; struct mntinfo *tmpmi; mntent_t *mnt; /* * Use the supplied stat buffer if not NULL or our own. */ if (statbuf) pstat = statbuf; else pstat = &filestat; if (!find_file(pathname, pstat, isvalid)) return(NULL); /* * Make mntinfo if it doesn't exist. */ if (!mntinfo) mntinfo = makemntinfo(NULL); /* * Find the mnt that pathname is on. */ if ((mnt = findmnt(pstat, mntinfo)) != NULL) return(mnt); /* * We failed to find correct mnt, so maybe it's a newly * mounted filesystem. We rebuild mntinfo and try again. */ if ((tmpmi = makemntinfo(mntinfo)) != NULL) { mntinfo = tmpmi; if ((mnt = findmnt(pstat, mntinfo)) != NULL) return(mnt); } error("%s: Could not find mount point", pathname); return(NULL); } #endif /* NFS_CHECK || RO_CHECK */ #if defined(NFS_CHECK) /* * Is "path" NFS mounted? Return 1 if it is, 0 if not, or -1 on error. */ int is_nfs_mounted(char *path, struct stat *statbuf, int *isvalid) { mntent_t *mnt; if ((mnt = (mntent_t *) getmntpt(path, statbuf, isvalid)) == NULL) return(-1); /* * We treat "cachefs" just like NFS */ if ((strcmp(mnt->me_type, METYPE_NFS) == 0) || (strcmp(mnt->me_type, "cachefs") == 0)) return(1); return(0); } #endif /* NFS_CHECK */ #if defined(RO_CHECK) /* * Is "path" on a read-only mounted filesystem? * Return 1 if it is, 0 if not, or -1 on error. */ int is_ro_mounted(char *path, struct stat *statbuf, int *isvalid) { mntent_t *mnt; if ((mnt = (mntent_t *) getmntpt(path, statbuf, isvalid)) == NULL) return(-1); if (mnt->me_flags & MEFLAG_READONLY) return(1); return(0); } #endif /* RO_CHECK */ /* * Is "path" a symlink? * Return 1 if it is, 0 if not, or -1 on error. */ int is_symlinked(char *path, struct stat *statbuf, int *isvalid) { static struct stat stb; if (!(*isvalid)) { if (lstat(path, &stb) != 0) return(-1); statbuf = &stb; } if (S_ISLNK(statbuf->st_mode)) return(1); return(0); } /* * Get filesystem information for "file". Set freespace * to the amount of free (available) space and number of free * files (inodes) on the filesystem "file" resides on. * Returns 0 on success or -1 on failure. * Filesystem values < 0 indicate unsupported or unavailable * information. */ int getfilesysinfo(char *file, long *freespace, long *freefiles) { #if defined(STATFS_TYPE) static statfs_t statfsbuf; char *mntpt; int t, r; /* * Get the mount point of the file. */ mntpt = find_file(file, NULL, &t); if (!mntpt) { debugmsg(DM_MISC, "unknown mount point for `%s'", file); return(-1); } /* * Stat the filesystem (system specific) */ #if STATFS_TYPE == STATFS_SYSV r = statfs(mntpt, &statfsbuf, sizeof(statfs_t), 0); #endif #if STATFS_TYPE == STATFS_BSD || STATFS_TYPE == STATFS_44BSD r = statfs(mntpt, &statfsbuf); #endif #if STATFS_TYPE == STATFS_OSF1 r = statfs(mntpt, &statfsbuf, sizeof(statfs_t)); #endif if (r < 0) { error("%s: Cannot statfs filesystem: %s.", mntpt, SYSERR); return(-1); } /* * If values are < 0, then assume the value is unsupported * or unavailable for that filesystem type. */ if (statfsbuf.f_bavail >= 0) *freespace = (statfsbuf.f_bavail * (statfsbuf.f_bsize / 512)) / 2; /* * BROKEN_STATFS means that statfs() does not set fields * to < 0 if the field is unsupported for the filesystem type. */ #if defined(BROKEN_STATFS) if (statfsbuf.f_ffree > 0) #else if (statfsbuf.f_ffree >= 0) #endif /* BROKEN_STATFS */ *freefiles = statfsbuf.f_ffree; #else /* !STATFS_TYPE */ *freespace = *freefiles = -1; #endif /* STATFS_TYPE */ return(0); }