// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2017-2018 HUAWEI, Inc. * https://www.huawei.com/ * Copyright (C) 2021, Alibaba Cloud */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "internal.h" #include "xattr.h" #include "erofs_defs.h" MALLOC_DEFINE(M_EROFS, "erofs", "EROFS filesystem"); static const char *erofs_opts[] = { "export", "from", NULL, }; static vfs_mount_t erofs_mount; static vfs_root_t erofs_root; static vfs_statfs_t erofs_statfs; static vfs_unmount_t erofs_unmount; static vfs_vget_t erofs_vgetf; static vfs_fhtovp_t erofs_fhtovp; #define EROFS_DEVICE_OPT_PREFIX "device." struct erofs_device_arg { uint16_t slot; char *path; }; static int erofs_load_generation_seed(struct erofs_mount *em, uint32_t sb_size, uint32_t *seedp) { uint32_t seed; void *buf; int error; error = erofs_bread(em, EROFS_SUPER_OFFSET, sb_size, &buf); if (error != 0) return (error); seed = fnv_32_buf(buf, sb_size, FNV1_32_INIT); erofs_brelse(buf); *seedp = seed != 0 ? seed : 1; return (0); } static void erofs_free_device_args(struct erofs_device_arg *args, unsigned int count) { unsigned int i; if (args == NULL) return; for (i = 0; i < count; ++i) free(args[i].path, M_EROFS); free(args, M_EROFS); } static int erofs_parse_device_slot(const char *name, uint16_t *slotp) { const char *p; unsigned int slot; if (strncmp(name, EROFS_DEVICE_OPT_PREFIX, sizeof(EROFS_DEVICE_OPT_PREFIX) - 1) != 0) return (ENOENT); p = name + sizeof(EROFS_DEVICE_OPT_PREFIX) - 1; if (*p < '1' || *p > '9') return (EINVAL); slot = 0; for (; *p != '\0'; ++p) { if (*p < '0' || *p > '9' || slot > (UINT16_MAX - (*p - '0')) / 10) return (EINVAL); slot = slot * 10 + (*p - '0'); } if (slot == 0 || slot > UINT16_MAX) return (EINVAL); *slotp = slot; return (0); } static int erofs_parse_device_options(struct mount *mp, struct erofs_device_arg **argsp, unsigned int *countp) { struct erofs_device_arg *args; struct vfsopt *opt; char name[32]; unsigned int count, i; uint16_t slot; int error; *argsp = NULL; *countp = 0; count = 0; TAILQ_FOREACH(opt, mp->mnt_optnew, link) { error = erofs_parse_device_slot(opt->name, &slot); if (error == ENOENT) continue; if (error != 0 || opt->value == NULL || opt->len <= 1 || ((char *)opt->value)[opt->len - 1] != '\0') { vfs_mount_error(mp, "erofs: invalid external device option %s", opt->name); return (EINVAL); } if (count == UINT16_MAX) return (E2BIG); ++count; } if (count == 0) return (0); args = mallocarray(count, sizeof(*args), M_EROFS, M_WAITOK | M_ZERO); i = 0; TAILQ_FOREACH(opt, mp->mnt_optnew, link) { error = erofs_parse_device_slot(opt->name, &slot); if (error == ENOENT) continue; KASSERT(error == 0, ("validated EROFS device option changed")); args[i].slot = slot; args[i].path = malloc(opt->len, M_EROFS, M_WAITOK); memcpy(args[i].path, opt->value, opt->len); ++i; } for (i = 0; i < count; ++i) { snprintf(name, sizeof(name), EROFS_DEVICE_OPT_PREFIX "%u", args[i].slot); vfs_deleteopt(mp->mnt_optnew, name); } *argsp = args; *countp = count; return (0); } static void erofs_release_device_info(struct erofs_device_info *dif) { if (dif->cp != NULL) { g_topology_lock(); g_vfs_close(dif->cp); g_topology_unlock(); dif->cp = NULL; } if (dif->devvp != NULL) { vrele(dif->devvp); dif->devvp = NULL; } if (dif->dev != NULL) { dev_rel(dif->dev); dif->dev = NULL; } } static bool erofs_provider_is_duplicate(struct erofs_mount *em, struct g_provider *pp) { unsigned int i; if (em == NULL) return (false); if (em->dif0.cp != NULL && em->dif0.cp->provider == pp) return (true); for (i = 0; i < em->extra_devices; ++i) { if (em->devs[i].cp != NULL && em->devs[i].cp->provider == pp) return (true); } return (false); } static int erofs_open_device(struct erofs_mount *em, const char *path, struct erofs_device_info *dif) { struct g_provider *pp; struct nameidata nd; struct vnode *devvp; struct cdev *dev; int error; bzero(dif, sizeof(*dif)); NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, path); error = namei(&nd); if (error != 0) return (error); devvp = nd.ni_vp; NDFREE_PNBUF(&nd); if (!vn_isdisk_error(devvp, &error)) { vput(devvp); return (error); } error = VOP_ACCESS(devvp, VREAD, curthread->td_ucred, curthread); if (error != 0) error = priv_check(curthread, PRIV_VFS_MOUNT_PERM); if (error != 0) { vput(devvp); return (error); } dev = devvp->v_rdev; dev_ref(dev); g_topology_lock(); pp = g_dev_getprovider(dev); if (pp == NULL) error = ENXIO; else if (erofs_provider_is_duplicate(em, pp)) error = EINVAL; else error = g_vfs_open(devvp, &dif->cp, "erofs", 0); if (error == 0) { dif->mediasize = dif->cp->provider->mediasize; dif->sectorsize = dif->cp->provider->sectorsize; } g_topology_unlock(); VOP_UNLOCK(devvp); if (error != 0) { dev_rel(dev); vrele(devvp); return (error); } dif->devvp = devvp; dif->dev = dev; if (dif->sectorsize == 0 || (dif->sectorsize & (dif->sectorsize - 1)) != 0) { erofs_release_device_info(dif); return (EINVAL); } return (0); } static void erofs_update_iosize_max(struct mount *mp, const struct erofs_device_info *dif) { u_long iosize; iosize = dif->dev != NULL && dif->dev->si_iosize_max != 0 ? dif->dev->si_iosize_max : MAXPHYS; mp->mnt_iosize_max = MIN(mp->mnt_iosize_max, MIN(iosize, (u_long)MAXPHYS)); } static void erofs_free_dev_context(struct erofs_mount *em) { unsigned int i; if (em->devs != NULL) { for (i = em->extra_devices; i > 0; --i) erofs_release_device_info(&em->devs[i - 1]); free(em->devs, M_EROFS); } } static void erofs_drop_internal_inodes(struct erofs_mount *em) { if (em->metabox_en != NULL) free(em->metabox_en, M_EROFS); if (em->packed_inode != NULL) free(em->packed_inode, M_EROFS); } static void erofs_sb_free(struct erofs_mount *em) { if (em == NULL) return; z_erofs_extent_cache_fini(em); erofs_xattr_prefixes_cleanup(em); erofs_drop_internal_inodes(em); erofs_free_dev_context(em); erofs_release_device_info(&em->dif0); free(em, M_EROFS); } static int erofs_superblock_csum_verify(struct erofs_mount *em, const struct erofs_super_block *dsb) { uint32_t expected, crc; size_t len; void *buf; int error; if ((le32toh(dsb->feature_compat) & EROFS_FEATURE_COMPAT_SB_CHKSUM) == 0) return (0); len = 1u << dsb->blkszbits; if (len > EROFS_SUPER_OFFSET) len -= EROFS_SUPER_OFFSET; buf = NULL; error = erofs_bread(em, EROFS_SUPER_OFFSET, len, &buf); if (error != 0) return (error); crc = calculate_crc32c(EROFS_CRC32C_SEED, (const uint8_t *)buf + offsetof(struct erofs_super_block, checksum) + sizeof(dsb->checksum), len - offsetof(struct erofs_super_block, checksum) - sizeof(dsb->checksum)); expected = le32toh(dsb->checksum); erofs_brelse(buf); if (crc != expected) { vfs_mount_error(em->mnt, "erofs: invalid superblock checksum 0x%08x, " "0x%08x expected", crc, expected); return (EINTEGRITY); } return (0); } static void erofs_sb_blocks_root(const struct erofs_super_block *dsb, uint32_t incompat, uint64_t *blocks, uint64_t *root_nid) { *blocks = le32toh(dsb->blocks_lo); if ((incompat & EROFS_FEATURE_INCOMPAT_48BIT) != 0 && dsb->rootnid_8b != 0) { *blocks |= (uint64_t)le16toh(dsb->rb.blocks_hi) << 32; *root_nid = le64toh(dsb->rootnid_8b); } else { *root_nid = le16toh(dsb->rb.rootnid_2b); } } static int erofs_validate_device_size(struct erofs_mount *em, struct erofs_device_info *dif, erofs_blk_t blocks) { uint64_t bytes; if (blocks == 0) return (EINTEGRITY); if (em->block_size < dif->sectorsize || em->block_size % dif->sectorsize != 0) return (EINVAL); if (blocks > (UINT64_MAX >> em->block_bits)) return (EINTEGRITY); bytes = blocks << em->block_bits; if (bytes > dif->mediasize) return (ENXIO); return (0); } static int erofs_init_device(struct erofs_mount *em, struct erofs_device_info *dif, const char *path) { struct erofs_device_info opened; erofs_blk_t blocks, uniaddr; int error; blocks = dif->blocks; uniaddr = dif->uniaddr; error = erofs_open_device(em, path, &opened); if (error != 0) return (error); erofs_update_iosize_max(em->mnt, &opened); opened.blocks = blocks; opened.uniaddr = uniaddr; *dif = opened; return (erofs_validate_device_size(em, dif, dif->blocks)); } static const char * erofs_device_arg_path(const struct erofs_device_arg *args, unsigned int count, unsigned int slot) { unsigned int i; for (i = 0; i < count; ++i) { if (args[i].slot == slot) return (args[i].path); } return (NULL); } static int erofs_scan_devices(struct erofs_mount *em, const struct erofs_super_block *dsb, const struct erofs_device_arg *args, unsigned int arg_count) { struct erofs_deviceslot *slots; struct erofs_device_info *dif; const char *path; uint64_t devt_off, devt_size, image_size, end, other_end; erofs_blk_t maxend; unsigned int i, j, mask; void *buf; int error; em->total_blocks = em->dif0.blocks; em->flatdev_blocks = em->dif0.blocks; if (em->extra_devices == 0) { if (arg_count != 0) { vfs_mount_error(em->mnt, "erofs: external devices given without a device table"); return (EINVAL); } return (0); } devt_off = (uint64_t)le16toh(dsb->devt_slotoff) * EROFS_DEVT_SLOT_SIZE; devt_size = (uint64_t)em->extra_devices * EROFS_DEVT_SLOT_SIZE; if (em->dif0.blocks > (UINT64_MAX >> em->block_bits)) return (EINTEGRITY); image_size = em->dif0.blocks << em->block_bits; if (devt_off > image_size || devt_size > image_size - devt_off || devt_size > SIZE_MAX) return (EINTEGRITY); error = erofs_bread(em, devt_off, (size_t)devt_size, &buf); if (error != 0) return (error); slots = buf; em->devs = mallocarray(em->extra_devices, sizeof(*em->devs), M_EROFS, M_WAITOK | M_ZERO); maxend = em->dif0.blocks; for (i = 0; i < em->extra_devices; ++i) { dif = &em->devs[i]; dif->blocks = le32toh(slots[i].blocks_lo); dif->uniaddr = le32toh(slots[i].uniaddr_lo); if (erofs_sb_has_48bit(em)) { dif->blocks |= (uint64_t)le16toh(slots[i].blocks_hi) << 32; dif->uniaddr |= (uint64_t)le16toh(slots[i].uniaddr_hi) << 32; } if (dif->blocks == 0 || __builtin_add_overflow(dif->uniaddr, dif->blocks, &end)) { error = EINTEGRITY; goto out; } if (end > (erofs_sb_has_48bit(em) ? (1ULL << 48) : (1ULL << 32))) { error = EINTEGRITY; goto out; } if (dif->uniaddr != 0 && dif->uniaddr < em->dif0.blocks) { error = EINTEGRITY; goto out; } for (j = 0; j < i; ++j) { if (dif->uniaddr == 0 || em->devs[j].uniaddr == 0) continue; if (__builtin_add_overflow(em->devs[j].uniaddr, em->devs[j].blocks, &other_end)) { error = EINTEGRITY; goto out; } if (dif->uniaddr < other_end && em->devs[j].uniaddr < end) { error = EINTEGRITY; goto out; } } if (__builtin_add_overflow(em->total_blocks, dif->blocks, &em->total_blocks)) { error = EOVERFLOW; goto out; } maxend = MAX(maxend, (erofs_blk_t)end); } erofs_brelse(buf); buf = NULL; em->flatdev_blocks = maxend; mask = 1; while (mask < (unsigned int)em->extra_devices + 1) mask <<= 1; em->device_id_mask = mask - 1; em->flatdev = arg_count == 0; if (em->flatdev) return (erofs_validate_device_size(em, &em->dif0, em->flatdev_blocks)); if (arg_count != em->extra_devices) { vfs_mount_error(em->mnt, "erofs: external devices don't match (ondisk %u, given %u)", em->extra_devices, arg_count); return (arg_count < em->extra_devices ? ENXIO : EINVAL); } for (i = 0; i < arg_count; ++i) { if (args[i].slot == 0 || args[i].slot > em->extra_devices) return (EINVAL); } for (i = 0; i < em->extra_devices; ++i) { path = erofs_device_arg_path(args, arg_count, i + 1); if (path == NULL) return (ENXIO); error = erofs_init_device(em, &em->devs[i], path); if (error != 0) return (error); } return (0); out: erofs_brelse(buf); return (error); } static int erofs_init_packed_inode(struct erofs_mount *em) { int error; /* Load the packed carrier before any fragment-backed metabox inode. */ if ((em->feature_incompat & EROFS_FEATURE_INCOMPAT_FRAGMENTS) != 0 && em->packed_nid > 0) { em->packed_inode = malloc(sizeof(*em->packed_inode), M_EROFS, M_WAITOK | M_ZERO); if (em->packed_inode == NULL) return (ENOMEM); error = erofs_read_inode(em, em->packed_nid, em->packed_inode); if (error != 0) { free(em->packed_inode, M_EROFS); em->packed_inode = NULL; return (error); } if (em->packed_inode->vtype != VREG || em->packed_inode->fragment) { vfs_mount_error(em->mnt, "erofs: packed inode nid=%ju is not a non-recursive regular file", (uintmax_t)em->packed_nid); return (EINTEGRITY); } } return (0); } static int erofs_init_metabox_inode(struct erofs_mount *em) { int error; /* * METABOX NIDs address inode slots in this backing inode's data. The * packed carrier is ready first so a compressed metabox may legally end in * a fragment pcluster without reading an uninitialized dependency. */ if (erofs_sb_has_metabox(em)) { struct erofs_map_blocks map; em->metabox_en = malloc(sizeof(*em->metabox_en), M_EROFS, M_WAITOK | M_ZERO); if (em->metabox_en == NULL) return (ENOMEM); error = erofs_read_inode(em, em->metabox_nid, em->metabox_en); if (error != 0) return (error); if (em->metabox_en->vtype != VREG) { vfs_mount_error(em->mnt, "erofs: metabox inode nid=%ju is not a regular file", (uintmax_t)em->metabox_nid); return (EINTEGRITY); } if (em->metabox_en->fragment) { if (em->packed_inode == NULL || em->packed_inode->nid == em->metabox_en->nid || em->metabox_en->size == 0) return (EINTEGRITY); bzero(&map, sizeof(map)); map.m_la = em->metabox_en->size - 1; error = z_erofs_map_blocks_iter(em, em->metabox_en, &map, EROFS_GET_BLOCKS_FIEMAP); if (error != 0 || (map.m_flags & EROFS_MAP_FRAGMENT) == 0) return (error != 0 ? error : EINTEGRITY); } } return (0); } static int erofs_mountfs(struct erofs_device_info *primary, struct mount *mp, const struct erofs_device_arg *args, unsigned int arg_count) { struct erofs_mount *em; struct erofs_super_block *dsb; uint32_t unsupported; void *buf; int error; em = malloc(sizeof(*em), M_EROFS, M_WAITOK | M_ZERO); em->mnt = mp; z_erofs_extent_cache_init(em); em->dif0 = *primary; bzero(primary, sizeof(*primary)); buf = NULL; error = erofs_bread(em, EROFS_SUPER_OFFSET, sizeof(*dsb), &buf); if (error != 0) goto fail; dsb = buf; if (le32toh(dsb->magic) != EROFS_SUPER_MAGIC_V1) { error = EINVAL; goto fail; } if (dsb->blkszbits < 9 || dsb->blkszbits > PAGE_SHIFT) { error = EINVAL; goto fail; } if (dsb->dirblkbits != 0) { error = EOPNOTSUPP; goto fail; } em->feature_compat = le32toh(dsb->feature_compat); em->feature_incompat = le32toh(dsb->feature_incompat); em->packed_nid = le64toh(dsb->packed_nid); em->extra_devices = erofs_sb_has_device_table(em) ? le16toh(dsb->extra_devices) : 0; unsupported = em->feature_incompat & ~EROFS_ALL_SUPPORTED_INCOMPAT; /* * Narrowly allow one extra combination: long xattr prefixes enabled * with non-plain prefix table stored in a packed inode, which adds * the FRAGMENTS (0x20) incompat bit. This is NOT a declaration of * general fragments support; per-inode data layout is still gated * by plain/inline checks in erofs_read_inode(). */ if (unsupported != 0) { if (unsupported != EROFS_FEATURE_INCOMPAT_FRAGMENTS || (em->feature_incompat & EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES) == 0 || (em->feature_compat & EROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX) != 0 || em->packed_nid == 0) { error = EOPNOTSUPP; goto fail; } } em->block_bits = dsb->blkszbits; em->block_size = 1u << em->block_bits; em->sb_size = 128 + dsb->sb_extslots * EROFS_SB_EXTSLOT_SIZE; if (em->sb_size > PAGE_SIZE - EROFS_SUPER_OFFSET) { error = EINVAL; goto fail; } em->meta_blkaddr = le32toh(dsb->meta_blkaddr); em->xattr_blkaddr = le32toh(dsb->xattr_blkaddr); em->xattr_prefix_start = le32toh(dsb->xattr_prefix_start); em->xattr_prefix_count = dsb->xattr_prefix_count; if (erofs_sb_has_ishare_xattrs(em) && dsb->ishare_xattr_prefix_id >= em->xattr_prefix_count) { error = EINTEGRITY; goto fail; } /* A non-zero reserved value disables the current name-filter format. */ if (erofs_sb_has_xattr_filter(em) && dsb->xattr_filter_reserved != 0) em->feature_compat &= ~EROFS_FEATURE_COMPAT_XATTR_FILTER; erofs_sb_blocks_root(dsb, em->feature_incompat, &em->blocks, &em->root_nid); em->dif0.blocks = em->blocks; error = erofs_validate_device_size(em, &em->dif0, em->dif0.blocks); if (error != 0) goto fail; error = erofs_superblock_csum_verify(em, dsb); if (error != 0) goto fail; em->inos = le64toh(dsb->inos); em->epoch = le64toh(dsb->epoch); em->fixed_nsec = le32toh(dsb->fixed_nsec); if (em->fixed_nsec >= 1000000000) { error = EINTEGRITY; goto fail; } error = erofs_load_generation_seed(em, em->sb_size, &em->generation_seed); if (error != 0) goto fail; if (em->packed_nid != 0 && erofs_nid_in_metabox(em->packed_nid)) { error = EINTEGRITY; goto fail; } if (erofs_sb_has_metabox(em)) { if (em->sb_size <= offsetof(struct erofs_super_block, metabox_nid)) { error = EINTEGRITY; goto fail; } em->metabox_nid = le64toh(dsb->metabox_nid); if (erofs_nid_in_metabox(em->metabox_nid)) { error = EINTEGRITY; goto fail; } } error = z_erofs_parse_cfgs(em, dsb); if (error != 0) goto fail; error = erofs_scan_devices(em, dsb, args, arg_count); if (error != 0) goto fail; if (erofs_sb_has_shared_ea_in_metabox(em) && !erofs_sb_has_metabox(em)) { error = EINTEGRITY; goto fail; } error = erofs_init_packed_inode(em); if (error != 0) goto fail; error = erofs_init_metabox_inode(em); if (error != 0) goto fail; error = erofs_xattr_prefixes_init(em); if (error != 0) goto fail; set_opt(&em->opt, POSIX_ACL); memcpy(em->volume_name, dsb->volume_name, 16); em->volume_name[16] = '\0'; erofs_brelse(buf); buf = NULL; mp->mnt_data = em; mp->mnt_stat.f_fsid.val[0] = dev2udev(em->dif0.devvp->v_rdev); mp->mnt_stat.f_fsid.val[1] = mp->mnt_vfc->vfc_typenum; MNT_ILOCK(mp); mp->mnt_flag |= MNT_LOCAL | MNT_RDONLY | MNT_ACLS; mp->mnt_kern_flag |= MNTK_LOOKUP_SHARED | MNTK_EXTENDED_SHARED | MNTK_USES_BCACHE; MNT_IUNLOCK(mp); return (0); fail: if (buf != NULL) erofs_brelse(buf); erofs_sb_free(em); return (error); } static int erofs_mount(struct mount *mp) { struct erofs_device_arg *args; struct erofs_device_info primary; char *fspec; unsigned int arg_count; int error, len; MNT_ILOCK(mp); mp->mnt_flag |= MNT_RDONLY; MNT_IUNLOCK(mp); if (mp->mnt_flag & MNT_UPDATE) { if (vfs_flagopt(mp->mnt_optnew, "export", NULL, 0)) return (0); return (EOPNOTSUPP); } args = NULL; arg_count = 0; error = erofs_parse_device_options(mp, &args, &arg_count); if (error != 0) return (error); if (vfs_filteropt(mp->mnt_optnew, erofs_opts) != 0) { erofs_free_device_args(args, arg_count); return (EINVAL); } fspec = NULL; error = vfs_getopt(mp->mnt_optnew, "from", (void **)&fspec, &len); if (error != 0 || fspec == NULL || len == 0 || fspec[len - 1] != '\0') { erofs_free_device_args(args, arg_count); return (EINVAL); } mp->mnt_iosize_max = MAXPHYS; error = erofs_open_device(NULL, fspec, &primary); if (error != 0) { erofs_free_device_args(args, arg_count); return (error); } erofs_update_iosize_max(mp, &primary); error = erofs_mountfs(&primary, mp, args, arg_count); erofs_free_device_args(args, arg_count); if (error != 0) return (error); vfs_mountedfrom(mp, fspec); return (erofs_statfs(mp, &mp->mnt_stat)); } static int erofs_root(struct mount *mp, int flags, struct vnode **vpp) { int error; error = erofs_vget(mp, MTOE(mp)->root_nid, flags, vpp); if (error != 0) vfs_mount_error(mp, "erofs: failed to load root nid %ju: error %d", (uintmax_t)MTOE(mp)->root_nid, error); return (error); } static int erofs_statfs(struct mount *mp, struct statfs *sbp) { struct erofs_mount *em; em = MTOE(mp); sbp->f_bsize = em->block_size; sbp->f_iosize = em->block_size; sbp->f_blocks = em->total_blocks; sbp->f_bfree = 0; sbp->f_bavail = 0; sbp->f_files = em->inos; sbp->f_ffree = 0; return (0); } static int erofs_unmount(struct mount *mp, int mntflags) { struct erofs_mount *em; int error, flags; flags = ((mntflags & MNT_FORCE) != 0) ? FORCECLOSE : 0; error = vflush(mp, 0, flags, curthread); if (error != 0) return (error); em = MTOE(mp); mp->mnt_data = NULL; erofs_sb_free(em); return (0); } static int erofs_vgetf(struct mount *mp, ino_t ino, int flags, struct vnode **vpp) { return (erofs_vget(mp, ino, flags, vpp)); } /* Persistent EROFS file handle to locked vnode. */ static int erofs_fhtovp(struct mount *mp, struct fid *fhp, int flags, struct vnode **vpp) { struct erofs_fid efid; struct erofs_node *en; struct vnode *vp; uint64_t nid; int error; *vpp = NULL; bzero(&efid, sizeof(efid)); memcpy(&efid, fhp, sizeof(efid)); if (efid.len != sizeof(efid) || efid.pad != 0) return (EINVAL); nid = ((uint64_t)efid.nid_hi << 32) | efid.nid_lo; if (!erofs_nid_is_valid(MTOE(mp), nid)) return (ESTALE); error = VFS_VGET(mp, (ino_t)nid, flags, &vp); if (error != 0) return (error); en = VTOE(vp); if (en->mode == 0 || en->nlink == 0 || en->nid != nid || en->generation != efid.gen) { vput(vp); return (ESTALE); } *vpp = vp; return (0); } static struct vfsops erofs_vfsops = { .vfs_fhtovp = erofs_fhtovp, .vfs_mount = erofs_mount, .vfs_root = erofs_root, .vfs_statfs = erofs_statfs, .vfs_unmount = erofs_unmount, .vfs_vget = erofs_vgetf, }; VFS_SET(erofs_vfsops, erofs, VFCF_READONLY); MODULE_DEPEND(erofs, acl_posix1e, 1, 1, 1); MODULE_DEPEND(erofs, zlib, 1, 1, 1); MODULE_VERSION(erofs, 1);