diff --git a/cmdline/unixapp.c b/cmdline/unixapp.c index a3e702cb..e7deb6ff 100644 --- a/cmdline/unixapp.c +++ b/cmdline/unixapp.c @@ -971,9 +971,83 @@ bail: return -1; } +static int extract_zfs_mount(const char* resolved, char* dataset, size_t dataset_size, char* mount_point, size_t mount_point_size) +{ + struct stat st; + + if (stat(resolved, &st) != 0) + return -1; + + FILE* fd = fopen("/proc/self/mountinfo", "r"); + if (!fd) + return -1; + + size_t best_len = 0; + while (1) { + char buf[PATH_MAX * 2 + 64]; + char* id_map[8]; + unsigned id_mac; + char* fs_map[8]; + unsigned fs_mac; + unsigned mount_major; + unsigned mount_minor; + + char* s = fgets(buf, sizeof(buf), fd); + if (s == 0) + break; + + /* find the separator position */ + char* separator = strstr(s, " - "); + if (!separator) + continue; + + /* skip the separator */ + *separator = 0; + separator += 3; + + /* split the line */ + id_mac = strsplit(id_map, 8, s, " \t\r\n", 0, 1); + fs_mac = strsplit(fs_map, 8, separator, " \t\r\n", 0, 1); + + if (id_mac < 5 || fs_mac < 2) + continue; + + /* mount point must contain the directory */ + unescape_mount(id_map[4]); + const char* mp = id_map[4]; + size_t mp_len = strlen(mp); + if (strncmp(resolved, mp, mp_len) != 0) + continue; + if (mp_len > 1 && resolved[mp_len] != '/' && resolved[mp_len] != 0) + continue; + + /* match the device backing the path, not a hidden parent mount */ + if (strcmp(fs_map[0], "zfs") != 0 + || sscanf(id_map[2], "%u:%u", &mount_major, &mount_minor) != 2 + || mount_major != major(st.st_dev) || mount_minor != minor(st.st_dev)) + continue; + + /* use the visible mount, including a legacy mount under a parent dataset */ + if (mp_len > best_len) { + best_len = mp_len; + unescape_mount(fs_map[1]); + pathcpy(dataset, dataset_size, fs_map[1]); + pathcpy(mount_point, mount_point_size, mp); + } + } + + int error = ferror(fd); + if (fclose(fd) != 0 || error) + return -1; + + return best_len == 0 ? -1 : 0; +} + static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char* uuid, size_t uuid_size, char* mount_point, size_t mount_point_size) { char resolved[PATH_MAX]; + char mounted_dataset[PATH_MAX]; + char mounted_point[PATH_MAX]; const char* zfs = find_zfs(); if (!zfs) { @@ -988,13 +1062,16 @@ static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char /* LCOV_EXCL_STOP */ } + if (extract_zfs_mount(resolved, mounted_dataset, sizeof(mounted_dataset), mounted_point, sizeof(mounted_point)) != 0) + return -1; + /* list all ZFS filesystems in one shot */ const char* argv[] = { zfs, "list", "-H", "-o", - "name,guid,mountpoint", + "name,guid", "-t", "filesystem", 0 @@ -1007,10 +1084,10 @@ static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char /* LCOV_EXCL_STOP */ } - size_t best_len = 0; + int found = 0; while (1) { char buf[PATH_MAX * 2 + 64]; - char* map[3]; + char* map[2]; unsigned mac; char* s = os_fgets(buf, sizeof(buf), fp); @@ -1018,33 +1095,36 @@ static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char break; /* split the line */ - mac = strsplit(map, 3, s, "\t\r\n", 0, 1); + mac = strsplit(map, 2, s, "\t\r\n", 0, 1); - if (mac < 3) + if (mac < 2 || strcmp(map[0], mounted_dataset) != 0) continue; - const char* mp = map[2]; - size_t mp_len = strlen(mp); - if (strncmp(resolved, mp, mp_len) != 0) - continue; - if (mp_len > 1 && resolved[mp_len] != '/' && resolved[mp_len] != 0) - continue; - - /* keep the longest (innermost) match */ - if (mp_len > best_len) { - best_len = mp_len; - if (dataset) - pathcpy(dataset, dataset_size, map[0]); - if (uuid) - pathcpy(uuid, uuid_size, map[1]); - if (mount_point) - pathcpy(mount_point, mount_point_size, map[2]); - } + found = 1; + if (dataset) + pathcpy(dataset, dataset_size, map[0]); + if (uuid) + pathcpy(uuid, uuid_size, map[1]); + if (mount_point) + pathcpy(mount_point, mount_point_size, mounted_point); } - os_pclose(fp); + /* incomplete output cannot confirm the mounted dataset's GUID */ + int ret = os_pclose(fp); + if (ret < 0) { + /* LCOV_EXCL_START */ + log_error(errno, "Failed to close ZFS dataset list for '%s'. %s.\n", dir, strerror(errno)); + return -1; + /* LCOV_EXCL_STOP */ + } + if (ret != 0) { + /* LCOV_EXCL_START */ + log_error(ESOFT, "Failed to list ZFS datasets for '%s' (status %d).\n", dir, ret); + return -1; + /* LCOV_EXCL_STOP */ + } - if (best_len == 0) { + if (!found) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ @@ -1855,6 +1935,12 @@ static int devuuid_bcachefs(uint64_t device, const char* dir, char* uuid, size_t #if HAVE_LINUX_DEVICE int devuuid_zfs(uint64_t device, const char* dir, char* uuid, size_t uuid_size) { + struct statfs sfs; + + /* only report ZFS lookup failures after identifying the filesystem */ + if (statfs(dir, &sfs) != 0 || statfs_type(&sfs) != ZFS_SUPER_MAGIC) + return -1; + if (extract_zfs(dir, 0, 0, uuid, uuid_size, 0, 0) != 0) return -1;