src/usr.bin/rdistd/filesys.c

433 lines
9.5 KiB
C

/* $OpenBSD: filesys.c,v 1.19 2015/01/21 04:08:37 guenther 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 <sys/types.h>
#include <sys/mount.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "server.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[PATH_MAX];
static char file[PATH_MAX + 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 = 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(&filestat, 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 = 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 : NULL);
}
/*
* 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_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)
{
static struct mntinfo *mntinfo;
struct mntinfo *newmi, *m;
struct stat mntstat;
mntent_t *mnt;
int timeo = 310;
if (!setmountent()) {
message(MT_NERROR, "setmntent failed: %s", 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()) != NULL) {
debugmsg(DM_MISC, "mountent = '%s'", mnt->me_path);
/*
* 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 = xcalloc(1, sizeof(*newmi));
newmi->mi_mnt = newmountent(mnt);
newmi->mi_dev = mntstat.st_dev;
/*
* Add entry to list
*/
if (mntinfo) {
for (m = mntinfo; m->mi_nxt; m = m->mi_nxt)
continue;
m->mi_nxt = newmi;
} else
mntinfo = newmi;
}
alarm(0);
endmountent();
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);
}
/*
* 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 = getmntpt(path, statbuf, isvalid)) == NULL)
return(-1);
if (mnt->me_flags & MEFLAG_NFS)
return(1);
return(0);
}
/*
* 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 = getmntpt(path, statbuf, isvalid)) == NULL)
return(-1);
if (mnt->me_flags & MEFLAG_READONLY)
return(1);
return(0);
}
/*
* 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, int64_t *freespace, int64_t *freefiles)
{
struct statfs statfsbuf;
char *mntpt;
int64_t val;
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);
}
r = statfs(mntpt, &statfsbuf);
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.
*/
val = -1;
if (statfsbuf.f_bavail >= 0)
val = (statfsbuf.f_bavail * (statfsbuf.f_bsize / 512)) / 2;
*freespace = val;
val = -1;
if (statfsbuf.f_favail >= 0)
val = statfsbuf.f_favail;
*freefiles = val;
return(0);
}