test code v1
This commit is contained in:
+468
@@ -0,0 +1,468 @@
|
||||
/* 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);
|
||||
Reference in New Issue
Block a user