571 lines
13 KiB
C
571 lines
13 KiB
C
/* SPDX-License-Identifier: BSD-2-Clause */
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/dirent.h>
|
|
#include <sys/extattr.h>
|
|
#include <sys/fnv_hash.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/libkern.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/namei.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/unistd.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/acl.h>
|
|
|
|
#include <vm/vnode_pager.h>
|
|
|
|
#include "internal.h"
|
|
#include "xattr.h"
|
|
|
|
static vop_inactive_t erofs_inactive;
|
|
static vop_reclaim_t erofs_reclaim;
|
|
|
|
static vop_readdir_t erofs_readdir;
|
|
static vop_readlink_t erofs_readlink;
|
|
|
|
static vop_open_t erofs_open;
|
|
static vop_read_t erofs_read;
|
|
static vop_bmap_t erofs_bmap;
|
|
|
|
static vop_getattr_t erofs_getattr;
|
|
static vop_setattr_t erofs_setattr;
|
|
static vop_access_t erofs_access;
|
|
static vop_pathconf_t erofs_pathconf;
|
|
|
|
static vop_getextattr_t erofs_getextattr;
|
|
static vop_listextattr_t erofs_listextattr;
|
|
static vop_deleteextattr_t erofs_deleteextattr;
|
|
static vop_setextattr_t erofs_setextattr;
|
|
static vop_getacl_t erofs_vop_getacl;
|
|
static vop_aclcheck_t erofs_aclcheck;
|
|
static vop_setacl_t erofs_setacl;
|
|
|
|
/* vop_fhtovp removed in FreeBSD 15.0 */
|
|
static vop_vptofh_t erofs_vptofh;
|
|
|
|
/* Access check: data nodes are read-only, but device/FIFO nodes are not denied
|
|
* writes. */
|
|
static int
|
|
erofs_access(struct vop_access_args *ap)
|
|
{
|
|
struct vnode *vp;
|
|
struct erofs_inode *vi;
|
|
struct acl *acl;
|
|
accmode_t accmode;
|
|
int error;
|
|
|
|
vp = ap->a_vp;
|
|
vi = VTOE(vp);
|
|
accmode = ap->a_accmode;
|
|
if ((accmode & VMODIFY_PERMS) != 0) {
|
|
switch (vp->v_type) {
|
|
case VDIR:
|
|
case VLNK:
|
|
case VREG:
|
|
return (EROFS);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
error = vfs_unixify_accmode(&accmode);
|
|
if (error != 0)
|
|
return (error);
|
|
if ((vp->v_mount->mnt_flag & MNT_ACLS) == 0)
|
|
return (vaccess(vp->v_type, vi->mode & ALLPERMS, vi->uid,
|
|
vi->gid, accmode, ap->a_cred));
|
|
|
|
acl = acl_alloc(M_WAITOK);
|
|
error = erofs_get_acl(vp, ACL_TYPE_ACCESS, acl);
|
|
if (error == 0)
|
|
error = vaccess_acl_posix1e(vp->v_type, vi->uid, vi->gid, acl,
|
|
accmode, ap->a_cred);
|
|
acl_free(acl);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Tell the generic pager that EROFS does not provide block-level bmap.
|
|
*
|
|
* Returning EOPNOTSUPP prevents the pager from assuming a bufobj/strategy is
|
|
* available, avoiding “No strategy for buffer” errors. VM will correctly
|
|
* fall back to the VOP_READ-based page-in path.
|
|
*/
|
|
static int
|
|
erofs_bmap(struct vop_bmap_args *ap)
|
|
{
|
|
(void)ap;
|
|
return (EOPNOTSUPP);
|
|
}
|
|
|
|
/* No dirty writeback on last ref release, so inactive is a no-op. */
|
|
static int
|
|
erofs_inactive(struct vop_inactive_args *ap)
|
|
{
|
|
(void)ap;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Create VM object when opening a regular vnode.
|
|
*
|
|
* The FreeBSD local vnode pager services synchronous and asynchronous faults
|
|
* through VOP_READ without requiring a block strategy method.
|
|
*/
|
|
static int
|
|
erofs_open(struct vop_open_args *ap)
|
|
{
|
|
struct vnode *vp;
|
|
struct erofs_inode *vi;
|
|
|
|
vp = ap->a_vp;
|
|
vi = VTOE(vp);
|
|
if (VN_ISDEV(vp))
|
|
return (EOPNOTSUPP);
|
|
if (vp->v_type == VREG)
|
|
vnode_create_vobject(vp, vi->size, ap->a_td);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
erofs_getattr(struct vop_getattr_args *ap)
|
|
{
|
|
struct vnode *vp;
|
|
struct erofs_inode *vi;
|
|
struct erofs_sb_info *sbi;
|
|
struct vattr *vap;
|
|
|
|
vp = ap->a_vp;
|
|
vi = VTOE(vp);
|
|
sbi = MTOE(vp->v_mount);
|
|
vap = ap->a_vap;
|
|
VATTR_NULL(vap);
|
|
vap->va_type = vp->v_type;
|
|
vap->va_mode = vi->mode & ALLPERMS;
|
|
vap->va_nlink = vi->nlink;
|
|
vap->va_uid = vi->uid;
|
|
vap->va_gid = vi->gid;
|
|
vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0];
|
|
vap->va_fileid = vi->nid;
|
|
vap->va_size = vi->size;
|
|
vap->va_blocksize = sbi->block_size;
|
|
vap->va_atime.tv_sec = vi->mtime;
|
|
vap->va_mtime.tv_sec = vi->mtime;
|
|
vap->va_ctime.tv_sec = vi->mtime;
|
|
vap->va_atime.tv_nsec = vi->mtime_nsec;
|
|
vap->va_mtime.tv_nsec = vi->mtime_nsec;
|
|
vap->va_ctime.tv_nsec = vi->mtime_nsec;
|
|
vap->va_gen = vi->generation;
|
|
vap->va_flags = 0;
|
|
vap->va_rdev = VN_ISDEV(vp) ? vi->rdev : NODEV;
|
|
if (vi->data_blocks > (UINT64_MAX >> sbi->blkszbits))
|
|
return (EINTEGRITY);
|
|
vap->va_bytes = vi->data_blocks << sbi->blkszbits;
|
|
vap->va_filerev = 0;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Read-only xattr get entry point.
|
|
*
|
|
* Delegates to erofs_getxattr() for two namespaces:
|
|
* - EXTATTR_NAMESPACE_USER
|
|
* - EXTATTR_NAMESPACE_SYSTEM (trusted.* / security.*)
|
|
*/
|
|
static int
|
|
erofs_getextattr(struct vop_getextattr_args *ap)
|
|
{
|
|
int error;
|
|
|
|
if (VN_ISDEV(ap->a_vp))
|
|
return (EOPNOTSUPP);
|
|
error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace, ap->a_cred,
|
|
ap->a_td, VREAD);
|
|
if (error != 0)
|
|
return (error);
|
|
if (ap->a_name == NULL || ap->a_name[0] == '\0')
|
|
return (EINVAL);
|
|
if (strlen(ap->a_name) > EXTATTR_MAXNAMELEN)
|
|
return (EINVAL);
|
|
|
|
switch (ap->a_attrnamespace) {
|
|
case EXTATTR_NAMESPACE_USER:
|
|
case EXTATTR_NAMESPACE_SYSTEM:
|
|
break;
|
|
default:
|
|
return (EOPNOTSUPP);
|
|
}
|
|
|
|
return (erofs_getxattr(ap->a_vp, ap->a_attrnamespace, ap->a_name,
|
|
ap->a_uio, ap->a_size));
|
|
}
|
|
|
|
/*
|
|
* Read-only xattr list entry point.
|
|
*
|
|
* Delegates to erofs_listxattr() for two namespaces:
|
|
* - EXTATTR_NAMESPACE_USER
|
|
* - EXTATTR_NAMESPACE_SYSTEM (trusted.* / security.*)
|
|
*/
|
|
static int
|
|
erofs_listextattr(struct vop_listextattr_args *ap)
|
|
{
|
|
int error;
|
|
|
|
if (VN_ISDEV(ap->a_vp))
|
|
return (EOPNOTSUPP);
|
|
error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace, ap->a_cred,
|
|
ap->a_td, VREAD);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
switch (ap->a_attrnamespace) {
|
|
case EXTATTR_NAMESPACE_USER:
|
|
case EXTATTR_NAMESPACE_SYSTEM:
|
|
break;
|
|
default:
|
|
return (EOPNOTSUPP);
|
|
}
|
|
|
|
return (erofs_listxattr(ap->a_vp, ap->a_attrnamespace, ap->a_uio,
|
|
ap->a_size));
|
|
}
|
|
|
|
static int
|
|
erofs_deleteextattr(struct vop_deleteextattr_args *ap)
|
|
{
|
|
if (VN_ISDEV(ap->a_vp))
|
|
return (EOPNOTSUPP);
|
|
return (EROFS);
|
|
}
|
|
|
|
static int
|
|
erofs_setextattr(struct vop_setextattr_args *ap)
|
|
{
|
|
if (VN_ISDEV(ap->a_vp))
|
|
return (EOPNOTSUPP);
|
|
return (EROFS);
|
|
}
|
|
|
|
/* EROFS is read-only; mutations on regular files/dirs/symlinks are denied, size
|
|
* changes on special vnodes are treated as no-ops per read-only convention. */
|
|
static int
|
|
erofs_setattr(struct vop_setattr_args *ap)
|
|
{
|
|
struct vnode *vp;
|
|
struct vattr *vap;
|
|
|
|
vp = ap->a_vp;
|
|
vap = ap->a_vap;
|
|
if (vap->va_mode != (mode_t)VNOVAL || vap->va_uid != (uid_t)VNOVAL ||
|
|
vap->va_gid != (gid_t)VNOVAL || vap->va_atime.tv_sec != VNOVAL ||
|
|
vap->va_atime.tv_nsec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL ||
|
|
vap->va_mtime.tv_nsec != VNOVAL || vap->va_flags != VNOVAL)
|
|
return (EROFS);
|
|
if (vap->va_size != VNOVAL) {
|
|
switch (vp->v_type) {
|
|
case VDIR:
|
|
return (EISDIR);
|
|
case VLNK:
|
|
case VREG:
|
|
return (EROFS);
|
|
case VCHR:
|
|
case VBLK:
|
|
case VSOCK:
|
|
case VFIFO:
|
|
case VNON:
|
|
case VBAD:
|
|
case VMARKER:
|
|
return (0);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
erofs_read(struct vop_read_args *ap)
|
|
{
|
|
switch (ap->a_vp->v_type) {
|
|
case VREG:
|
|
return (erofs_read_file(ap->a_vp, ap->a_uio, ap->a_ioflag));
|
|
case VDIR:
|
|
return (EISDIR);
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
|
|
static int
|
|
erofs_readdir(struct vop_readdir_args *ap)
|
|
{
|
|
if (ap->a_vp->v_type != VDIR)
|
|
return (ENOTDIR);
|
|
return (erofs_readdir_block(ap->a_vp, ap->a_uio, ap->a_eofflag,
|
|
ap->a_ncookies, ap->a_cookies));
|
|
}
|
|
|
|
static int
|
|
erofs_readlink(struct vop_readlink_args *ap)
|
|
{
|
|
if (ap->a_vp->v_type != VLNK)
|
|
return (EINVAL);
|
|
return (erofs_readlink_target(ap->a_vp, ap->a_uio));
|
|
}
|
|
|
|
static int
|
|
erofs_pathconf(struct vop_pathconf_args *ap)
|
|
{
|
|
switch (ap->a_name) {
|
|
case _PC_NAME_MAX:
|
|
*ap->a_retval = EROFS_NAME_LEN;
|
|
return (0);
|
|
case _PC_PATH_MAX:
|
|
*ap->a_retval = PATH_MAX;
|
|
return (0);
|
|
case _PC_FILESIZEBITS:
|
|
*ap->a_retval = 64;
|
|
return (0);
|
|
case _PC_LINK_MAX:
|
|
*ap->a_retval = INT_MAX;
|
|
return (0);
|
|
case _PC_CHOWN_RESTRICTED:
|
|
case _PC_NO_TRUNC:
|
|
*ap->a_retval = 1;
|
|
return (0);
|
|
case _PC_ACL_EXTENDED:
|
|
*ap->a_retval =
|
|
((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) != 0) ? 1 : 0;
|
|
return (0);
|
|
case _PC_ACL_PATH_MAX:
|
|
*ap->a_retval =
|
|
((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) != 0) ?
|
|
ACL_MAX_ENTRIES : 3;
|
|
return (0);
|
|
case _PC_ACL_NFS4:
|
|
*ap->a_retval = 0;
|
|
return (0);
|
|
default:
|
|
return (vop_stdpathconf(ap));
|
|
}
|
|
}
|
|
|
|
static int
|
|
erofs_vop_getacl(struct vop_getacl_args *ap)
|
|
{
|
|
if (VN_ISDEV(ap->a_vp))
|
|
return (EOPNOTSUPP);
|
|
if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0)
|
|
return (EOPNOTSUPP);
|
|
return (erofs_get_acl(ap->a_vp, ap->a_type, ap->a_aclp));
|
|
}
|
|
|
|
static int
|
|
erofs_aclcheck(struct vop_aclcheck_args *ap)
|
|
{
|
|
if (VN_ISDEV(ap->a_vp))
|
|
return (EOPNOTSUPP);
|
|
if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0)
|
|
return (EOPNOTSUPP);
|
|
if (ap->a_aclp == NULL)
|
|
return (EINVAL);
|
|
switch (ap->a_type) {
|
|
case ACL_TYPE_ACCESS:
|
|
break;
|
|
case ACL_TYPE_DEFAULT:
|
|
if (ap->a_vp->v_type != VDIR)
|
|
return (EINVAL);
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
return (acl_posix1e_check(ap->a_aclp));
|
|
}
|
|
|
|
static int
|
|
erofs_setacl(struct vop_setacl_args *ap)
|
|
{
|
|
if (VN_ISDEV(ap->a_vp))
|
|
return (EOPNOTSUPP);
|
|
return (EROFS);
|
|
}
|
|
|
|
static u_int
|
|
erofs_vfs_hash(erofs_nid_t nid)
|
|
{
|
|
|
|
return (fnv_32_buf(&nid, sizeof(nid), FNV1_32_INIT));
|
|
}
|
|
|
|
static int
|
|
erofs_vfs_hash_cmp(struct vnode *vp, void *pnid)
|
|
{
|
|
struct erofs_inode *vi;
|
|
|
|
vi = VTOE(vp);
|
|
return (vi == NULL || vi->nid != *(erofs_nid_t *)pnid);
|
|
}
|
|
|
|
static void
|
|
erofs_fill_vnode(struct erofs_sb_info *sbi, struct vnode *vp,
|
|
const struct erofs_inode *vi)
|
|
{
|
|
vp->v_type = vi->vtype;
|
|
if (vp->v_type == VFIFO)
|
|
vp->v_op = &erofs_fifoops;
|
|
if (vi->nid == sbi->root_nid)
|
|
vp->v_vflag |= VV_ROOT;
|
|
}
|
|
|
|
/*
|
|
* Get vnode by raw on-disk nid. The raw nid is also the FreeBSD fileid and
|
|
* hash identity, so the metabox selector bit remains collision-free.
|
|
* Uses the standard FreeBSD vfs_hash API.
|
|
* (Linux equivalent: erofs_iget in Linux's inode.c)
|
|
*/
|
|
int
|
|
erofs_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp)
|
|
{
|
|
struct erofs_sb_info *sbi;
|
|
struct erofs_inode *vi;
|
|
struct thread *td;
|
|
struct vnode *vp;
|
|
erofs_nid_t nid;
|
|
u_int hash;
|
|
bool shared;
|
|
int error;
|
|
|
|
td = curthread;
|
|
nid = (uint64_t)ino;
|
|
shared = (flags & LK_TYPE_MASK) == LK_SHARED;
|
|
hash = erofs_vfs_hash(nid);
|
|
error = vfs_hash_get(mp, hash, flags, td, vpp, erofs_vfs_hash_cmp,
|
|
&nid);
|
|
if (error != 0 || *vpp != NULL)
|
|
return (error);
|
|
|
|
sbi = MTOE(mp);
|
|
vi = malloc(sizeof(*vi), M_EROFS, M_WAITOK | M_ZERO);
|
|
error = getnewvnode("erofs", mp, &erofs_vnodeops, &vp);
|
|
if (error != 0) {
|
|
free(vi, M_EROFS);
|
|
*vpp = NULL;
|
|
return (error);
|
|
}
|
|
vp->v_data = vi;
|
|
vi->nid = nid;
|
|
lockmgr(vp->v_vnlock, LK_EXCLUSIVE, NULL);
|
|
error = insmntque(vp, mp);
|
|
if (error != 0) {
|
|
free(vi, M_EROFS);
|
|
*vpp = NULL;
|
|
return (error);
|
|
}
|
|
error = vfs_hash_insert(vp, hash, flags, td, vpp, erofs_vfs_hash_cmp,
|
|
&nid);
|
|
if (error != 0 || *vpp != NULL)
|
|
return (error);
|
|
|
|
error = erofs_read_inode(sbi, nid, vi);
|
|
if (error != 0) {
|
|
*vpp = NULL;
|
|
vgone(vp);
|
|
vput(vp);
|
|
return (error);
|
|
}
|
|
erofs_xattr_cache_init(vi);
|
|
erofs_fill_vnode(sbi, vp, vi);
|
|
vn_set_state(vp, VSTATE_CONSTRUCTED);
|
|
if (shared)
|
|
VOP_LOCK(vp, LK_DOWNGRADE);
|
|
*vpp = vp;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
erofs_reclaim(struct vop_reclaim_args *ap)
|
|
{
|
|
struct vnode *vp;
|
|
struct erofs_inode *vi;
|
|
|
|
vp = ap->a_vp;
|
|
vi = VTOE(vp);
|
|
if (vi != NULL) {
|
|
vfs_hash_remove(vp);
|
|
erofs_xattr_cache_fini(MTOE(vp->v_mount), vi);
|
|
free(vi, M_EROFS);
|
|
vp->v_data = NULL;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Vnode pointer to persistent EROFS file handle. */
|
|
static int
|
|
erofs_vptofh(struct vop_vptofh_args *ap)
|
|
{
|
|
struct erofs_fid efid;
|
|
struct erofs_inode *vi;
|
|
|
|
vi = VTOE(ap->a_vp);
|
|
bzero(&efid, sizeof(efid));
|
|
efid.len = sizeof(efid);
|
|
efid.nid_hi = vi->nid >> 32;
|
|
efid.nid_lo = vi->nid;
|
|
efid.gen = vi->generation;
|
|
memcpy(ap->a_fhp, &efid, sizeof(efid));
|
|
return (0);
|
|
}
|
|
|
|
struct vop_vector erofs_vnodeops = {
|
|
.vop_default = &default_vnodeops,
|
|
.vop_inactive = erofs_inactive,
|
|
.vop_reclaim = erofs_reclaim,
|
|
|
|
.vop_lookup = vfs_cache_lookup,
|
|
.vop_cachedlookup = erofs_lookup,
|
|
.vop_readdir = erofs_readdir,
|
|
.vop_readlink = erofs_readlink,
|
|
|
|
.vop_open = erofs_open,
|
|
.vop_read = erofs_read,
|
|
.vop_bmap = erofs_bmap,
|
|
.vop_getpages = vnode_pager_local_getpages,
|
|
.vop_getpages_async = vnode_pager_local_getpages_async,
|
|
|
|
.vop_getattr = erofs_getattr,
|
|
.vop_setattr = erofs_setattr,
|
|
.vop_access = erofs_access,
|
|
.vop_pathconf = erofs_pathconf,
|
|
|
|
.vop_getextattr = erofs_getextattr,
|
|
.vop_listextattr = erofs_listextattr,
|
|
.vop_deleteextattr = erofs_deleteextattr,
|
|
.vop_setextattr = erofs_setextattr,
|
|
.vop_getacl = erofs_vop_getacl,
|
|
.vop_aclcheck = erofs_aclcheck,
|
|
.vop_setacl = erofs_setacl,
|
|
|
|
.vop_vptofh = erofs_vptofh,
|
|
};
|
|
VFS_VOP_VECTOR_REGISTER(erofs_vnodeops);
|
|
|
|
struct vop_vector erofs_fifoops = {
|
|
.vop_default = &fifo_specops,
|
|
.vop_access = erofs_access,
|
|
.vop_aclcheck = erofs_aclcheck,
|
|
.vop_deleteextattr = erofs_deleteextattr,
|
|
.vop_getacl = erofs_vop_getacl,
|
|
.vop_getextattr = erofs_getextattr,
|
|
.vop_getattr = erofs_getattr,
|
|
.vop_listextattr = erofs_listextattr,
|
|
.vop_pathconf = erofs_pathconf,
|
|
.vop_reclaim = erofs_reclaim,
|
|
.vop_setacl = erofs_setacl,
|
|
.vop_setattr = erofs_setattr,
|
|
.vop_setextattr = erofs_setextattr,
|
|
.vop_vptofh = erofs_vptofh,
|
|
};
|
|
VFS_VOP_VECTOR_REGISTER(erofs_fifoops);
|