【问题标题】:How to check if a path specifies a volume root directory如何检查路径是否指定了卷根目录
【发布时间】:2018-02-05 20:22:15
【问题描述】:

如何检查给定路径(绝对路径或相对路径)是否使用 POSIX 或标准 C 运行时调用指定卷的根目录?理想情况下,代码应该可以在 Linux 和 Mac OS X 上运行。

【问题讨论】:

    标签: c linux macos posix stdio


    【解决方案1】:

    首先,您可以使用标准函数真正检查的是所显示的路径是否是一个挂载点,并且该挂载点可能是也可能不是其文件系统的根。

    假设您的系统为每个挂载点分配了唯一的f_fsid 值,您可以使用POSIX-stanard statvfs() function 并将路径组件的相关statvfs structuref_fsid 字段与其父级进行比较:

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

    所有错误检查都留作练习(这也会使这个例子变得更长更难理解......)。

    如果路径的f_fsid 与其父路径的f_fsid 不同,或者如果路径是根目录本身,则传入的路径必须是其文件系统的挂载点。请注意,这不必是文件系统的实际根。例如,主机可以通过 NFS 导出文件系统,NFS 客户端可以将远程文件系统中的任何子目录挂载为“本地根”,或者环回挂载可以引用另一个文件系统的子目录。

    【讨论】:

    • 谢谢,这在 Linux 和 Mac OS 上都可以正常工作。我已经纠正了您代码中的两个小错误,我猜这也是留给读者的练习:)
    【解决方案2】:

    嗯,你有几种方法。从历史上看,根 inode 的 inum 为 2,所以只需 stat(path, &amp;buf); 并检查 if (buf.st_ino == 2)

    最近,随着不同文件系统类型的激增,无法保证根 inode 中的 inum 等于 2,因此您必须采用不同的方法:只需检查给定路径和父目录是否(只需附加 @ 987654325@ 到路径)驻留在不同的设备中(struct stat 信息的st_dev 字段)

    以下代码说明了这一点:

    #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 */
    

    在 FreeBSD 系统上执行:

    $ 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
    

    便携说明

    这应该适用于任何实现stat(2) 系统调用的un*x/linux。 UNIX v7 已经实现了这一点,因此应该可以在所有可用的 unice 中找到它。

    附加说明

    这种方法不适用于实际的文件系统根目录 (/),因为父目录和根目录都指向同一个 inode。这可以通过检查父节点和子节点的st_ino 是否相等来解决。只需将测试更改为

    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 */
    

    【讨论】:

    • 感谢您的广泛回答!我对 Linux 不太熟悉,所以我无法确定您或 Andrew Henle 的答案是否是最好的方法。你的答案比安德鲁的或安德鲁的比你的有什么优势吗?我对 Linux 的经验不够丰富。
    • 任何方式都是好的。恕我直言,我的更保守,因为它只使用旧的 unix 系统调用,所以它应该更便携,但它们中的任何一个都应该同样有用。我已经测试了我的,当路径不是目录路径(例如,找不到文件、不是目录或由于权限而无法访问文件)时,它会正确分派。就我个人而言,我更喜欢附加“/. ." 到路径而不是在最后一个路径元素上切割它。您将无法在缺少 statvfs(2) 的系统中使用它(但很少有人这样做)
    【解决方案3】:

    POSIX 有一个realpath 函数,它返回任何给定路径的规范路径(从而消除/解析点、点、链接等)。

    现在 POSIX 不定义卷,只定义来自唯一给定根 / 的文件名层次结构。现在 Unix 变体能够将文件树一个一个地挂载到另一个之上,以便在运行时获得一棵树。没有标准的方法来“挂载”这些卷,即使mount 是一种非常常见的方法,因为有很多方法可以实现此功能。因此,您必须阅读您的操作系统变体的文档以确定如何获取所有已安装点的列表。

    您也可以阅读Linux function to get mount pointsHow to get mount point information from an API on Mac?

    【讨论】:

    • Jean,这并没有回答问题,这实际上是在询问 路径是否实际上是安装点(根设备目录只能是根目录或什么是相同的,已安装设备的根目录)对不起,但我不得不对你的答案投反对票。 :/ 规范路径不处理根目录或挂载点,它只消除路径中嵌入的/..//./ 字符串并保持干净。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-22
    • 2012-11-30
    • 1970-01-01
    • 1970-01-01
    • 2015-06-20
    • 2015-07-30
    • 2016-09-20
    相关资源
    最近更新 更多