tags:

views:

255

answers:

4

Given a path to a file or directory, how can I determine the mount point for that file? For example, if /tmp is mounted as a tmpfs filesystem then given the file name /tmp/foo/bar I want to know that it's stored on a tmpfs rooted at /tmp.

This will be in C++ and I'd like to avoid invoking external commands via system(). The code should be robust--not necessarily against deliberate tampering but definitely in the face of nested mountpoints, symlinks, etc.

I haven't been able to find a simple system call to do this. It looks like I'll have to write the check myself. Here's a rough outline of what I'm planning.

  1. Canonicalize the file name a la the readlink shell command. How?
  2. Read /etc/mtab with getmntent() & co.
  3. Determine the corresponding mount entry for the file. How?

For #1 is there a simple system call or do I need to read each directory component of the path and resolve them with readlink(2) if they are symlinks? And handle . and .. myself? Seems like a pain.

For #3 I've got various ideas on how to do this. Not sure which is best.

  1. open() the file, its parent, its parent's parent, etc. using openat(fd, "..") until I reach one of the /etc/mtab entries. (How do I know when I do? fstat() them and compare the inode numbers?)
  2. Find the longest directory name in the mount table which is a substring of my file name.

I'm leaning towards the first option but before I code it all up I want to make sure I'm not overlooking anything--ideally a built-in function that does this already!

+2  A: 

You could start with realpath and work your way up checking each directory with stat to see if it on the same device. It does seem like there should be a simpler way.


Edit:

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    char *p;
    char path[PATH_MAX];
    struct stat buf;
    dev_t dev;

    if (realpath(argv[1], path) == NULL) {
        fprintf(stderr, "can't find %s\n", argv[1]);
        exit(1);
    }

    if (stat(path, &buf) != 0) {
        fprintf(stderr, "can't statind %s\n", path);
    exit(1);
    }

    dev = buf.st_dev;
    while((p = strrchr(path, '/'))) {
        *p = '\0';
        stat(path, &buf);
        if (buf.st_dev != dev) {
            printf("mount point = %s\n", path);
            exit(0);
        }
   }
    printf("mount point = /\n");
}

Thanksd for the distraction ;-)

Richard Pennington
A: 

You should be able to read in /etc/mtab, parse out all the locations where something is already mounted, and see if any of your files in question are located in any of those locations (or their sub-directories). You shouldn't need any special system functions for this, if you have the mount points and file paths as strings then you can process them using normal string processing functions.

Obviously, symlinks can throw a wrench into this whole process. Any file path that includes a symlink will have to be converted into its "actual" path before processing it. Hopefully there's a way to do this without individually checking a file and each of its parents, but you can always brute-force it if you must. You'll probably want to use realpath to remove symlinks.

bta
+6  A: 

This is what I've come up with. It turns out there's usually no need to iterate through the parent directories. All you have to do is get the file's device number and then file the corresponding mount entry with the same device number.

struct mntent *mountpoint(char *filename, struct mntent *mnt, char *buf, size_t buflen)
{
    struct stat s;
    FILE *      fp;
    dev_t       dev;

    if (stat(filename, &s) != 0) {
        return NULL;
    }

    dev = s.st_dev;

    if ((fp = setmntent("/proc/mounts", "r")) == NULL) {
        return NULL;
    }

    while (getmntent_r(fp, mnt, buf, buflen)) {
        if (stat(mnt->mnt_dir, &s) != 0) {
            continue;
        }

        if (s.st_dev == dev) {
            endmntent(fp);
            return mnt;
        }
    }

    endmntent(fp);

    // Should never reach here.
    errno = EINVAL;
    return NULL;
}

Thanks to @Richard for the heads up on realpath(), and on comparing device numbers instead of inode numbers.

John Kugelman
Nice. Much better than mine since you get all the information you need.
Richard Pennington
You might want to use /proc/mounts instead of /etc/mtab - /etc/mtab is maintained by the mount command and could be fudged with, whereas /proc/mounts is a representation of the mount table itself.BSD users should use getmntinfo()
Daniel Papasian
That should even work on device special files, without the need for the special-case at the end (`stat.st_dev` is *always* the ID of the device containing the file, even for device special files).
caf
A: 

Have you checked out the Boost Filesystem Library? I've not used it myself but it may give you what you want in a very portable format.

Robin Welch