开发者

Where is a file mounted?

开发者 https://www.devze.com 2022-12-21 19:10 出处:网络
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

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 开发者_JAVA技巧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!


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 find 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 @RichardPennington for the heads up on realpath(), and on comparing device numbers instead of inode numbers.


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 ;-)


This worked for me on OSX, which does not provide the mntent functions. In C++:

struct stat fileStat;
int result = stat(path, &fileStat);
if (result != 0) {
    // handle error
}    

struct statfs* mounts;
int numMounts = getmntinfo(&mounts, MNT_WAIT);
if (numMounts == 0) {
    // handle error
}    

for (int i = 0; i < numMounts; i++) {
    if (fileStat.st_dev == mounts[i].f_fsid.val[0])
        // mounts[i].f_mntonname is the mount path
}


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消