root/usr.bin/rdistd/filesys.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);
}