源码解析fstab文件的设备名如何表示

1,167 阅读3分钟

平常/etc/fstab文件内容如下:

我们知道fstab文件的第一例代表设备名,可以直接写/dev/sdb1,也可以写UUID=sdb1的uuid、LABEL=sdb1的label。那么除了这三种方式,还有别的方式吗?个人试验,可以写wwid,但需要是绝对路径,比如在/dev/disk/by-id/目录内容如下:
可以用/dev/disk/by-id/nvme-HFS256GD9TNG-62A0A_ES81N471111401H5I-part2写在fstab的第一列。而如果用WWID=nvme-HFS256GD9TNG-62A0A_ES81N471111401H5I-part2 ,则系统识别不到,这是为什么?为了调研这个问题,自然要查看读取fstab文件的代码。

首先,读取/etc/fstab文件内容,将用户填写的信息挂载起来的命令是systemd-fstab-generator,这是systemd中的一个服务。所以在systemd(版本systemd-237)源码中,查看meson.build内容,查找到systemd-fstab-generator的源码文件是fstab-generator.c和mount-setup.c。

在fstab-generator.c 的main函数中调用parse_fstab函数,如下:

static int parse_fstab(bool initrd) {
        _cleanup_endmntent_ FILE *f = NULL;
        const char *fstab_path;
        struct mntent *me;
        int r = 0;

        fstab_path = initrd ? "/sysroot/etc/fstab" : "/etc/fstab";
        f = setmntent(fstab_path, "re");
        if (!f) {
                if (errno == ENOENT)
                        return 0;

                return log_error_errno(errno, "Failed to open %s: %m", fstab_path);
        }   

        while ((me = getmntent(f))) {
                _cleanup_free_ char *where = NULL, *what = NULL, *canonical_where = NULL;
                bool makefs, growfs, noauto, nofail;
                int k;

                if (initrd && !mount_in_initrd(me))
                        continue;

                what = fstab_node_to_udev_node(me->mnt_fsname);
                if (!what)
                        return log_oom();

                if (is_device_path(what) && path_is_read_only_fs("sys") > 0) {
                        log_info("Running in a container, ignoring fstab device entry for %s.", what);
                        continue;
                }   

parse_fstab函数使用getmntent函数读取了/etc/fstab文件的每一行内容,其me->mnt_fsname就是要挂载的设备名。进入到fstab_node_to_udev_node函数:

static char *tag_to_udev_node(const char *tagvalue, const char *by) {
        _cleanup_free_ char *t = NULL, *u = NULL;
        size_t enc_len;

        u = unquote(tagvalue, QUOTES);
        if (!u)
                return NULL;

        enc_len = strlen(u) * 4 + 1;
        t = new(char, enc_len);
        if (!t)
                return NULL;

        if (encode_devnode_name(u, t, enc_len) < 0)
                return NULL;

        return strjoin("/dev/disk/by-", by, "/", t);
}

char *fstab_node_to_udev_node(const char *p) {
        assert(p);

        if (startswith(p, "LABEL="))
                return tag_to_udev_node(p+6, "label");

        if (startswith(p, "UUID="))
                return tag_to_udev_node(p+5, "uuid");

        if (startswith(p, "PARTUUID="))
                return tag_to_udev_node(p+9, "partuuid");

        if (startswith(p, "PARTLABEL="))
                return tag_to_udev_node(p+10, "partlabel");

        return strdup(p);
}

在代码中,清晰看到使用startswith判断设备名的开头是什么,然后调用tag_to_udev_node函数。如果是LABEL=,则会strjoin("/dev/disk/by-", by, "/", t),即获取/dev/disk/by-label/下对应的软连接。

至此,可以明白fatab文件如果书写UUID=xxx,其实systend会找到/dev/disk/by-uuid/xxx软连接;如果是label=xxx,其实systemd会找到/dev/disk/by-label/xxx软连接。所以,我们直接用/dev/disk/by-xx/xxx来表示设备也是可以的。根据代码,知道除了uuid和label外,还可以用partuuid和partlabel来表示设备。

查看/dev/disk/目录,还有一个by-id的目录,其中的文件名就是设备的wwid。因为代码没有解析WWID,所以我们也就在fstab中无法用WWID来表示设备了。

所以文件开头的问题,是不是这会儿就明白了呢?

另外,现在系统中,更喜欢使用uuid来表示设备,而不是使用设备名/dev/sdb。因为磁盘插槽替换,或者磁盘驱动问题,都可能会造成设备名变化。而挂载一个磁盘,必须要格式化,格式化则会给磁盘分配一个唯一的uuid。即使设备名变了,/dev/disk/by-uuid/xx始终会指向原来的设备。这是为什么呢?因为udev规则,/lib/udev/rules.d/60-persistent-storage.rules文件会维持这关系。