【问题标题】:How to convert between a dev_t and major/minor device numbers?如何在 dev_t 和主要/次要设备号之间转换?
【发布时间】:2023-09-25 09:44:01
【问题描述】:

我正在尝试编写一个处理ustar 档案的可移植程序。对于设备文件,这些档案存储主要次要设备号。但是,POSIX 中的struct stat 仅包含一个st_rdev 类型的@ 成员987654323@ 用“设备 ID(如果文件是字符或块特殊)”描述。

如何在一对主要和次要设备号与stat() 返回的单个st_rdev 成员之间以可移植的方式进行转换?

【问题讨论】:

    标签: c posix tar portability device-node


    【解决方案1】:

    虽然所有 POSIX 编程接口都按原样使用设备号(dev_t 类型),但 FUZxxl 在对此答案的评论中指出,常见的UStar 文件格式——最常见的 tar 归档格式——确实会拆分设备号分为主要和次要。 (它们通常每个编码为七个八进制数字,因此出于兼容性原因,应该限制为 21 位无符号主要和 21 位无符号次要。这也意味着将设备号映射到仅主要或仅次要不是可靠的方法。)

    以下内容包括在Jonathon Reinhart's answer 上扩展的文件,在网上挖掘各种系统手册页和文档(makedev()major()minor())后,加上这个问题的 cmets。

    #if defined(custom_makedev) && defined(custom_major) && defined(custom_minor)
    /* Already defined */
    #else
    
    #undef custom_makedev
    #undef custom_major
    #undef custom_minor
    
    #if defined(__linux__) || defined(__GLIBC__)
    /* Linux, Android, and other systems using GNU C library */
    #ifndef _BSD_SOURCE
    #define _BSD_SOURCE 1
    #endif
    #include <sys/types.h>
    #define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
    #define custom_major(devnum)           major(devnum)
    #define custom_minor(devnum)           minor(devnum)
    
    #elif defined(_WIN32)
    /* 32- and 64-bit Windows. VERIFY: These are just a guess! */
    #define custom_makedev(dmajor, dminor) ((((unsigned int)dmajor << 8) & 0xFF00U) | ((unsigned int)dminor & 0xFFFF00FFU))
    #define custom_major(devnum)           (((unsigned int)devnum & 0xFF00U) >> 8)
    #define custom_minor(devnum)           ((unsigned int)devnum & 0xFFFF00FFU)
    
    #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
    /* FreeBSD, OpenBSD, NetBSD, and DragonFlyBSD */
    #include <sys/types.h>
    #define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
    #define custom_major(devnum)           major(devnum)
    #define custom_minor(devnum)           minor(devnum)
    
    #elif defined(__APPLE__) && defined(__MACH__)
    /* Mac OS X */
    #include <sys/types.h>
    #define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
    #define custom_major(devnum)           major(devnum)
    #define custom_minor(devnum)           minor(devnum)
    
    #elif defined(_AIX) || defined (__osf__)
    /* AIX, OSF/1, Tru64 Unix */
    #include <sys/types.h>
    #define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
    #define custom_major(devnum)           major(devnum)
    #define custom_minor(devnum)           minor(devnum)
    
    #elif defined(hpux)
    /* HP-UX */
    #include <sys/sysmacros.h>
    #define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
    #define custom_major(devnum)           major(devnum)
    #define custom_minor(devnum)           minor(devnum)
    
    #elif defined(sun)
    /* Solaris */
    #include <sys/types.h>
    #include <sys/mkdev.h>
    #define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
    #define custom_major(devnum)           major(devnum)
    #define custom_minor(devnum)           minor(devnum)
    
    #else
    /* Unknown OS. Try a the BSD approach. */
    #ifndef _BSD_SOURCE
    #define _BSD_SOURCE 1
    #endif
    #include <sys/types.h>
    #if defined(makedev) && defined(major) && defined(minor)
    #define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
    #define custom_major(devnum)           major(devnum)
    #define custom_minor(devnum)           minor(devnum)
    #endif
    #endif
    
    #if !defined(custom_makedev) || !defined(custom_major) || !defined(custom_minor)
    #error Unknown OS: please add definitions for custom_makedev(), custom_major(), and custom_minor(), for device number major/minor handling.
    #endif
    
    #endif
    

    可以从现有的支持 UStar 格式的存档器中收集其他定义。在我看来,与每个操作系统/架构上的现有实现兼容是最重要的。

    以上内容应涵盖所有使用 GNU C 库、Linux(包括 Android)、FreeBSD、OpenBSD、NetBSD、DragonFlyBSD、Mac OS X、AIX、Tru64、HP-UX 和 Solaris 的系统,以及任何定义宏的系统&lt;sys/types.h&gt; 包括在内。 Windows 部分,我不确定。

    据我了解,Windows 对所有普通文件使用设备 0,对设备使用 HANDLE(空指针类型)。我完全不确定上述逻辑在 Windows 上是否合理,但许多旧系统将设备编号的 8 个最低有效位放入次要位,将接下来的 8 位放入主要位,约定似乎是任何剩余位也将被放入(不转移)小调。检查现有的 UStar 格式的 tar 档案并参考设备会很有用,但我个人根本不使用 Windows。

    如果未检测到系统,并且系统未使用 BSD 样式的包含来定义宏,则上述将错误停止编译。 (我会亲自添加编译时机制,以帮助找到正确的标头定义,例如使用 findxargsgrep,以防发生这种情况,并建议将添加内容也发送到上游。@ 987654335@ 应该显示所有预定义的宏,以帮助识别操作系统和/或 C 库。)

    最初,POSIX 声明 dev_t 必须是算术类型(因此,理论上,在某些系统上它可能是 floatdouble 的某种变体),但 IEEE Std 1003.1, 2013 Edition 说它必须是整数类型。我敢打赌,这意味着没有已知的 POSIX-y 系统曾经使用过浮点 dev_t 类型。看起来 Windows 使用 void 指针或HANDLE 类型,但 Windows 无论如何都不符合 POSIX。

    【讨论】:

    • 您可以通过在顶部使用一次#undef HAVE_MAKEDEV,然后在找到支持的平台时简单地设置它来稍微简化代码——除非您打算让用户在顶部设置-DHAVE_MAKEDEV命令行并以某种方式神奇地包含独立于此代码的正确标题,用于不受支持的平台。
    • @JonathanLeffler:我担心包含的标题可能会定义HAVE_MAKEDEV。我不明白为什么,真的(如果他们需要检测宏,他们应该使用_HAVE_MAKEDEV),但这就是我在每个#include 之后保留#undef HAVE_MAKEDEV 的原因。我在旧的 Unixen 中小心翼翼地踩...
    • POSIX 有一个使用主设备号和次设备号的接口:ustar 文件格式将设备号分为主设备号和次设备号。这就是我需要这个。
    • @FUZxxl:该死,你是right。这也是一种常见的文件格式。另一个问题是major 和minor 可能只有21 位无符号整数的空间,因此您确实需要在每个架构上进行正确的设备-major/minor 映射。我将相应地编辑我的答案。
    • @JonathanLeffler:根据 FUZxxl 的评论,我决定重写答案。由于 UStar 文件格式使用非常广泛,因此我可以看到不必对拆分进行猜测。
    【解决方案2】:

    在定义BSD_SOURCE 后使用major()minor() 宏。

    makedev()、major() 和 minor() 函数未在 POSIX.1,但存在于许多其他系统上。

    http://man7.org/linux/man-pages/man3/major.3.html

    【讨论】:

    • 你知道没有定义这些函数的常见平台吗?
    • 我在 Linux 和 BSD 上使用过它们。是否有任何其他类似 UNIX 的平台符合“通用”条件?
    • OS X、Solaris、Microsoft Windows、更多的 BSD 风格,仅举几例。
    • 好吧,我正在考虑将 OSX 视为 BSD,但我没有在那里使用它。 Solaris 可能有自己的宏,我不记得了。 Windows 不是 Unix,也没有主要/次要设备的概念。
    • @FUZxxl,Windows 甚至还没有成为 POSIX 平台。它确实支持一些 POSIX API,但还不足以让大多数POSIX C 程序无需修改即可工作。
    【解决方案3】:

    我有一个基于 Minix 的旧版 ls 的程序,但从那时起我修改了很多 mangled。它具有以下代码来检测主要和次要宏 - 以及一些关于(现在)它过去工作过的古董系统的 cmets。它假定有足够新的 GCC 版本来支持 #pragma GCC diagnostic ignored 等。除非您明确包含它,否则您必须非常努力(例如 clang -Weverything)才能使 -Wunused-macros 选项生效。

    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wunused-macros"
    /* Defines to ensure major and minor macros are available */
    #define _DARWIN_C_SOURCE    /* In <sys/types.h> on MacOS X */
    #define _BSD_SOURCE         /* In <sys/sysmacros.h> via <sys/types.h> on Linux (Ubuntu 12.0.4) */
    #define __EXTENSIONS__      /* Maybe beneficial on Solaris */
    #pragma GCC diagnostic pop
    
    /* From Solaris 2.6 sys/sysmacros.h
    **
    ** WARNING: The device number macros defined here should not be used by
    ** device drivers or user software. [...]  Application software should make
    ** use of the library routines available in makedev(3). [...]  Macro
    ** routines bmajor(), major(), minor(), emajor(), eminor(), and makedev()
    ** will be removed or their definitions changed at the next major release
    ** following SVR4.
    **
    ** #define  O_BITSMAJOR 7       -- # of SVR3 major device bits
    ** #define  O_BITSMINOR 8       -- # of SVR3 minor device bits
    ** #define  O_MAXMAJ    0x7f    -- SVR3 max major value
    ** #define  O_MAXMIN    0xff    -- SVR3 max major value
    **
    ** #define  L_BITSMAJOR 14      -- # of SVR4 major device bits
    ** #define  L_BITSMINOR 18      -- # of SVR4 minor device bits
    ** #define  L_MAXMAJ    0x3fff  -- SVR4 max major value
    ** #define  L_MAXMIN    0x3ffff -- MAX minor for 3b2 software drivers.
    ** -- For 3b2 hardware devices the minor is restricted to 256 (0-255)
    */
    
    /* AC_HEADER_MAJOR:
    ** - defines MAJOR_IN_MKDEV if found in sys/mkdev.h
    ** - defines MAJOR_IN_SYSMACROS if found in sys/macros.h
    ** - otherwise, hope they are in sys/types.h
    */
    
    #if defined MAJOR_IN_MKDEV
    #include <sys/mkdev.h>
    #elif defined MAJOR_IN_SYSMACROS
    #include <sys/sysmacros.h>
    #elif defined(MAJOR_MINOR_MACROS_IN_SYS_TYPES_H)
    /* MacOS X 10.2 - for example */
    /* MacOS X 10.5 requires -D_DARWIN_C_SOURCE or -U_POSIX_C_SOURCE - see above */
    #elif defined(USE_CLASSIC_MAJOR_MINOR_MACROS)
    #define major(x)    ((x>>8) & 0x7F)
    #define minor(x)    (x & 0xFF)
    #else
    /* Hope the macros are in <sys/types.h> or otherwise magically visible */
    #endif
    
    #define MAJOR(x)    ((long)major(x))
    #define MINOR(x)    ((long)minor(x))
    

    您有理由不那么热衷于代码中的“希望宏......神奇地可见”部分。

    AC_HEADER_MAJOR 的引用是对autoconf 中推断此信息的宏。如果您有由autoconf 生成的config.h 文件,这将是相关的。

    POSIX

    注意,POSIX pax 命令定义了ustar 格式,并指定它在信息中包含devmajordevminor,但添加了:

    …分别代表字符特殊文件和块特殊文件。在这种情况下,devmajordevminor 字段应包含定义设备的信息,其格式未在本卷 POSIX.1-2008 中指定。实现可以将设备规范映射到它们自己的本地规范,也可以忽略该条目。

    这意味着没有一种完全可移植的方式来表示数字。这并非完全不合理(但它是令人讨厌的);主要和次要设备编号的含义因平台而异,也未指定。任何通过ustar 格式创建块或字符设备的尝试只有在源机器和目标机器运行相同(相同版本)操作系统的情况下才能合理可靠地工作——尽管它们通常可以跨相同操作系统的版本移植.

    【讨论】: