How to check if a path specifies a volume root directory

1.5k Views Asked by At

How can I check whether a given path (absolute or relative) specifies the root directory of a volume with POSIX or standard C runtime calls? Ideally, the code should work on both, Linux and Mac OS X.

3

There are 3 best solutions below

1
On BEST ANSWER

First, all you can really check using standard functions is if the path presented is a mount point, and that mount point may or may not be the root of its filesystem.

Assuming your system assigns a unique f_fsid value to each mount point, you can use the POSIX-stanard statvfs() function and compare the f_fsid field of the relevant statvfs structure of the path component with its parent:

#include <stdlib.h>
#include <string.h>
#include <sys/statvfs.h>


int isMountPoint( const char *path )
{
    struct statvfs sv1, sv2;

    char *realP = realpath( path, NULL );

    // if the real path is "/", yeah, it's the root
    if ( !strcmp( realP, "/" ) )
    {
        free( realP );
        return( 1 );
    }

    statvfs( realP, &sv1 );

    // find the parent by truncating at the last "/"
    char *pp = strrchr( realP, '/' );

    // if there is no parent, must be root
    if ( NULL == pp )
    {
        free( realP );
        return( 1 );
    }

    // keep the final / (makes handling things like "/mnt"
    // much easier)
    pp++;
    *pp = '\0';

    statvfs( realP, &sv2 );
    free( realP );

    // if f_fsid differs, it's the root of
    // the mounted filesystem
    return( sv1.f_fsid != sv2.f_fsid );
}

All error checking is left as an exercise (and it would also make this example longer and harder to understand...).

If the f_fsid of a path differs from its parent's f_fsid, or if the path is the root directory itself, the path passed in must be the mount point of its filesystem. Note that this does not have to be the actual root of the filesystem. For example, a host can export a file system via NFS, and an NFS client can mount any subdirectory in the remote file system as the "local root", or a loopback mount can refer to a subdirectory of another filesystem.

2
On

Well, you have several ways. Historically, the root inode has had inum of 2, so just stat(path, &buf); and check if (buf.st_ino == 2).

Recently, with proliferation of different filesystem types, there's no warranty of having inum equal to 2 in the root inode, so you must follow a different approach: Just check if the given path and the parent directory (just append "/.." to path) reside in different devices (st_dev field of struct stat info)

The following code illustrates this:

#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

int is_mount(char *path)
{
    struct stat sdir; /* inode info */
    struct stat spdir; /* parent inode info */
    char buffer[PATH_MAX];
    int res = stat(path, &sdir);
    if (res < 0) return -1;
    if (snprintf(buffer, sizeof buffer, "%s/..", path) >= sizeof buffer) {
            errno = ENAMETOOLONG;
            return -1;
    }
    res = stat(buffer, &spdir);
    if (res < 0) return -1;
    return sdir.st_dev != spdir.st_dev;  /* SEE ADDITIONAL NOTE */
}

int main(int argc, char **argv)
{
    int i;
    for (i = 1; i < argc; i++) {
            int res = is_mount(argv[i]);
            if ( res < 0 ) {
                    fprintf(stderr,
                            "%s: %s (errno = %d)\n", 
                            argv[i], strerror(errno), errno);
                    continue;
            }
            printf("%5s\t%s\n", res ? "true" : "false", argv[i]);
    } /* for */
} /* main */

Execution on a FreeBSD system:

$ ismount /home/luis/usr.ports/german/geonext/Makefile /home/luis/usr.ports/german/geonext /home/luis/usr.ports/german /home/luis/usr.ports /home/luis  /home / /var/run/cups /var/run /var pru3.c
/home/luis/usr.ports/german/geonext/Makefile: Not a directory (errno = 20)
pru3.c: Not a directory (errno = 20)
false       /home/luis/usr.ports/german/geonext
false       /home/luis/usr.ports/german
 true       /home/luis/usr.ports
false       /home/luis
false       /home
 true       /             <-- in the above code, this returns false, see ADDITIONAL NOTE.
false       /var/run/cups
 true       /var/run
false       /var

PORTABILITY NOTE

This should work in any un*x/linux that implements the stat(2) system call. UNIX v7 already implements this, so it's supposed to be found in all unices available.

ADDITIONAL NOTE

This approach does not work for the actual filesystem root (/) because the parent dir and the root dir both point to the same inode. This can be solved by checking the st_ino of both parent and child inodes to be equal. Just change the test for

return    (sdir.st_dev != spdir.st_dev)  /* different devices */
       || ( /* sdir.st_dev == spdir.st_dev && ---redundant */ 
               sdir.st_ino == spdir.st_ino); /* root dir case */
1
On

POSIX has a realpath function that returns the canonical path of any given path (thus eliminating/resolving dots, dotdots, links, etc).

Now POSIX doesn't define volume, only the hierarchy of filenames from a unique given root /. Now Unix variants are able to mount file trees one on top of other to obtain a single tree at runtime. There is no standard way to "mount" these volumes, even if mount is a very common way, because there is plenty of ways to realize this feature. So you have to read the documentation of your OS variant to determine how you can obtain the list of all mounted points.

You may also read Linux function to get mount points and How to get mount point information from an API on Mac?.