Files
erofs-freebsd-out-tree/erofs_vnops.c
T
2026-08-14 12:01:38 +02:00

469 lines
11 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/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_access_t erofs_access;
static vop_aclcheck_t erofs_aclcheck;
static vop_bmap_t erofs_bmap;
static vop_deleteextattr_t erofs_deleteextattr;
/* vop_fhtovp removed in FreeBSD 15.0 */
static vop_getacl_t erofs_vop_getacl;
static vop_getextattr_t erofs_getextattr;
static vop_getattr_t erofs_getattr;
static vop_inactive_t erofs_inactive;
static vop_listextattr_t erofs_listextattr;
static vop_open_t erofs_open;
static vop_pathconf_t erofs_pathconf;
static vop_read_t erofs_read;
static vop_readdir_t erofs_readdir;
static vop_readlink_t erofs_readlink;
static vop_reclaim_t erofs_reclaim;
static vop_setacl_t erofs_setacl;
static vop_setattr_t erofs_setattr;
static vop_setextattr_t erofs_setextattr;
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_node *en;
struct acl *acl;
accmode_t accmode;
int error;
vp = ap->a_vp;
en = 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, en->mode & ALLPERMS, en->uid,
en->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, en->uid, en->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_node *en;
vp = ap->a_vp;
en = VTOE(vp);
if (VN_ISDEV(vp))
return (EOPNOTSUPP);
if (vp->v_type == VREG) {
if (vnode_create_vobject(vp, en->size, ap->a_td) != 0)
return (ENOMEM);
}
return (0);
}
static int
erofs_getattr(struct vop_getattr_args *ap)
{
struct vnode *vp;
struct erofs_node *en;
struct erofs_mount *em;
struct vattr *vap;
vp = ap->a_vp;
en = VTOE(vp);
em = MTOE(vp->v_mount);
vap = ap->a_vap;
VATTR_NULL(vap);
vap->va_type = vp->v_type;
vap->va_mode = en->mode & ALLPERMS;
vap->va_nlink = en->nlink;
vap->va_uid = en->uid;
vap->va_gid = en->gid;
vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0];
vap->va_fileid = en->nid;
vap->va_size = en->size;
vap->va_blocksize = em->block_size;
vap->va_atime.tv_sec = en->mtime;
vap->va_mtime.tv_sec = en->mtime;
vap->va_ctime.tv_sec = en->mtime;
vap->va_atime.tv_nsec = en->mtime_nsec;
vap->va_mtime.tv_nsec = en->mtime_nsec;
vap->va_ctime.tv_nsec = en->mtime_nsec;
vap->va_gen = en->generation;
vap->va_flags = 0;
vap->va_rdev = VN_ISDEV(vp) ? en->rdev : NODEV;
if (en->data_blocks > (UINT64_MAX >> em->block_bits))
return (EINTEGRITY);
vap->va_bytes = en->data_blocks << em->block_bits;
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 int
erofs_reclaim(struct vop_reclaim_args *ap)
{
struct vnode *vp;
struct erofs_node *en;
vp = ap->a_vp;
en = VTOE(vp);
if (en != NULL) {
vfs_hash_remove(vp);
free(en, 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_node *en;
en = VTOE(ap->a_vp);
bzero(&efid, sizeof(efid));
efid.len = sizeof(efid);
efid.nid_hi = en->nid >> 32;
efid.nid_lo = en->nid;
efid.gen = en->generation;
memcpy(ap->a_fhp, &efid, sizeof(efid));
return (0);
}
struct vop_vector erofs_vnodeops = {
.vop_default = &default_vnodeops,
.vop_access = erofs_access,
.vop_aclcheck = erofs_aclcheck,
.vop_bmap = erofs_bmap,
.vop_cachedlookup = erofs_lookup,
.vop_deleteextattr = erofs_deleteextattr,
.vop_getacl = erofs_vop_getacl,
.vop_getextattr = erofs_getextattr,
.vop_getattr = erofs_getattr,
.vop_getpages = vnode_pager_local_getpages,
.vop_getpages_async = vnode_pager_local_getpages_async,
.vop_inactive = erofs_inactive,
.vop_listextattr = erofs_listextattr,
.vop_lookup = vfs_cache_lookup,
.vop_open = erofs_open,
.vop_pathconf = erofs_pathconf,
.vop_read = erofs_read,
.vop_readdir = erofs_readdir,
.vop_readlink = erofs_readlink,
.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_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);