/* * Copyright (c) 2000-2022 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */ /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)kpi_vfs.c */ /* * NOTICE: This file was modified by SPARTA, Inc. in 2005 to introduce * support for mandatory and extensible security protections. This notice * is included in support of clause 2.2 (b) of the Apple Public License, * Version 2.0. */ /* * External virtual filesystem routines */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CONFIG_MACF #include #endif #if NULLFS #include #endif #include #define ESUCCESS 0 #undef mount_t #undef vnode_t #define COMPAT_ONLY #define NATIVE_XATTR(VP) \ ((VP)->v_mount ? (VP)->v_mount->mnt_kern_flag & MNTK_EXTENDED_ATTRS : 0) #if CONFIG_APPLEDOUBLE static void xattrfile_remove(vnode_t dvp, const char *basename, vfs_context_t ctx, int force); static void xattrfile_setattr(vnode_t dvp, const char * basename, struct vnode_attr * vap, vfs_context_t ctx); #endif /* CONFIG_APPLEDOUBLE */ extern lck_rw_t rootvnode_rw_lock; static errno_t post_rename(vnode_t fdvp, vnode_t fvp, vnode_t tdvp, vnode_t tvp); KALLOC_TYPE_DEFINE(KT_VFS_CONTEXT, struct vfs_context, KT_PRIV_ACCT); extern int fstypenumstart; char vfs_typenum_arr[13]; LCK_GRP_DECLARE(typenum_arr_grp, "typenum array group"); LCK_MTX_DECLARE(vfs_typenum_mtx, &typenum_arr_grp); /* * vnode_setneedinactive * * Description: Indicate that when the last iocount on this vnode goes away, * and the usecount is also zero, we should inform the filesystem * via VNOP_INACTIVE. * * Parameters: vnode_t vnode to mark * * Returns: Nothing * * Notes: Notably used when we're deleting a file--we need not have a * usecount, so VNOP_INACTIVE may not get called by anyone. We * want it called when we drop our iocount. */ void vnode_setneedinactive(vnode_t vp) { cache_purge(vp); vnode_lock_spin(vp); vp->v_lflag |= VL_NEEDINACTIVE; vnode_unlock(vp); } /* ====================================================================== */ /* ************ EXTERNAL KERNEL APIS ********************************** */ /* ====================================================================== */ /* * implementations of exported VFS operations */ int VFS_MOUNT(mount_t mp, vnode_t devvp, user_addr_t data, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_mount == 0)) { return ENOTSUP; } if (vfs_context_is64bit(ctx)) { if (vfs_64bitready(mp)) { error = (*mp->mnt_op->vfs_mount)(mp, devvp, data, ctx); } else { error = ENOTSUP; } } else { error = (*mp->mnt_op->vfs_mount)(mp, devvp, data, ctx); } return error; } int VFS_START(mount_t mp, int flags, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_start == 0)) { return ENOTSUP; } error = (*mp->mnt_op->vfs_start)(mp, flags, ctx); return error; } int VFS_UNMOUNT(mount_t mp, int flags, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_unmount == 0)) { return ENOTSUP; } error = (*mp->mnt_op->vfs_unmount)(mp, flags, ctx); return error; } /* * Returns: 0 Success * ENOTSUP Not supported * :ENOENT * :??? * * Note: The return codes from the underlying VFS's root routine can't * be fully enumerated here, since third party VFS authors may not * limit their error returns to the ones documented here, even * though this may result in some programs functioning incorrectly. * * The return codes documented above are those which may currently * be returned by HFS from hfs_vfs_root, which is a simple wrapper * for a call to hfs_vget on the volume mount point, not including * additional error codes which may be propagated from underlying * routines called by hfs_vget. */ int VFS_ROOT(mount_t mp, struct vnode ** vpp, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_root == 0)) { return ENOTSUP; } if (ctx == NULL) { ctx = vfs_context_current(); } error = (*mp->mnt_op->vfs_root)(mp, vpp, ctx); return error; } int VFS_QUOTACTL(mount_t mp, int cmd, uid_t uid, caddr_t datap, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_quotactl == 0)) { return ENOTSUP; } error = (*mp->mnt_op->vfs_quotactl)(mp, cmd, uid, datap, ctx); return error; } int VFS_GETATTR(mount_t mp, struct vfs_attr *vfa, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_getattr == 0)) { return ENOTSUP; } if (ctx == NULL) { ctx = vfs_context_current(); } error = (*mp->mnt_op->vfs_getattr)(mp, vfa, ctx); return error; } int VFS_SETATTR(mount_t mp, struct vfs_attr *vfa, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_setattr == 0)) { return ENOTSUP; } if (ctx == NULL) { ctx = vfs_context_current(); } error = (*mp->mnt_op->vfs_setattr)(mp, vfa, ctx); return error; } int VFS_SYNC(mount_t mp, int flags, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_sync == 0)) { return ENOTSUP; } if (ctx == NULL) { ctx = vfs_context_current(); } error = (*mp->mnt_op->vfs_sync)(mp, flags, ctx); return error; } int VFS_VGET(mount_t mp, ino64_t ino, struct vnode **vpp, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_vget == 0)) { return ENOTSUP; } if (ctx == NULL) { ctx = vfs_context_current(); } error = (*mp->mnt_op->vfs_vget)(mp, ino, vpp, ctx); return error; } int VFS_FHTOVP(mount_t mp, int fhlen, unsigned char *fhp, vnode_t *vpp, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_fhtovp == 0)) { return ENOTSUP; } if (ctx == NULL) { ctx = vfs_context_current(); } error = (*mp->mnt_op->vfs_fhtovp)(mp, fhlen, fhp, vpp, ctx); return error; } int VFS_VPTOFH(struct vnode *vp, int *fhlenp, unsigned char *fhp, vfs_context_t ctx) { int error; if ((vp->v_mount == dead_mountp) || (vp->v_mount->mnt_op->vfs_vptofh == 0)) { return ENOTSUP; } if (ctx == NULL) { ctx = vfs_context_current(); } error = (*vp->v_mount->mnt_op->vfs_vptofh)(vp, fhlenp, fhp, ctx); return error; } int VFS_IOCTL(struct mount *mp, u_long command, caddr_t data, int flags, vfs_context_t context) { if (mp == dead_mountp || !mp->mnt_op->vfs_ioctl) { return ENOTSUP; } return mp->mnt_op->vfs_ioctl(mp, command, data, flags, context ?: vfs_context_current()); } int VFS_VGET_SNAPDIR(mount_t mp, vnode_t *vpp, vfs_context_t ctx) { int error; if ((mp == dead_mountp) || (mp->mnt_op->vfs_vget_snapdir == 0)) { return ENOTSUP; } if (ctx == NULL) { ctx = vfs_context_current(); } error = (*mp->mnt_op->vfs_vget_snapdir)(mp, vpp, ctx); return error; } /* returns the cached throttle mask for the mount_t */ uint64_t vfs_throttle_mask(mount_t mp) { return mp->mnt_throttle_mask; } /* returns a copy of vfs type name for the mount_t */ void vfs_name(mount_t mp, char *buffer) { strncpy(buffer, mp->mnt_vtable->vfc_name, MFSNAMELEN); } /* returns vfs type number for the mount_t */ int vfs_typenum(mount_t mp) { return mp->mnt_vtable->vfc_typenum; } /* Safe to cast to "struct label*"; returns "void*" to limit dependence of mount.h on security headers. */ void* vfs_mntlabel(mount_t mp) { return (void*)mac_mount_label(mp); } uint64_t vfs_mount_id(mount_t mp) { return mp->mnt_mount_id; } /* returns command modifier flags of mount_t ie. MNT_CMDFLAGS */ uint64_t vfs_flags(mount_t mp) { return (uint64_t)(mp->mnt_flag & (MNT_CMDFLAGS | MNT_VISFLAGMASK)); } /* set any of the command modifier flags(MNT_CMDFLAGS) in mount_t */ void vfs_setflags(mount_t mp, uint64_t flags) { uint32_t lflags = (uint32_t)(flags & (MNT_CMDFLAGS | MNT_VISFLAGMASK)); mount_lock(mp); mp->mnt_flag |= lflags; mount_unlock(mp); } /* clear any of the command modifier flags(MNT_CMDFLAGS) in mount_t */ void vfs_clearflags(mount_t mp, uint64_t flags) { uint32_t lflags = (uint32_t)(flags & (MNT_CMDFLAGS | MNT_VISFLAGMASK)); mount_lock(mp); mp->mnt_flag &= ~lflags; mount_unlock(mp); } /* Is the mount_t ronly and upgrade read/write requested? */ int vfs_iswriteupgrade(mount_t mp) /* ronly && MNTK_WANTRDWR */ { return (mp->mnt_flag & MNT_RDONLY) && (mp->mnt_kern_flag & MNTK_WANTRDWR); } /* Is the mount_t mounted ronly */ int vfs_isrdonly(mount_t mp) { return mp->mnt_flag & MNT_RDONLY; } /* Is the mount_t mounted for filesystem synchronous writes? */ int vfs_issynchronous(mount_t mp) { return mp->mnt_flag & MNT_SYNCHRONOUS; } /* Is the mount_t mounted read/write? */ int vfs_isrdwr(mount_t mp) { return (mp->mnt_flag & MNT_RDONLY) == 0; } /* Is mount_t marked for update (ie MNT_UPDATE) */ int vfs_isupdate(mount_t mp) { return mp->mnt_flag & MNT_UPDATE; } /* Is mount_t marked for reload (ie MNT_RELOAD) */ int vfs_isreload(mount_t mp) { return (mp->mnt_flag & MNT_UPDATE) && (mp->mnt_flag & MNT_RELOAD); } /* Is mount_t marked for forced unmount (ie MNT_FORCE or MNTK_FRCUNMOUNT) */ int vfs_isforce(mount_t mp) { if (mp->mnt_lflag & MNT_LFORCE) { return 1; } else { return 0; } } int vfs_isunmount(mount_t mp) { if ((mp->mnt_lflag & MNT_LUNMOUNT)) { return 1; } else { return 0; } } int vfs_64bitready(mount_t mp) { if ((mp->mnt_vtable->vfc_vfsflags & VFC_VFS64BITREADY)) { return 1; } else { return 0; } } int vfs_authcache_ttl(mount_t mp) { if ((mp->mnt_kern_flag & (MNTK_AUTH_OPAQUE | MNTK_AUTH_CACHE_TTL))) { return mp->mnt_authcache_ttl; } else { return CACHED_RIGHT_INFINITE_TTL; } } void vfs_setauthcache_ttl(mount_t mp, int ttl) { mount_lock(mp); mp->mnt_kern_flag |= MNTK_AUTH_CACHE_TTL; mp->mnt_authcache_ttl = ttl; mount_unlock(mp); } void vfs_clearauthcache_ttl(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag &= ~MNTK_AUTH_CACHE_TTL; /* * back to the default TTL value in case * MNTK_AUTH_OPAQUE is set on this mount */ mp->mnt_authcache_ttl = CACHED_LOOKUP_RIGHT_TTL; mount_unlock(mp); } int vfs_authopaque(mount_t mp) { if ((mp->mnt_kern_flag & MNTK_AUTH_OPAQUE)) { return 1; } else { return 0; } } int vfs_authopaqueaccess(mount_t mp) { if ((mp->mnt_kern_flag & MNTK_AUTH_OPAQUE_ACCESS)) { return 1; } else { return 0; } } void vfs_setauthopaque(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag |= MNTK_AUTH_OPAQUE; mount_unlock(mp); } void vfs_setauthopaqueaccess(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag |= MNTK_AUTH_OPAQUE_ACCESS; mount_unlock(mp); } void vfs_clearauthopaque(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag &= ~MNTK_AUTH_OPAQUE; mount_unlock(mp); } void vfs_clearauthopaqueaccess(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag &= ~MNTK_AUTH_OPAQUE_ACCESS; mount_unlock(mp); } void vfs_setextendedsecurity(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag |= MNTK_EXTENDED_SECURITY; mount_unlock(mp); } void vfs_setmntsystem(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag |= MNTK_SYSTEM; mount_unlock(mp); } void vfs_setmntsystemdata(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag |= MNTK_SYSTEMDATA; mount_unlock(mp); } void vfs_setmntswap(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag |= (MNTK_SYSTEM | MNTK_SWAP_MOUNT); mount_unlock(mp); } void vfs_clearextendedsecurity(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag &= ~MNTK_EXTENDED_SECURITY; mount_unlock(mp); } void vfs_setnoswap(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag |= MNTK_NOSWAP; mount_unlock(mp); } void vfs_clearnoswap(mount_t mp) { mount_lock(mp); mp->mnt_kern_flag &= ~MNTK_NOSWAP; mount_unlock(mp); } int vfs_extendedsecurity(mount_t mp) { return mp->mnt_kern_flag & MNTK_EXTENDED_SECURITY; } /* returns the max size of short symlink in this mount_t */ uint32_t vfs_maxsymlen(mount_t mp) { return mp->mnt_maxsymlinklen; } /* set max size of short symlink on mount_t */ void vfs_setmaxsymlen(mount_t mp, uint32_t symlen) { mp->mnt_maxsymlinklen = symlen; } boolean_t vfs_is_basesystem(mount_t mp) { return ((mp->mnt_supl_kern_flag & MNTK_SUPL_BASESYSTEM) == 0) ? false : true; } /* return a pointer to the RO vfs_statfs associated with mount_t */ struct vfsstatfs * vfs_statfs(mount_t mp) { return &mp->mnt_vfsstat; } int vfs_getattr(mount_t mp, struct vfs_attr *vfa, vfs_context_t ctx) { int error; if ((error = VFS_GETATTR(mp, vfa, ctx)) != 0) { return error; } /* * If we have a filesystem create time, use it to default some others. */ if (VFSATTR_IS_SUPPORTED(vfa, f_create_time)) { if (VFSATTR_IS_ACTIVE(vfa, f_modify_time) && !VFSATTR_IS_SUPPORTED(vfa, f_modify_time)) { VFSATTR_RETURN(vfa, f_modify_time, vfa->f_create_time); } } return 0; } int vfs_setattr(mount_t mp, struct vfs_attr *vfa, vfs_context_t ctx) { int error; /* * with a read-only system volume, we need to allow rename of the root volume * even if it's read-only. Don't return EROFS here if setattr changes only * the volume name */ if (vfs_isrdonly(mp) && !((strcmp(mp->mnt_vfsstat.f_fstypename, "apfs") == 0) && (vfa->f_active == VFSATTR_f_vol_name))) { return EROFS; } error = VFS_SETATTR(mp, vfa, ctx); /* * If we had alternate ways of setting vfs attributes, we'd * fall back here. */ return error; } /* return the private data handle stored in mount_t */ void * vfs_fsprivate(mount_t mp) { return mp->mnt_data; } /* set the private data handle in mount_t */ void vfs_setfsprivate(mount_t mp, void *mntdata) { mount_lock(mp); mp->mnt_data = mntdata; mount_unlock(mp); } /* query whether the mount point supports native EAs */ int vfs_nativexattrs(mount_t mp) { return mp->mnt_kern_flag & MNTK_EXTENDED_ATTRS; } /* * return the block size of the underlying * device associated with mount_t */ int vfs_devblocksize(mount_t mp) { return mp->mnt_devblocksize; } /* * Returns vnode with an iocount that must be released with vnode_put() */ vnode_t vfs_vnodecovered(mount_t mp) { vnode_t vp = mp->mnt_vnodecovered; if ((vp == NULL) || (vnode_getwithref(vp) != 0)) { return NULL; } else { return vp; } } /* * Returns device vnode backing a mountpoint with an iocount (if valid vnode exists). * The iocount must be released with vnode_put(). Note that this KPI is subtle * with respect to the validity of using this device vnode for anything substantial * (which is discouraged). If commands are sent to the device driver without * taking proper steps to ensure that the device is still open, chaos may ensue. * Similarly, this routine should only be called if there is some guarantee that * the mount itself is still valid. */ vnode_t vfs_devvp(mount_t mp) { vnode_t vp = mp->mnt_devvp; if ((vp != NULLVP) && (vnode_get(vp) == 0)) { return vp; } return NULLVP; } /* * return the io attributes associated with mount_t */ void vfs_ioattr(mount_t mp, struct vfsioattr *ioattrp) { ioattrp->io_reserved[0] = NULL; ioattrp->io_reserved[1] = NULL; if (mp == NULL) { ioattrp->io_maxreadcnt = MAXPHYS; ioattrp->io_maxwritecnt = MAXPHYS; ioattrp->io_segreadcnt = 32; ioattrp->io_segwritecnt = 32; ioattrp->io_maxsegreadsize = MAXPHYS; ioattrp->io_maxsegwritesize = MAXPHYS; ioattrp->io_devblocksize = DEV_BSIZE; ioattrp->io_flags = 0; ioattrp->io_max_swappin_available = 0; } else { ioattrp->io_maxreadcnt = mp->mnt_maxreadcnt; ioattrp->io_maxwritecnt = mp->mnt_maxwritecnt; ioattrp->io_segreadcnt = mp->mnt_segreadcnt; ioattrp->io_segwritecnt = mp->mnt_segwritecnt; ioattrp->io_maxsegreadsize = mp->mnt_maxsegreadsize; ioattrp->io_maxsegwritesize = mp->mnt_maxsegwritesize; ioattrp->io_devblocksize = mp->mnt_devblocksize; ioattrp->io_flags = mp->mnt_ioflags; ioattrp->io_max_swappin_available = mp->mnt_max_swappin_available; } } /* * set the IO attributes associated with mount_t */ void vfs_setioattr(mount_t mp, struct vfsioattr * ioattrp) { if (mp == NULL) { return; } mp->mnt_maxreadcnt = ioattrp->io_maxreadcnt; mp->mnt_maxwritecnt = ioattrp->io_maxwritecnt; mp->mnt_segreadcnt = ioattrp->io_segreadcnt; mp->mnt_segwritecnt = ioattrp->io_segwritecnt; mp->mnt_maxsegreadsize = ioattrp->io_maxsegreadsize; mp->mnt_maxsegwritesize = ioattrp->io_maxsegwritesize; mp->mnt_devblocksize = ioattrp->io_devblocksize; mp->mnt_ioflags = ioattrp->io_flags; mp->mnt_max_swappin_available = ioattrp->io_max_swappin_available; } /* * Add a new filesystem into the kernel specified in passed in * vfstable structure. It fills in the vnode * dispatch vector that is to be passed to when vnodes are created. * It returns a handle which is to be used to when the FS is to be removed */ typedef int (*PFI)(void *); extern int vfs_opv_numops; errno_t vfs_fsadd(struct vfs_fsentry *vfe, vfstable_t *handle) { struct vfstable *newvfstbl = NULL; int i, j; int(***opv_desc_vector_p)(void *); int(**opv_desc_vector)(void *); const struct vnodeopv_entry_desc *opve_descp; int desccount; int descsize; PFI *descptr; /* * This routine is responsible for all the initialization that would * ordinarily be done as part of the system startup; */ if (vfe == (struct vfs_fsentry *)0) { return EINVAL; } desccount = vfe->vfe_vopcnt; if ((desccount <= 0) || ((desccount > 8)) || (vfe->vfe_vfsops == (struct vfsops *)NULL) || (vfe->vfe_opvdescs == (struct vnodeopv_desc **)NULL)) { return EINVAL; } /* Non-threadsafe filesystems are not supported */ if ((vfe->vfe_flags & (VFS_TBLTHREADSAFE | VFS_TBLFSNODELOCK)) == 0) { return EINVAL; } newvfstbl = kalloc_type(struct vfstable, Z_WAITOK | Z_ZERO); newvfstbl->vfc_vfsops = vfe->vfe_vfsops; strncpy(&newvfstbl->vfc_name[0], vfe->vfe_fsname, MFSNAMELEN); if ((vfe->vfe_flags & VFS_TBLNOTYPENUM)) { int tmp; int found = 0; lck_mtx_lock(&vfs_typenum_mtx); for (tmp = fstypenumstart; tmp < OID_AUTO_START; tmp++) { if (isclr(vfs_typenum_arr, tmp)) { newvfstbl->vfc_typenum = tmp; setbit(vfs_typenum_arr, tmp); found = 1; break; } } if (!found) { lck_mtx_unlock(&vfs_typenum_mtx); return EINVAL; } if (maxvfstypenum < OID_AUTO_START) { /* getvfsbyname checks up to but not including maxvfstypenum */ maxvfstypenum = newvfstbl->vfc_typenum + 1; } lck_mtx_unlock(&vfs_typenum_mtx); } else { newvfstbl->vfc_typenum = vfe->vfe_fstypenum; lck_mtx_lock(&vfs_typenum_mtx); setbit(vfs_typenum_arr, newvfstbl->vfc_typenum); if (newvfstbl->vfc_typenum >= maxvfstypenum) { maxvfstypenum = newvfstbl->vfc_typenum + 1; } lck_mtx_unlock(&vfs_typenum_mtx); } newvfstbl->vfc_refcount = 0; newvfstbl->vfc_flags = 0; newvfstbl->vfc_mountroot = NULL; newvfstbl->vfc_next = NULL; newvfstbl->vfc_vfsflags = 0; if (vfe->vfe_flags & VFS_TBL64BITREADY) { newvfstbl->vfc_vfsflags |= VFC_VFS64BITREADY; } if (vfe->vfe_flags & VFS_TBLVNOP_PAGEINV2) { newvfstbl->vfc_vfsflags |= VFC_VFSVNOP_PAGEINV2; } if (vfe->vfe_flags & VFS_TBLVNOP_PAGEOUTV2) { newvfstbl->vfc_vfsflags |= VFC_VFSVNOP_PAGEOUTV2; } if ((vfe->vfe_flags & VFS_TBLLOCALVOL) == VFS_TBLLOCALVOL) { newvfstbl->vfc_flags |= MNT_LOCAL; } if ((vfe->vfe_flags & VFS_TBLLOCALVOL) && (vfe->vfe_flags & VFS_TBLGENERICMNTARGS) == 0) { newvfstbl->vfc_vfsflags |= VFC_VFSLOCALARGS; } else { newvfstbl->vfc_vfsflags |= VFC_VFSGENERICARGS; } if (vfe->vfe_flags & VFS_TBLNATIVEXATTR) { newvfstbl->vfc_vfsflags |= VFC_VFSNATIVEXATTR; } if (vfe->vfe_flags & VFS_TBLUNMOUNT_PREFLIGHT) { newvfstbl->vfc_vfsflags |= VFC_VFSPREFLIGHT; } if (vfe->vfe_flags & VFS_TBLREADDIR_EXTENDED) { newvfstbl->vfc_vfsflags |= VFC_VFSREADDIR_EXTENDED; } if (vfe->vfe_flags & VFS_TBLNOMACLABEL) { newvfstbl->vfc_vfsflags |= VFC_VFSNOMACLABEL; } if (vfe->vfe_flags & VFS_TBLVNOP_NOUPDATEID_RENAME) { newvfstbl->vfc_vfsflags |= VFC_VFSVNOP_NOUPDATEID_RENAME; } if (vfe->vfe_flags & VFS_TBLVNOP_SECLUDE_RENAME) { newvfstbl->vfc_vfsflags |= VFC_VFSVNOP_SECLUDE_RENAME; } if (vfe->vfe_flags & VFS_TBLCANMOUNTROOT) { newvfstbl->vfc_vfsflags |= VFC_VFSCANMOUNTROOT; } /* * Allocate and init the vectors. * Also handle backwards compatibility. * * We allocate one large block to hold all * vnode operation vectors stored contiguously. */ /* XXX - shouldn't be M_TEMP */ descsize = desccount * vfs_opv_numops; descptr = kalloc_type(PFI, descsize, Z_WAITOK | Z_ZERO); newvfstbl->vfc_descptr = descptr; newvfstbl->vfc_descsize = descsize; newvfstbl->vfc_sysctl = NULL; for (i = 0; i < desccount; i++) { opv_desc_vector_p = vfe->vfe_opvdescs[i]->opv_desc_vector_p; /* * Fill in the caller's pointer to the start of the i'th vector. * They'll need to supply it when calling vnode_create. */ opv_desc_vector = descptr + i * vfs_opv_numops; *opv_desc_vector_p = opv_desc_vector; for (j = 0; vfe->vfe_opvdescs[i]->opv_desc_ops[j].opve_op; j++) { opve_descp = &(vfe->vfe_opvdescs[i]->opv_desc_ops[j]); /* Silently skip known-disabled operations */ if (opve_descp->opve_op->vdesc_flags & VDESC_DISABLED) { printf("vfs_fsadd: Ignoring reference in %p to disabled operation %s.\n", vfe->vfe_opvdescs[i], opve_descp->opve_op->vdesc_name); continue; } /* * Sanity check: is this operation listed * in the list of operations? We check this * by seeing if its offset is zero. Since * the default routine should always be listed * first, it should be the only one with a zero * offset. Any other operation with a zero * offset is probably not listed in * vfs_op_descs, and so is probably an error. * * A panic here means the layer programmer * has committed the all-too common bug * of adding a new operation to the layer's * list of vnode operations but * not adding the operation to the system-wide * list of supported operations. */ if (opve_descp->opve_op->vdesc_offset == 0 && opve_descp->opve_op != VDESC(vnop_default)) { printf("vfs_fsadd: operation %s not listed in %s.\n", opve_descp->opve_op->vdesc_name, "vfs_op_descs"); panic("vfs_fsadd: bad operation"); } /* * Fill in this entry. */ opv_desc_vector[opve_descp->opve_op->vdesc_offset] = opve_descp->opve_impl; } /* * Finally, go back and replace unfilled routines * with their default. (Sigh, an O(n^3) algorithm. I * could make it better, but that'd be work, and n is small.) */ opv_desc_vector_p = vfe->vfe_opvdescs[i]->opv_desc_vector_p; /* * Force every operations vector to have a default routine. */ opv_desc_vector = *opv_desc_vector_p; if (opv_desc_vector[VOFFSET(vnop_default)] == NULL) { panic("vfs_fsadd: operation vector without default routine."); } for (j = 0; j < vfs_opv_numops; j++) { if (opv_desc_vector[j] == NULL) { opv_desc_vector[j] = opv_desc_vector[VOFFSET(vnop_default)]; } } } /* end of each vnodeopv_desc parsing */ *handle = vfstable_add(newvfstbl); if (newvfstbl->vfc_vfsops->vfs_init) { struct vfsconf vfsc; bzero(&vfsc, sizeof(struct vfsconf)); vfsc.vfc_reserved1 = 0; bcopy((*handle)->vfc_name, vfsc.vfc_name, sizeof(vfsc.vfc_name)); vfsc.vfc_typenum = (*handle)->vfc_typenum; vfsc.vfc_refcount = (*handle)->vfc_refcount; vfsc.vfc_flags = (*handle)->vfc_flags; vfsc.vfc_reserved2 = 0; vfsc.vfc_reserved3 = 0; (*newvfstbl->vfc_vfsops->vfs_init)(&vfsc); } kfree_type(struct vfstable, newvfstbl); return 0; } /* * Removes the filesystem from kernel. * The argument passed in is the handle that was given when * file system was added */ errno_t vfs_fsremove(vfstable_t handle) { struct vfstable * vfstbl = (struct vfstable *)handle; void *old_desc = NULL; size_t descsize = 0; errno_t err; /* Preflight check for any mounts */ mount_list_lock(); if (vfstbl->vfc_refcount != 0) { mount_list_unlock(); return EBUSY; } /* Free the spot in vfs_typenum_arr */ lck_mtx_lock(&vfs_typenum_mtx); clrbit(vfs_typenum_arr, handle->vfc_typenum); if (maxvfstypenum == handle->vfc_typenum) { maxvfstypenum--; } lck_mtx_unlock(&vfs_typenum_mtx); /* * save the old descriptor; the free cannot occur unconditionally, * since vfstable_del() may fail. */ if (vfstbl->vfc_descptr && vfstbl->vfc_descsize) { old_desc = vfstbl->vfc_descptr; descsize = vfstbl->vfc_descsize; } err = vfstable_del(vfstbl); mount_list_unlock(); /* free the descriptor if the delete was successful */ if (err == 0) { kfree_type(PFI, descsize, old_desc); } return err; } void vfs_setowner(mount_t mp, uid_t uid, gid_t gid) { mp->mnt_fsowner = uid; mp->mnt_fsgroup = gid; } /* * Callers should be careful how they use this; accessing * mnt_last_write_completed_timestamp is not thread-safe. Writing to * it isn't either. Point is: be prepared to deal with strange values * being returned. */ uint64_t vfs_idle_time(mount_t mp) { if (mp->mnt_pending_write_size) { return 0; } struct timeval now; microuptime(&now); return (now.tv_sec - mp->mnt_last_write_completed_timestamp.tv_sec) * 1000000 + now.tv_usec - mp->mnt_last_write_completed_timestamp.tv_usec; } /* * vfs_context_create_with_proc() takes a reference on an arbitrary * thread in the process. To distinguish this reference-counted thread * from the usual non-reference-counted thread, we set the least significant * bit of of vc_thread. */ #define VFS_CONTEXT_THREAD_IS_REFERENCED(ctx) \ (!!(((uintptr_t)(ctx)->vc_thread) & 1UL)) #define VFS_CONTEXT_SET_REFERENCED_THREAD(ctx, thr) \ (ctx)->vc_thread = (thread_t)(((uintptr_t)(thr)) | 1UL) #define VFS_CONTEXT_GET_THREAD(ctx) \ ((thread_t)(((uintptr_t)(ctx)->vc_thread) & ~1UL)) int vfs_context_pid(vfs_context_t ctx) { return proc_pid(vfs_context_proc(ctx)); } int vfs_context_copy_audit_token(vfs_context_t ctx, audit_token_t *token) { kern_return_t err; task_t task; mach_msg_type_number_t info_size = TASK_AUDIT_TOKEN_COUNT; task = vfs_context_task(ctx); if (task == NULL) { // Not sure how this would happen; we are supposed to be // in the middle of using the context. Regardless, don't // wander off a NULL pointer. return ESRCH; } err = task_info(task, TASK_AUDIT_TOKEN, (integer_t *)token, &info_size); return (err) ? ESRCH : 0; } int vfs_context_suser(vfs_context_t ctx) { return suser(ctx->vc_ucred, NULL); } /* * Return bit field of signals posted to all threads in the context's process. * * XXX Signals should be tied to threads, not processes, for most uses of this * XXX call. */ int vfs_context_issignal(vfs_context_t ctx, sigset_t mask) { proc_t p = vfs_context_proc(ctx); if (p) { return proc_pendingsignals(p, mask); } return 0; } int vfs_context_is64bit(vfs_context_t ctx) { uthread_t uth; thread_t t; if (ctx != NULL && (t = VFS_CONTEXT_GET_THREAD(ctx)) != NULL) { uth = get_bsdthread_info(t); } else { uth = current_uthread(); } return uthread_is64bit(uth); } boolean_t vfs_context_can_resolve_triggers(vfs_context_t ctx) { proc_t proc = vfs_context_proc(ctx); if (proc) { if (proc->p_vfs_iopolicy & P_VFS_IOPOLICY_TRIGGER_RESOLVE_DISABLE) { return false; } return true; } return false; } boolean_t vfs_context_can_break_leases(vfs_context_t ctx) { proc_t proc = vfs_context_proc(ctx); if (proc) { /* * We do not have a separate I/O policy for this, * because the scenarios where we would not want * local file lease breaks are currently exactly * the same as where we would not want dataless * file materialization (mainly, system daemons * passively snooping file activity). */ if (proc->p_vfs_iopolicy & P_VFS_IOPOLICY_MATERIALIZE_DATALESS_FILES) { return true; } return false; } return true; } boolean_t vfs_context_allow_fs_blksize_nocache_write(vfs_context_t ctx) { uthread_t uth; thread_t t; proc_t p; if ((ctx == NULL) || (t = VFS_CONTEXT_GET_THREAD(ctx)) == NULL) { return FALSE; } uth = get_bsdthread_info(t); if (uth && (uth->uu_flag & UT_FS_BLKSIZE_NOCACHE_WRITES)) { return TRUE; } p = (proc_t)get_bsdthreadtask_info(t); if (p && (os_atomic_load(&p->p_vfs_iopolicy, relaxed) & P_VFS_IOPOLICY_NOCACHE_WRITE_FS_BLKSIZE)) { return TRUE; } return FALSE; } /* * vfs_context_proc * * Description: Given a vfs_context_t, return the proc_t associated with it. * * Parameters: vfs_context_t The context to use * * Returns: proc_t The process for this context * * Notes: This function will return the current_proc() if any of the * following conditions are true: * * o The supplied context pointer is NULL * o There is no Mach thread associated with the context * o There is no Mach task associated with the Mach thread * o There is no proc_t associated with the Mach task * o The proc_t has no per process open file table * * This causes this function to return a value matching as * closely as possible the previous behaviour. */ proc_t vfs_context_proc(vfs_context_t ctx) { proc_t proc = NULL; thread_t t; if (ctx != NULL && (t = VFS_CONTEXT_GET_THREAD(ctx)) != NULL) { proc = (proc_t)get_bsdthreadtask_info(t); } return proc == NULL ? current_proc() : proc; } /* * vfs_context_get_special_port * * Description: Return the requested special port from the task associated * with the given context. * * Parameters: vfs_context_t The context to use * int Index of special port * ipc_port_t * Pointer to returned port * * Returns: kern_return_t see task_get_special_port() */ kern_return_t vfs_context_get_special_port(vfs_context_t ctx, int which, ipc_port_t *portp) { return task_get_special_port(vfs_context_task(ctx), which, portp); } /* * vfs_context_set_special_port * * Description: Set the requested special port in the task associated * with the given context. * * Parameters: vfs_context_t The context to use * int Index of special port * ipc_port_t New special port * * Returns: kern_return_t see task_set_special_port_internal() */ kern_return_t vfs_context_set_special_port(vfs_context_t ctx, int which, ipc_port_t port) { return task_set_special_port_internal(vfs_context_task(ctx), which, port); } /* * vfs_context_thread * * Description: Return the Mach thread associated with a vfs_context_t * * Parameters: vfs_context_t The context to use * * Returns: thread_t The thread for this context, or * NULL, if there is not one. * * Notes: NULL thread_t's are legal, but discouraged. They occur only * as a result of a static vfs_context_t declaration in a function * and will result in this function returning NULL. * * This is intentional; this function should NOT return the * current_thread() in this case. */ thread_t vfs_context_thread(vfs_context_t ctx) { return VFS_CONTEXT_GET_THREAD(ctx); } /* * vfs_context_task * * Description: Return the Mach task associated with a vfs_context_t * * Parameters: vfs_context_t The context to use * * Returns: task_t The task for this context, or * NULL, if there is not one. * * Notes: NULL task_t's are legal, but discouraged. They occur only * as a result of a static vfs_context_t declaration in a function * and will result in this function returning NULL. * * This is intentional; this function should NOT return the * task associated with current_thread() in this case. */ task_t vfs_context_task(vfs_context_t ctx) { task_t task = NULL; thread_t t; if (ctx != NULL && (t = VFS_CONTEXT_GET_THREAD(ctx)) != NULL) { task = get_threadtask(t); } return task; } /* * vfs_context_cwd * * Description: Returns a reference on the vnode for the current working * directory for the supplied context * * Parameters: vfs_context_t The context to use * * Returns: vnode_t The current working directory * for this context * * Notes: The function first attempts to obtain the current directory * from the thread, and if it is not present there, falls back * to obtaining it from the process instead. If it can't be * obtained from either place, we return NULLVP. */ vnode_t vfs_context_cwd(vfs_context_t ctx) { vnode_t cwd = NULLVP; thread_t t; if (ctx != NULL && (t = VFS_CONTEXT_GET_THREAD(ctx)) != NULL) { uthread_t uth = get_bsdthread_info(t); proc_t proc; /* * Get the cwd from the thread; if there isn't one, get it * from the process, instead. */ if ((cwd = uth->uu_cdir) == NULLVP && (proc = (proc_t)get_bsdthreadtask_info(t)) != NULL) { cwd = proc->p_fd.fd_cdir; } } return cwd; } /* * vfs_context_create * * Description: Allocate and initialize a new context. * * Parameters: vfs_context_t: Context to copy, or NULL for new * * Returns: Pointer to new context * * Notes: Copy cred and thread from argument, if available; else * initialize with current thread and new cred. Returns * with a reference held on the credential. */ vfs_context_t vfs_context_create(vfs_context_t ctx) { vfs_context_t newcontext; newcontext = zalloc_flags(KT_VFS_CONTEXT, Z_WAITOK | Z_ZERO | Z_NOFAIL); if (ctx == NULL) { ctx = vfs_context_current(); } *newcontext = *ctx; if (IS_VALID_CRED(ctx->vc_ucred)) { kauth_cred_ref(ctx->vc_ucred); } return newcontext; } /* * vfs_context_create_with_proc * * Description: Create a new context with credentials taken from * the specified proc. * * Parameters: proc_t: The process whose crendials to use. * * Returns: Pointer to new context. * * Notes: The context will also take a reference on an arbitrary * thread in the process as well as the process's credentials. */ vfs_context_t vfs_context_create_with_proc(proc_t p) { vfs_context_t newcontext; thread_t thread; kauth_cred_t cred; if (p == current_proc()) { return vfs_context_create(NULL); } newcontext = zalloc_flags(KT_VFS_CONTEXT, Z_WAITOK | Z_ZERO | Z_NOFAIL); proc_lock(p); thread = proc_thread(p); /* XXX */ if (thread != NULL) { thread_reference(thread); } proc_unlock(p); cred = kauth_cred_proc_ref(p); VFS_CONTEXT_SET_REFERENCED_THREAD(newcontext, thread); newcontext->vc_ucred = cred; return newcontext; } vfs_context_t vfs_context_current(void) { static_assert(offsetof(struct thread_ro, tro_owner) == offsetof(struct vfs_context, vc_thread)); static_assert(offsetof(struct thread_ro, tro_cred) == offsetof(struct vfs_context, vc_ucred)); return (vfs_context_t)current_thread_ro(); } vfs_context_t vfs_context_kernel(void) { return &vfs_context0; } int vfs_context_rele(vfs_context_t ctx) { if (ctx) { if (IS_VALID_CRED(ctx->vc_ucred)) { kauth_cred_unref(&ctx->vc_ucred); } if (VFS_CONTEXT_THREAD_IS_REFERENCED(ctx)) { assert(VFS_CONTEXT_GET_THREAD(ctx) != NULL); thread_deallocate(VFS_CONTEXT_GET_THREAD(ctx)); } zfree(KT_VFS_CONTEXT, ctx); } return 0; } kauth_cred_t vfs_context_ucred(vfs_context_t ctx) { return ctx->vc_ucred; } /* * Return true if the context is owned by the superuser. */ int vfs_context_issuser(vfs_context_t ctx) { return kauth_cred_issuser(vfs_context_ucred(ctx)); } int vfs_context_iskernel(vfs_context_t ctx) { return ctx == &vfs_context0; } /* * Given a context, for all fields of vfs_context_t which * are not held with a reference, set those fields to the * values for the current execution context. * * Returns: 0 for success, nonzero for failure * * The intended use is: * 1. vfs_context_create() gets the caller a context * 2. vfs_context_bind() sets the unrefcounted data * 3. vfs_context_rele() releases the context * */ int vfs_context_bind(vfs_context_t ctx) { assert(!VFS_CONTEXT_THREAD_IS_REFERENCED(ctx)); ctx->vc_thread = current_thread(); return 0; } int vfs_set_thread_fs_private(uint8_t tag, uint64_t fs_private) { struct uthread *ut; if (tag != FS_PRIVATE_TAG_APFS) { return ENOTSUP; } ut = current_uthread(); ut->t_fs_private = fs_private; return 0; } int vfs_get_thread_fs_private(uint8_t tag, uint64_t *fs_private) { struct uthread *ut; if (tag != FS_PRIVATE_TAG_APFS) { return ENOTSUP; } ut = current_uthread(); *fs_private = ut->t_fs_private; return 0; } int vfs_isswapmount(mount_t mnt) { return mnt && ISSET(mnt->mnt_kern_flag, MNTK_SWAP_MOUNT) ? 1 : 0; } /* XXXXXXXXXXXXXX VNODE KAPIS XXXXXXXXXXXXXXXXXXXXXXXXX */ /* * Convert between vnode types and inode formats (since POSIX.1 * defines mode word of stat structure in terms of inode formats). */ enum vtype vnode_iftovt(int mode) { return iftovt_tab[((mode) & S_IFMT) >> 12]; } int vnode_vttoif(enum vtype indx) { return vttoif_tab[(int)(indx)]; } int vnode_makeimode(int indx, int mode) { return (int)(VTTOIF(indx) | (mode)); } /* * vnode manipulation functions. */ /* returns system root vnode iocount; It should be released using vnode_put() */ vnode_t vfs_rootvnode(void) { vnode_t vp = NULLVP; if (rootvnode) { lck_rw_lock_shared(&rootvnode_rw_lock); vp = rootvnode; if (vp && (vnode_get(vp) != 0)) { vp = NULLVP; } lck_rw_unlock_shared(&rootvnode_rw_lock); } return vp; } uint32_t vnode_vid(vnode_t vp) { return (uint32_t)(vp->v_id); } mount_t vnode_mount(vnode_t vp) { return vp->v_mount; } #if CONFIG_IOSCHED vnode_t vnode_mountdevvp(vnode_t vp) { if (vp->v_mount) { return vp->v_mount->mnt_devvp; } else { return (vnode_t)0; } } #endif boolean_t vnode_isonexternalstorage(vnode_t vp) { if (vp) { if (vp->v_mount) { if (vp->v_mount->mnt_ioflags & MNT_IOFLAGS_PERIPHERAL_DRIVE) { return TRUE; } } } return FALSE; } boolean_t vnode_isonssd(vnode_t vp) { if (vp) { if (vp->v_mount) { if (vp->v_mount->mnt_kern_flag & MNTK_SSD) { return TRUE; } } } return FALSE; } mount_t vnode_mountedhere(vnode_t vp) { mount_t mp; if ((vp->v_type == VDIR) && ((mp = vp->v_mountedhere) != NULL) && (mp->mnt_vnodecovered == vp)) { return mp; } else { return (mount_t)NULL; } } /* returns vnode type of vnode_t */ enum vtype vnode_vtype(vnode_t vp) { return vp->v_type; } /* returns FS specific node saved in vnode */ void * vnode_fsnode(vnode_t vp) { return vp->v_data; } void vnode_clearfsnode(vnode_t vp) { vp->v_data = NULL; } dev_t vnode_specrdev(vnode_t vp) { return vp->v_rdev; } /* Accessor functions */ /* is vnode_t a root vnode */ int vnode_isvroot(vnode_t vp) { return (vp->v_flag & VROOT)? 1 : 0; } /* is vnode_t a system vnode */ int vnode_issystem(vnode_t vp) { return (vp->v_flag & VSYSTEM)? 1 : 0; } /* is vnode_t a swap file vnode */ int vnode_isswap(vnode_t vp) { return (vp->v_flag & VSWAP)? 1 : 0; } /* is vnode_t a tty */ int vnode_istty(vnode_t vp) { return (vp->v_flag & VISTTY) ? 1 : 0; } /* if vnode_t mount operation in progress */ int vnode_ismount(vnode_t vp) { return (vp->v_flag & VMOUNT)? 1 : 0; } /* is this vnode under recyle now */ int vnode_isrecycled(vnode_t vp) { int ret; vnode_lock_spin(vp); ret = (vp->v_lflag & (VL_TERMINATE | VL_DEAD))? 1 : 0; vnode_unlock(vp); return ret; } /* is this vnode marked for termination */ int vnode_willberecycled(vnode_t vp) { return (vp->v_lflag & VL_MARKTERM) ? 1 : 0; } /* vnode was created by background task requesting rapid aging * and has not since been referenced by a normal task */ int vnode_israge(vnode_t vp) { return (vp->v_flag & VRAGE)? 1 : 0; } int vnode_needssnapshots(__unused vnode_t vp) { return 0; } /* Check the process/thread to see if we should skip atime updates */ int vfs_ctx_skipatime(vfs_context_t ctx) { struct uthread *ut; proc_t proc; thread_t thr; proc = vfs_context_proc(ctx); thr = vfs_context_thread(ctx); /* Validate pointers in case we were invoked via a kernel context */ if (thr && proc) { ut = get_bsdthread_info(thr); if (proc->p_lflag & P_LRAGE_VNODES) { return 1; } if (ut) { if (ut->uu_flag & (UT_RAGE_VNODES | UT_ATIME_UPDATE)) { return 1; } } if (proc->p_vfs_iopolicy & P_VFS_IOPOLICY_ATIME_UPDATES) { return 1; } } return 0; } /* is vnode_t marked to not keep data cached once it's been consumed */ int vnode_isnocache(vnode_t vp) { return (vp->v_flag & VNOCACHE_DATA)? 1 : 0; } /* * has sequential readahead been disabled on this vnode */ int vnode_isnoreadahead(vnode_t vp) { return (vp->v_flag & VRAOFF)? 1 : 0; } int vnode_is_openevt(vnode_t vp) { return (vp->v_flag & VOPENEVT)? 1 : 0; } /* is vnode_t a standard one? */ int vnode_isstandard(vnode_t vp) { return (vp->v_flag & VSTANDARD)? 1 : 0; } /* don't vflush() if SKIPSYSTEM */ int vnode_isnoflush(vnode_t vp) { return (vp->v_flag & VNOFLUSH)? 1 : 0; } /* is vnode_t a regular file */ int vnode_isreg(vnode_t vp) { return (vp->v_type == VREG)? 1 : 0; } /* is vnode_t a directory? */ int vnode_isdir(vnode_t vp) { return (vp->v_type == VDIR)? 1 : 0; } /* is vnode_t a symbolic link ? */ int vnode_islnk(vnode_t vp) { return (vp->v_type == VLNK)? 1 : 0; } int vnode_lookup_continue_needed(vnode_t vp, struct componentname *cnp) { struct nameidata *ndp = cnp->cn_ndp; if (ndp == NULL) { panic("vnode_lookup_continue_needed(): cnp->cn_ndp is NULL"); } if (vnode_isdir(vp)) { if (vp->v_mountedhere != NULL) { goto yes; } #if CONFIG_TRIGGERS if (vp->v_resolve) { goto yes; } #endif /* CONFIG_TRIGGERS */ } if (vnode_islnk(vp)) { /* From lookup(): || *ndp->ni_next == '/') No need for this, we know we're NULL-terminated here */ if (cnp->cn_flags & FOLLOW) { goto yes; } if (ndp->ni_flag & NAMEI_TRAILINGSLASH) { goto yes; } } return 0; yes: ndp->ni_flag |= NAMEI_CONTLOOKUP; return EKEEPLOOKING; } /* is vnode_t a fifo ? */ int vnode_isfifo(vnode_t vp) { return (vp->v_type == VFIFO)? 1 : 0; } /* is vnode_t a block device? */ int vnode_isblk(vnode_t vp) { return (vp->v_type == VBLK)? 1 : 0; } int vnode_isspec(vnode_t vp) { return ((vp->v_type == VCHR) || (vp->v_type == VBLK)) ? 1 : 0; } /* is vnode_t a char device? */ int vnode_ischr(vnode_t vp) { return (vp->v_type == VCHR)? 1 : 0; } /* is vnode_t a socket? */ int vnode_issock(vnode_t vp) { return (vp->v_type == VSOCK)? 1 : 0; } /* is vnode_t a device with multiple active vnodes referring to it? */ int vnode_isaliased(vnode_t vp) { enum vtype vt = vp->v_type; if (!((vt == VCHR) || (vt == VBLK))) { return 0; } else { return vp->v_specflags & SI_ALIASED; } } /* is vnode_t a named stream? */ int vnode_isnamedstream( #if NAMEDSTREAMS vnode_t vp #else __unused vnode_t vp #endif ) { #if NAMEDSTREAMS return (vp->v_flag & VISNAMEDSTREAM) ? 1 : 0; #else return 0; #endif } int vnode_isshadow( #if NAMEDSTREAMS vnode_t vp #else __unused vnode_t vp #endif ) { #if NAMEDSTREAMS return (vp->v_flag & VISSHADOW) ? 1 : 0; #else return 0; #endif } /* does vnode have associated named stream vnodes ? */ int vnode_hasnamedstreams( #if NAMEDSTREAMS vnode_t vp #else __unused vnode_t vp #endif ) { #if NAMEDSTREAMS return (vp->v_lflag & VL_HASSTREAMS) ? 1 : 0; #else return 0; #endif } /* TBD: set vnode_t to not cache data after it is consumed once; used for quota */ void vnode_setnocache(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag |= VNOCACHE_DATA; vnode_unlock(vp); } void vnode_clearnocache(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag &= ~VNOCACHE_DATA; vnode_unlock(vp); } void vnode_set_openevt(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag |= VOPENEVT; vnode_unlock(vp); } void vnode_clear_openevt(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag &= ~VOPENEVT; vnode_unlock(vp); } void vnode_setnoreadahead(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag |= VRAOFF; vnode_unlock(vp); } void vnode_clearnoreadahead(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag &= ~VRAOFF; vnode_unlock(vp); } int vnode_isfastdevicecandidate(vnode_t vp) { return (vp->v_flag & VFASTDEVCANDIDATE)? 1 : 0; } void vnode_setfastdevicecandidate(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag |= VFASTDEVCANDIDATE; vnode_unlock(vp); } void vnode_clearfastdevicecandidate(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag &= ~VFASTDEVCANDIDATE; vnode_unlock(vp); } int vnode_isautocandidate(vnode_t vp) { return (vp->v_flag & VAUTOCANDIDATE)? 1 : 0; } void vnode_setautocandidate(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag |= VAUTOCANDIDATE; vnode_unlock(vp); } void vnode_clearautocandidate(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag &= ~VAUTOCANDIDATE; vnode_unlock(vp); } /* mark vnode_t to skip vflush() is SKIPSYSTEM */ void vnode_setnoflush(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag |= VNOFLUSH; vnode_unlock(vp); } void vnode_clearnoflush(vnode_t vp) { vnode_lock_spin(vp); vp->v_flag &= ~VNOFLUSH; vnode_unlock(vp); } /* is vnode_t a blkdevice and has a FS mounted on it */ int vnode_ismountedon(vnode_t vp) { return (vp->v_specflags & SI_MOUNTEDON)? 1 : 0; } void vnode_setmountedon(vnode_t vp) { vnode_lock_spin(vp); vp->v_specflags |= SI_MOUNTEDON; vnode_unlock(vp); } void vnode_clearmountedon(vnode_t vp) { vnode_lock_spin(vp); vp->v_specflags &= ~SI_MOUNTEDON; vnode_unlock(vp); } void vnode_settag(vnode_t vp, int tag) { /* * We only assign enum values to v_tag, but add an assert to make sure we * catch it in dev/debug builds if this ever change. */ assert(tag >= SHRT_MIN && tag <= SHRT_MAX); vp->v_tag = (uint16_t)tag; } int vnode_tag(vnode_t vp) { return vp->v_tag; } vnode_t vnode_parent(vnode_t vp) { return vp->v_parent; } void vnode_setparent(vnode_t vp, vnode_t dvp) { vp->v_parent = dvp; } void vnode_setname(vnode_t vp, char * name) { vp->v_name = name; } /* return the registered FS name when adding the FS to kernel */ void vnode_vfsname(vnode_t vp, char * buf) { strlcpy(buf, vp->v_mount->mnt_vtable->vfc_name, MFSNAMELEN); } /* return the FS type number */ int vnode_vfstypenum(vnode_t vp) { return vp->v_mount->mnt_vtable->vfc_typenum; } int vnode_vfs64bitready(vnode_t vp) { /* * Checking for dead_mountp is a bit of a hack for SnowLeopard: */ if ((vp->v_mount != dead_mountp) && (vp->v_mount->mnt_vtable->vfc_vfsflags & VFC_VFS64BITREADY)) { return 1; } else { return 0; } } /* return the visible flags on associated mount point of vnode_t */ uint32_t vnode_vfsvisflags(vnode_t vp) { return vp->v_mount->mnt_flag & MNT_VISFLAGMASK; } /* return the command modifier flags on associated mount point of vnode_t */ uint32_t vnode_vfscmdflags(vnode_t vp) { return vp->v_mount->mnt_flag & MNT_CMDFLAGS; } /* return the max symlink of short links of vnode_t */ uint32_t vnode_vfsmaxsymlen(vnode_t vp) { return vp->v_mount->mnt_maxsymlinklen; } /* return a pointer to the RO vfs_statfs associated with vnode_t's mount point */ struct vfsstatfs * vnode_vfsstatfs(vnode_t vp) { return &vp->v_mount->mnt_vfsstat; } /* return a handle to the FSs specific private handle associated with vnode_t's mount point */ void * vnode_vfsfsprivate(vnode_t vp) { return vp->v_mount->mnt_data; } /* is vnode_t in a rdonly mounted FS */ int vnode_vfsisrdonly(vnode_t vp) { return (vp->v_mount->mnt_flag & MNT_RDONLY)? 1 : 0; } int vnode_compound_rename_available(vnode_t vp) { return vnode_compound_op_available(vp, COMPOUND_VNOP_RENAME); } int vnode_compound_rmdir_available(vnode_t vp) { return vnode_compound_op_available(vp, COMPOUND_VNOP_RMDIR); } int vnode_compound_mkdir_available(vnode_t vp) { return vnode_compound_op_available(vp, COMPOUND_VNOP_MKDIR); } int vnode_compound_remove_available(vnode_t vp) { return vnode_compound_op_available(vp, COMPOUND_VNOP_REMOVE); } int vnode_compound_open_available(vnode_t vp) { return vnode_compound_op_available(vp, COMPOUND_VNOP_OPEN); } int vnode_compound_op_available(vnode_t vp, compound_vnop_id_t opid) { return (vp->v_mount->mnt_compound_ops & opid) != 0; } /* * Returns vnode ref to current working directory; if a per-thread current * working directory is in effect, return that instead of the per process one. * * XXX Published, but not used. */ vnode_t current_workingdir(void) { return vfs_context_cwd(vfs_context_current()); } /* * Get a filesec and optional acl contents from an extended attribute. * Function will attempt to retrive ACL, UUID, and GUID information using a * read of a named extended attribute (KAUTH_FILESEC_XATTR). * * Parameters: vp The vnode on which to operate. * fsecp The filesec (and ACL, if any) being * retrieved. * ctx The vnode context in which the * operation is to be attempted. * * Returns: 0 Success * !0 errno value * * Notes: The kauth_filesec_t in '*fsecp', if retrieved, will be in * host byte order, as will be the ACL contents, if any. * Internally, we will cannonize these values from network (PPC) * byte order after we retrieve them so that the on-disk contents * of the extended attribute are identical for both PPC and Intel * (if we were not being required to provide this service via * fallback, this would be the job of the filesystem * 'VNOP_GETATTR' call). * * We use ntohl() because it has a transitive property on Intel * machines and no effect on PPC mancines. This guarantees us * * XXX: Deleting rather than ignoreing a corrupt security structure is * probably the only way to reset it without assistance from an * file system integrity checking tool. Right now we ignore it. * * XXX: We should enummerate the possible errno values here, and where * in the code they originated. */ static int vnode_get_filesec(vnode_t vp, kauth_filesec_t *fsecp, vfs_context_t ctx) { kauth_filesec_t fsec; uio_t fsec_uio; size_t fsec_size; size_t xsize, rsize; int error; uint32_t host_fsec_magic; uint32_t host_acl_entrycount; fsec = NULL; fsec_uio = NULL; /* find out how big the EA is */ error = vn_getxattr(vp, KAUTH_FILESEC_XATTR, NULL, &xsize, XATTR_NOSECURITY, ctx); if (error != 0) { /* no EA, no filesec */ if ((error == ENOATTR) || (error == ENOENT) || (error == EJUSTRETURN)) { error = 0; } /* either way, we are done */ goto out; } /* * To be valid, a kauth_filesec_t must be large enough to hold a zero * ACE entrly ACL, and if it's larger than that, it must have the right * number of bytes such that it contains an atomic number of ACEs, * rather than partial entries. Otherwise, we ignore it. */ if (!KAUTH_FILESEC_VALID(xsize)) { KAUTH_DEBUG(" ERROR - Bogus kauth_fiilesec_t: %ld bytes", xsize); error = 0; goto out; } /* how many entries would fit? */ fsec_size = KAUTH_FILESEC_COUNT(xsize); if (fsec_size > KAUTH_ACL_MAX_ENTRIES) { KAUTH_DEBUG(" ERROR - Bogus (too large) kauth_fiilesec_t: %ld bytes", xsize); error = 0; goto out; } /* get buffer and uio */ if (((fsec = kauth_filesec_alloc((int)fsec_size)) == NULL) || ((fsec_uio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ)) == NULL) || uio_addiov(fsec_uio, CAST_USER_ADDR_T(fsec), xsize)) { KAUTH_DEBUG(" ERROR - could not allocate iov to read ACL"); error = ENOMEM; goto out; } /* read security attribute */ rsize = xsize; if ((error = vn_getxattr(vp, KAUTH_FILESEC_XATTR, fsec_uio, &rsize, XATTR_NOSECURITY, ctx)) != 0) { /* no attribute - no security data */ if ((error == ENOATTR) || (error == ENOENT) || (error == EJUSTRETURN)) { error = 0; } /* either way, we are done */ goto out; } /* * Validate security structure; the validation must take place in host * byte order. If it's corrupt, we will just ignore it. */ /* Validate the size before trying to convert it */ if (rsize < KAUTH_FILESEC_SIZE(0)) { KAUTH_DEBUG("ACL - DATA TOO SMALL (%d)", rsize); goto out; } /* Validate the magic number before trying to convert it */ host_fsec_magic = ntohl(KAUTH_FILESEC_MAGIC); if (fsec->fsec_magic != host_fsec_magic) { KAUTH_DEBUG("ACL - BAD MAGIC %x", host_fsec_magic); goto out; } /* Validate the entry count before trying to convert it. */ host_acl_entrycount = ntohl(fsec->fsec_acl.acl_entrycount); if (host_acl_entrycount != KAUTH_FILESEC_NOACL) { if (host_acl_entrycount > KAUTH_ACL_MAX_ENTRIES) { KAUTH_DEBUG("ACL - BAD ENTRYCOUNT %x", host_acl_entrycount); goto out; } if (KAUTH_FILESEC_SIZE(host_acl_entrycount) > rsize) { KAUTH_DEBUG("ACL - BUFFER OVERFLOW (%d entries too big for %d)", host_acl_entrycount, rsize); goto out; } } kauth_filesec_acl_setendian(KAUTH_ENDIAN_HOST, fsec, NULL); *fsecp = fsec; fsec = NULL; error = 0; out: if (fsec != NULL) { kauth_filesec_free(fsec); } if (fsec_uio != NULL) { uio_free(fsec_uio); } if (error) { *fsecp = NULL; } return error; } /* * Set a filesec and optional acl contents into an extended attribute. * function will attempt to store ACL, UUID, and GUID information using a * write to a named extended attribute (KAUTH_FILESEC_XATTR). The 'acl' * may or may not point to the `fsec->fsec_acl`, depending on whether the * original caller supplied an acl. * * Parameters: vp The vnode on which to operate. * fsec The filesec being set. * acl The acl to be associated with 'fsec'. * ctx The vnode context in which the * operation is to be attempted. * * Returns: 0 Success * !0 errno value * * Notes: Both the fsec and the acl are always valid. * * The kauth_filesec_t in 'fsec', if any, is in host byte order, * as are the acl contents, if they are used. Internally, we will * cannonize these values into network (PPC) byte order before we * attempt to write them so that the on-disk contents of the * extended attribute are identical for both PPC and Intel (if we * were not being required to provide this service via fallback, * this would be the job of the filesystem 'VNOP_SETATTR' call). * We reverse this process on the way out, so we leave with the * same byte order we started with. * * XXX: We should enummerate the possible errno values here, and where * in the code they originated. */ static int vnode_set_filesec(vnode_t vp, kauth_filesec_t fsec, kauth_acl_t acl, vfs_context_t ctx) { uio_t fsec_uio; int error; uint32_t saved_acl_copysize; fsec_uio = NULL; if ((fsec_uio = uio_create(2, 0, UIO_SYSSPACE, UIO_WRITE)) == NULL) { KAUTH_DEBUG(" ERROR - could not allocate iov to write ACL"); error = ENOMEM; goto out; } /* * Save the pre-converted ACL copysize, because it gets swapped too * if we are running with the wrong endianness. */ saved_acl_copysize = KAUTH_ACL_COPYSIZE(acl); kauth_filesec_acl_setendian(KAUTH_ENDIAN_DISK, fsec, acl); uio_addiov(fsec_uio, CAST_USER_ADDR_T(fsec), KAUTH_FILESEC_SIZE(0) - KAUTH_ACL_SIZE(KAUTH_FILESEC_NOACL)); uio_addiov(fsec_uio, CAST_USER_ADDR_T(acl), saved_acl_copysize); error = vn_setxattr(vp, KAUTH_FILESEC_XATTR, fsec_uio, XATTR_NOSECURITY, /* we have auth'ed already */ ctx); VFS_DEBUG(ctx, vp, "SETATTR - set ACL returning %d", error); kauth_filesec_acl_setendian(KAUTH_ENDIAN_HOST, fsec, acl); out: if (fsec_uio != NULL) { uio_free(fsec_uio); } return error; } /* * Handle uid/gid == 99 and MNT_IGNORE_OWNERSHIP here. */ void vnode_attr_handle_uid_and_gid(struct vnode_attr *vap, mount_t mp, vfs_context_t ctx) { uid_t nuid; gid_t ngid; bool is_suser = vfs_context_issuser(ctx) ? true : false; if (VATTR_IS_ACTIVE(vap, va_uid)) { if (is_suser && VATTR_IS_SUPPORTED(vap, va_uid)) { nuid = vap->va_uid; } else if (mp->mnt_flag & MNT_IGNORE_OWNERSHIP) { nuid = mp->mnt_fsowner; if (nuid == KAUTH_UID_NONE) { nuid = 99; } } else if (VATTR_IS_SUPPORTED(vap, va_uid)) { nuid = vap->va_uid; } else { /* this will always be something sensible */ nuid = mp->mnt_fsowner; } if ((nuid == 99) && !is_suser) { nuid = kauth_cred_getuid(vfs_context_ucred(ctx)); } VATTR_RETURN(vap, va_uid, nuid); } if (VATTR_IS_ACTIVE(vap, va_gid)) { if (is_suser && VATTR_IS_SUPPORTED(vap, va_gid)) { ngid = vap->va_gid; } else if (mp->mnt_flag & MNT_IGNORE_OWNERSHIP) { ngid = mp->mnt_fsgroup; if (ngid == KAUTH_GID_NONE) { ngid = 99; } } else if (VATTR_IS_SUPPORTED(vap, va_gid)) { ngid = vap->va_gid; } else { /* this will always be something sensible */ ngid = mp->mnt_fsgroup; } if ((ngid == 99) && !is_suser) { ngid = kauth_cred_getgid(vfs_context_ucred(ctx)); } VATTR_RETURN(vap, va_gid, ngid); } } /* * Returns: 0 Success * ENOMEM Not enough space [only if has filesec] * EINVAL Requested unknown attributes * VNOP_GETATTR: ??? * vnode_get_filesec: ??? * kauth_cred_guid2uid: ??? * kauth_cred_guid2gid: ??? * vfs_update_vfsstat: ??? */ int vnode_getattr(vnode_t vp, struct vnode_attr *vap, vfs_context_t ctx) { kauth_filesec_t fsec; kauth_acl_t facl; int error; /* * Reject attempts to fetch unknown attributes. */ if (vap->va_active & ~VNODE_ATTR_ALL) { return EINVAL; } /* don't ask for extended security data if the filesystem doesn't support it */ if (!vfs_extendedsecurity(vnode_mount(vp))) { VATTR_CLEAR_ACTIVE(vap, va_acl); VATTR_CLEAR_ACTIVE(vap, va_uuuid); VATTR_CLEAR_ACTIVE(vap, va_guuid); } /* * If the caller wants size values we might have to synthesise, give the * filesystem the opportunity to supply better intermediate results. */ if (VATTR_IS_ACTIVE(vap, va_data_alloc) || VATTR_IS_ACTIVE(vap, va_total_size) || VATTR_IS_ACTIVE(vap, va_total_alloc)) { VATTR_SET_ACTIVE(vap, va_data_size); VATTR_SET_ACTIVE(vap, va_data_alloc); VATTR_SET_ACTIVE(vap, va_total_size); VATTR_SET_ACTIVE(vap, va_total_alloc); } vap->va_vaflags &= ~VA_USEFSID; error = VNOP_GETATTR(vp, vap, ctx); if (error) { KAUTH_DEBUG("ERROR - returning %d", error); goto out; } /* * If extended security data was requested but not returned, try the fallback * path. */ if (VATTR_NOT_RETURNED(vap, va_acl) || VATTR_NOT_RETURNED(vap, va_uuuid) || VATTR_NOT_RETURNED(vap, va_guuid)) { fsec = NULL; if (XATTR_VNODE_SUPPORTED(vp)) { /* try to get the filesec */ if ((error = vnode_get_filesec(vp, &fsec, ctx)) != 0) { goto out; } } /* if no filesec, no attributes */ if (fsec == NULL) { VATTR_RETURN(vap, va_acl, NULL); VATTR_RETURN(vap, va_uuuid, kauth_null_guid); VATTR_RETURN(vap, va_guuid, kauth_null_guid); } else { /* looks good, try to return what we were asked for */ VATTR_RETURN(vap, va_uuuid, fsec->fsec_owner); VATTR_RETURN(vap, va_guuid, fsec->fsec_group); /* only return the ACL if we were actually asked for it */ if (VATTR_IS_ACTIVE(vap, va_acl)) { if (fsec->fsec_acl.acl_entrycount == KAUTH_FILESEC_NOACL) { VATTR_RETURN(vap, va_acl, NULL); } else { facl = kauth_acl_alloc(fsec->fsec_acl.acl_entrycount); if (facl == NULL) { kauth_filesec_free(fsec); error = ENOMEM; goto out; } __nochk_bcopy(&fsec->fsec_acl, facl, KAUTH_ACL_COPYSIZE(&fsec->fsec_acl)); VATTR_RETURN(vap, va_acl, facl); } } kauth_filesec_free(fsec); } } /* * If someone gave us an unsolicited filesec, toss it. We promise that * we're OK with a filesystem giving us anything back, but our callers * only expect what they asked for. */ if (VATTR_IS_SUPPORTED(vap, va_acl) && !VATTR_IS_ACTIVE(vap, va_acl)) { if (vap->va_acl != NULL) { kauth_acl_free(vap->va_acl); } VATTR_CLEAR_SUPPORTED(vap, va_acl); } #if 0 /* enable when we have a filesystem only supporting UUIDs */ /* * Handle the case where we need a UID/GID, but only have extended * security information. */ if (VATTR_NOT_RETURNED(vap, va_uid) && VATTR_IS_SUPPORTED(vap, va_uuuid) && !kauth_guid_equal(&vap->va_uuuid, &kauth_null_guid)) { if ((error = kauth_cred_guid2uid(&vap->va_uuuid, &nuid)) == 0) { VATTR_RETURN(vap, va_uid, nuid); } } if (VATTR_NOT_RETURNED(vap, va_gid) && VATTR_IS_SUPPORTED(vap, va_guuid) && !kauth_guid_equal(&vap->va_guuid, &kauth_null_guid)) { if ((error = kauth_cred_guid2gid(&vap->va_guuid, &ngid)) == 0) { VATTR_RETURN(vap, va_gid, ngid); } } #endif vnode_attr_handle_uid_and_gid(vap, vp->v_mount, ctx); /* * Synthesise some values that can be reasonably guessed. */ if (!VATTR_IS_SUPPORTED(vap, va_iosize)) { assert(vp->v_mount->mnt_vfsstat.f_iosize <= UINT32_MAX); VATTR_RETURN(vap, va_iosize, (uint32_t)vp->v_mount->mnt_vfsstat.f_iosize); } if (!VATTR_IS_SUPPORTED(vap, va_flags)) { VATTR_RETURN(vap, va_flags, 0); } if (!VATTR_IS_SUPPORTED(vap, va_filerev)) { VATTR_RETURN(vap, va_filerev, 0); } if (!VATTR_IS_SUPPORTED(vap, va_gen)) { VATTR_RETURN(vap, va_gen, 0); } /* * Default sizes. Ordering here is important, as later defaults build on earlier ones. */ if (!VATTR_IS_SUPPORTED(vap, va_data_size)) { VATTR_RETURN(vap, va_data_size, 0); } /* do we want any of the possibly-computed values? */ if (VATTR_IS_ACTIVE(vap, va_data_alloc) || VATTR_IS_ACTIVE(vap, va_total_size) || VATTR_IS_ACTIVE(vap, va_total_alloc)) { /* make sure f_bsize is valid */ if (vp->v_mount->mnt_vfsstat.f_bsize == 0) { if ((error = vfs_update_vfsstat(vp->v_mount, ctx, VFS_KERNEL_EVENT)) != 0) { goto out; } } /* default va_data_alloc from va_data_size */ if (!VATTR_IS_SUPPORTED(vap, va_data_alloc)) { VATTR_RETURN(vap, va_data_alloc, roundup(vap->va_data_size, vp->v_mount->mnt_vfsstat.f_bsize)); } /* default va_total_size from va_data_size */ if (!VATTR_IS_SUPPORTED(vap, va_total_size)) { VATTR_RETURN(vap, va_total_size, vap->va_data_size); } /* default va_total_alloc from va_total_size which is guaranteed at this point */ if (!VATTR_IS_SUPPORTED(vap, va_total_alloc)) { VATTR_RETURN(vap, va_total_alloc, roundup(vap->va_total_size, vp->v_mount->mnt_vfsstat.f_bsize)); } } /* * If we don't have a change time, pull it from the modtime. */ if (!VATTR_IS_SUPPORTED(vap, va_change_time) && VATTR_IS_SUPPORTED(vap, va_modify_time)) { VATTR_RETURN(vap, va_change_time, vap->va_modify_time); } /* * This is really only supported for the creation VNOPs, but since the field is there * we should populate it correctly. */ VATTR_RETURN(vap, va_type, vp->v_type); /* * The fsid can be obtained from the mountpoint directly. */ if (VATTR_IS_ACTIVE(vap, va_fsid) && (!VATTR_IS_SUPPORTED(vap, va_fsid) || vap->va_vaflags & VA_REALFSID || !(vap->va_vaflags & VA_USEFSID))) { VATTR_RETURN(vap, va_fsid, vp->v_mount->mnt_vfsstat.f_fsid.val[0]); } out: vap->va_vaflags &= ~VA_USEFSID; return error; } /* * Choose 32 bit or 64 bit fsid */ uint64_t vnode_get_va_fsid(struct vnode_attr *vap) { if (VATTR_IS_SUPPORTED(vap, va_fsid64)) { return (uint64_t)vap->va_fsid64.val[0] + ((uint64_t)vap->va_fsid64.val[1] << 32); } return vap->va_fsid; } /* * Set the attributes on a vnode in a vnode context. * * Parameters: vp The vnode whose attributes to set. * vap A pointer to the attributes to set. * ctx The vnode context in which the * operation is to be attempted. * * Returns: 0 Success * !0 errno value * * Notes: The kauth_filesec_t in 'vap', if any, is in host byte order. * * The contents of the data area pointed to by 'vap' may be * modified if the vnode is on a filesystem which has been * mounted with ingore ownership flags, or by the underlyng * VFS itself, or by the fallback code, if the underlying VFS * does not support ACL, UUID, or GUUID attributes directly. * * XXX: We should enummerate the possible errno values here, and where * in the code they originated. */ int vnode_setattr(vnode_t vp, struct vnode_attr *vap, vfs_context_t ctx) { int error; #if CONFIG_FSE uint64_t active; int is_perm_change = 0; int is_stat_change = 0; #endif /* * Reject attempts to set unknown attributes. */ if (vap->va_active & ~VNODE_ATTR_ALL) { return EINVAL; } /* * Make sure the filesystem is mounted R/W. * If not, return an error. */ if (vfs_isrdonly(vp->v_mount)) { error = EROFS; goto out; } #if DEVELOPMENT || DEBUG /* * XXX VSWAP: Check for entitlements or special flag here * so we can restrict access appropriately. */ #else /* DEVELOPMENT || DEBUG */ if (vnode_isswap(vp) && (ctx != vfs_context_kernel())) { error = EPERM; goto out; } #endif /* DEVELOPMENT || DEBUG */ #if NAMEDSTREAMS /* For streams, va_data_size is the only setable attribute. */ if ((vp->v_flag & VISNAMEDSTREAM) && (vap->va_active != VNODE_ATTR_va_data_size)) { error = EPERM; goto out; } #endif /* Check for truncation */ if (VATTR_IS_ACTIVE(vap, va_data_size)) { switch (vp->v_type) { case VREG: /* For regular files it's ok */ break; case VDIR: /* Not allowed to truncate directories */ error = EISDIR; goto out; default: /* For everything else we will clear the bit and let underlying FS decide on the rest */ VATTR_CLEAR_ACTIVE(vap, va_data_size); if (vap->va_active) { break; } /* If it was the only bit set, return success, to handle cases like redirect to /dev/null */ return 0; } } /* * If ownership is being ignored on this volume, we silently discard * ownership changes. */ if (vp->v_mount->mnt_flag & MNT_IGNORE_OWNERSHIP) { VATTR_CLEAR_ACTIVE(vap, va_uid); VATTR_CLEAR_ACTIVE(vap, va_gid); } /* * Make sure that extended security is enabled if we're going to try * to set any. */ if (!vfs_extendedsecurity(vnode_mount(vp)) && (VATTR_IS_ACTIVE(vap, va_acl) || VATTR_IS_ACTIVE(vap, va_uuuid) || VATTR_IS_ACTIVE(vap, va_guuid))) { KAUTH_DEBUG("SETATTR - returning ENOTSUP to request to set extended security"); error = ENOTSUP; goto out; } /* Never allow the setting of any unsupported superuser flags. */ if (VATTR_IS_ACTIVE(vap, va_flags)) { vap->va_flags &= (SF_SUPPORTED | UF_SETTABLE); } #if CONFIG_FSE /* * Remember all of the active attributes that we're * attempting to modify. */ active = vap->va_active & ~VNODE_ATTR_RDONLY; #endif error = VNOP_SETATTR(vp, vap, ctx); if ((error == 0) && !VATTR_ALL_SUPPORTED(vap)) { error = vnode_setattr_fallback(vp, vap, ctx); } #if CONFIG_FSE #define PERMISSION_BITS (VNODE_ATTR_BIT(va_uid) | VNODE_ATTR_BIT(va_uuuid) | \ VNODE_ATTR_BIT(va_gid) | VNODE_ATTR_BIT(va_guuid) | \ VNODE_ATTR_BIT(va_mode) | VNODE_ATTR_BIT(va_acl)) /* * Now that we've changed them, decide whether to send an * FSevent. */ if ((active & PERMISSION_BITS) & vap->va_supported) { is_perm_change = 1; } else { /* * We've already checked the permission bits, and we * also want to filter out access time / backup time * changes. */ active &= ~(PERMISSION_BITS | VNODE_ATTR_BIT(va_access_time) | VNODE_ATTR_BIT(va_backup_time)); /* Anything left to notify about? */ if (active & vap->va_supported) { is_stat_change = 1; } } if (error == 0) { if (is_perm_change) { if (need_fsevent(FSE_CHOWN, vp)) { add_fsevent(FSE_CHOWN, ctx, FSE_ARG_VNODE, vp, FSE_ARG_DONE); } } else if (is_stat_change && need_fsevent(FSE_STAT_CHANGED, vp)) { add_fsevent(FSE_STAT_CHANGED, ctx, FSE_ARG_VNODE, vp, FSE_ARG_DONE); } } #undef PERMISSION_BITS #endif out: return error; } /* * Fallback for setting the attributes on a vnode in a vnode context. This * Function will attempt to store ACL, UUID, and GUID information utilizing * a read/modify/write operation against an EA used as a backing store for * the object. * * Parameters: vp The vnode whose attributes to set. * vap A pointer to the attributes to set. * ctx The vnode context in which the * operation is to be attempted. * * Returns: 0 Success * !0 errno value * * Notes: The kauth_filesec_t in 'vap', if any, is in host byte order, * as are the fsec and lfsec, if they are used. * * The contents of the data area pointed to by 'vap' may be * modified to indicate that the attribute is supported for * any given requested attribute. * * XXX: We should enummerate the possible errno values here, and where * in the code they originated. */ int vnode_setattr_fallback(vnode_t vp, struct vnode_attr *vap, vfs_context_t ctx) { kauth_filesec_t fsec; kauth_acl_t facl; struct kauth_filesec lfsec; int error; error = 0; /* * Extended security fallback via extended attributes. * * Note that we do not free the filesec; the caller is expected to * do this. */ if (VATTR_NOT_RETURNED(vap, va_acl) || VATTR_NOT_RETURNED(vap, va_uuuid) || VATTR_NOT_RETURNED(vap, va_guuid)) { VFS_DEBUG(ctx, vp, "SETATTR - doing filesec fallback"); /* * Fail for file types that we don't permit extended security * to be set on. */ if (!XATTR_VNODE_SUPPORTED(vp)) { VFS_DEBUG(ctx, vp, "SETATTR - Can't write ACL to file type %d", vnode_vtype(vp)); error = EINVAL; goto out; } /* * If we don't have all the extended security items, we need * to fetch the existing data to perform a read-modify-write * operation. */ fsec = NULL; if (!VATTR_IS_ACTIVE(vap, va_acl) || !VATTR_IS_ACTIVE(vap, va_uuuid) || !VATTR_IS_ACTIVE(vap, va_guuid)) { if ((error = vnode_get_filesec(vp, &fsec, ctx)) != 0) { KAUTH_DEBUG("SETATTR - ERROR %d fetching filesec for update", error); goto out; } } /* if we didn't get a filesec, use our local one */ if (fsec == NULL) { KAUTH_DEBUG("SETATTR - using local filesec for new/full update"); fsec = &lfsec; } else { KAUTH_DEBUG("SETATTR - updating existing filesec"); } /* find the ACL */ facl = &fsec->fsec_acl; /* if we're using the local filesec, we need to initialise it */ if (fsec == &lfsec) { fsec->fsec_magic = KAUTH_FILESEC_MAGIC; fsec->fsec_owner = kauth_null_guid; fsec->fsec_group = kauth_null_guid; facl->acl_entrycount = KAUTH_FILESEC_NOACL; facl->acl_flags = 0; } /* * Update with the supplied attributes. */ if (VATTR_IS_ACTIVE(vap, va_uuuid)) { KAUTH_DEBUG("SETATTR - updating owner UUID"); fsec->fsec_owner = vap->va_uuuid; VATTR_SET_SUPPORTED(vap, va_uuuid); } if (VATTR_IS_ACTIVE(vap, va_guuid)) { KAUTH_DEBUG("SETATTR - updating group UUID"); fsec->fsec_group = vap->va_guuid; VATTR_SET_SUPPORTED(vap, va_guuid); } if (VATTR_IS_ACTIVE(vap, va_acl)) { if (vap->va_acl == NULL) { KAUTH_DEBUG("SETATTR - removing ACL"); facl->acl_entrycount = KAUTH_FILESEC_NOACL; } else { KAUTH_DEBUG("SETATTR - setting ACL with %d entries", vap->va_acl->acl_entrycount); facl = vap->va_acl; } VATTR_SET_SUPPORTED(vap, va_acl); } /* * If the filesec data is all invalid, we can just remove * the EA completely. */ if ((facl->acl_entrycount == KAUTH_FILESEC_NOACL) && kauth_guid_equal(&fsec->fsec_owner, &kauth_null_guid) && kauth_guid_equal(&fsec->fsec_group, &kauth_null_guid)) { error = vn_removexattr(vp, KAUTH_FILESEC_XATTR, XATTR_NOSECURITY, ctx); /* no attribute is ok, nothing to delete */ if (error == ENOATTR) { error = 0; } VFS_DEBUG(ctx, vp, "SETATTR - remove filesec returning %d", error); } else { /* write the EA */ error = vnode_set_filesec(vp, fsec, facl, ctx); VFS_DEBUG(ctx, vp, "SETATTR - update filesec returning %d", error); } /* if we fetched a filesec, dispose of the buffer */ if (fsec != &lfsec) { kauth_filesec_free(fsec); } } out: return error; } /* * Upcall for a filesystem to tell VFS about an EVFILT_VNODE-type * event on a vnode. */ int vnode_notify(vnode_t vp, uint32_t events, struct vnode_attr *vap) { /* These are the same as the corresponding knotes, at least for now. Cheating a little. */ uint32_t knote_mask = (VNODE_EVENT_WRITE | VNODE_EVENT_DELETE | VNODE_EVENT_RENAME | VNODE_EVENT_LINK | VNODE_EVENT_EXTEND | VNODE_EVENT_ATTRIB); uint32_t dir_contents_mask = (VNODE_EVENT_DIR_CREATED | VNODE_EVENT_FILE_CREATED | VNODE_EVENT_DIR_REMOVED | VNODE_EVENT_FILE_REMOVED); uint32_t knote_events = (events & knote_mask); /* Permissions are not explicitly part of the kqueue model */ if (events & VNODE_EVENT_PERMS) { knote_events |= NOTE_ATTRIB; } /* Directory contents information just becomes NOTE_WRITE */ if ((vnode_isdir(vp)) && (events & dir_contents_mask)) { knote_events |= NOTE_WRITE; } if (knote_events) { lock_vnode_and_post(vp, knote_events); #if CONFIG_FSE if (vap != NULL) { create_fsevent_from_kevent(vp, events, vap); } #else (void)vap; #endif } return 0; } int vnode_isdyldsharedcache(vnode_t vp) { return (vp->v_flag & VSHARED_DYLD) ? 1 : 0; } /* * For a filesystem that isn't tracking its own vnode watchers: * check whether a vnode is being monitored. */ int vnode_ismonitored(vnode_t vp) { return vp->v_knotes.slh_first != NULL; } int vnode_getbackingvnode(vnode_t in_vp, vnode_t* out_vpp) { if (out_vpp) { *out_vpp = NULLVP; } #if NULLFS return nullfs_getbackingvnode(in_vp, out_vpp); #else #pragma unused(in_vp) return ENOENT; #endif } /* * Initialize a struct vnode_attr and activate the attributes required * by the vnode_notify() call. */ int vfs_get_notify_attributes(struct vnode_attr *vap) { VATTR_INIT(vap); vap->va_active = VNODE_NOTIFY_ATTRS; return 0; } #if CONFIG_TRIGGERS int vfs_settriggercallback(fsid_t *fsid, vfs_trigger_callback_t vtc, void *data, uint32_t flags __unused, vfs_context_t ctx) { int error; mount_t mp; mp = mount_list_lookupby_fsid(fsid, 0 /* locked */, 1 /* withref */); if (mp == NULL) { return ENOENT; } error = vfs_busy(mp, LK_NOWAIT); mount_iterdrop(mp); if (error != 0) { return ENOENT; } mount_lock(mp); if (mp->mnt_triggercallback != NULL) { error = EBUSY; mount_unlock(mp); goto out; } mp->mnt_triggercallback = vtc; mp->mnt_triggerdata = data; mount_unlock(mp); mp->mnt_triggercallback(mp, VTC_REPLACE, data, ctx); out: vfs_unbusy(mp); return 0; } #endif /* CONFIG_TRIGGERS */ /* * Definition of vnode operations. */ #if 0 /* *# *#% lookup dvp L ? ? *#% lookup vpp - L - */ struct vnop_lookup_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; vnode_t *a_vpp; struct componentname *a_cnp; vfs_context_t a_context; }; #endif /* 0*/ /* * Returns: 0 Success * lock_fsnode:ENOENT No such file or directory [only for VFS * that is not thread safe & vnode is * currently being/has been terminated] * :ENAMETOOLONG * :ENOENT * :EJUSTRETURN * :EPERM * :EISDIR * :ENOTDIR * :??? * * Note: The return codes from the underlying VFS's lookup routine can't * be fully enumerated here, since third party VFS authors may not * limit their error returns to the ones documented here, even * though this may result in some programs functioning incorrectly. * * The return codes documented above are those which may currently * be returned by HFS from hfs_lookup, not including additional * error code which may be propagated from underlying routines. */ errno_t VNOP_LOOKUP(vnode_t dvp, vnode_t *vpp, struct componentname *cnp, vfs_context_t ctx) { int _err; struct vnop_lookup_args a; a.a_desc = &vnop_lookup_desc; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = cnp; a.a_context = ctx; _err = (*dvp->v_op[vnop_lookup_desc.vdesc_offset])(&a); if (_err == 0 && *vpp) { DTRACE_FSINFO(lookup, vnode_t, *vpp); } return _err; } #if 0 struct vnop_compound_open_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; vnode_t *a_vpp; struct componentname *a_cnp; int32_t a_flags; int32_t a_fmode; struct vnode_attr *a_vap; vfs_context_t a_context; void *a_reserved; }; #endif /* 0 */ int VNOP_COMPOUND_OPEN(vnode_t dvp, vnode_t *vpp, struct nameidata *ndp, int32_t flags, int32_t fmode, uint32_t *statusp, struct vnode_attr *vap, vfs_context_t ctx) { int _err; struct vnop_compound_open_args a; int did_create = 0; int want_create; uint32_t tmp_status = 0; struct componentname *cnp = &ndp->ni_cnd; want_create = (flags & O_CREAT); a.a_desc = &vnop_compound_open_desc; a.a_dvp = dvp; a.a_vpp = vpp; /* Could be NULL */ a.a_cnp = cnp; a.a_flags = flags; a.a_fmode = fmode; a.a_status = (statusp != NULL) ? statusp : &tmp_status; a.a_vap = vap; a.a_context = ctx; a.a_open_create_authorizer = vn_authorize_create; a.a_open_existing_authorizer = vn_authorize_open_existing; a.a_reserved = NULL; if (dvp == NULLVP) { panic("No dvp?"); } if (want_create && !vap) { panic("Want create, but no vap?"); } if (!want_create && vap) { panic("Don't want create, but have a vap?"); } _err = (*dvp->v_op[vnop_compound_open_desc.vdesc_offset])(&a); if (want_create) { if (_err == 0 && *vpp) { DTRACE_FSINFO(compound_open, vnode_t, *vpp); } else { DTRACE_FSINFO(compound_open, vnode_t, dvp); } } else { DTRACE_FSINFO(compound_open, vnode_t, *vpp); } did_create = (*a.a_status & COMPOUND_OPEN_STATUS_DID_CREATE); if (did_create && !want_create) { panic("Filesystem did a create, even though none was requested?"); } if (did_create) { #if CONFIG_APPLEDOUBLE if (!NATIVE_XATTR(dvp)) { /* * Remove stale Apple Double file (if any). */ xattrfile_remove(dvp, cnp->cn_nameptr, ctx, 0); } #endif /* CONFIG_APPLEDOUBLE */ /* On create, provide kqueue notification */ post_event_if_success(dvp, _err, NOTE_WRITE); } lookup_compound_vnop_post_hook(_err, dvp, *vpp, ndp, did_create); #if 0 /* FSEvents... */ if (*vpp && _err && _err != EKEEPLOOKING) { vnode_put(*vpp); *vpp = NULLVP; } #endif /* 0 */ return _err; } #if 0 struct vnop_create_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; vnode_t *a_vpp; struct componentname *a_cnp; struct vnode_attr *a_vap; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_CREATE(vnode_t dvp, vnode_t * vpp, struct componentname * cnp, struct vnode_attr * vap, vfs_context_t ctx) { int _err; struct vnop_create_args a; a.a_desc = &vnop_create_desc; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = cnp; a.a_vap = vap; a.a_context = ctx; _err = (*dvp->v_op[vnop_create_desc.vdesc_offset])(&a); if (_err == 0 && *vpp) { DTRACE_FSINFO(create, vnode_t, *vpp); } #if CONFIG_APPLEDOUBLE if (_err == 0 && !NATIVE_XATTR(dvp)) { /* * Remove stale Apple Double file (if any). */ xattrfile_remove(dvp, cnp->cn_nameptr, ctx, 0); } #endif /* CONFIG_APPLEDOUBLE */ post_event_if_success(dvp, _err, NOTE_WRITE); return _err; } #if 0 /* *# *#% whiteout dvp L L L *#% whiteout cnp - - - *#% whiteout flag - - - *# */ struct vnop_whiteout_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; struct componentname *a_cnp; int a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_WHITEOUT(__unused vnode_t dvp, __unused struct componentname *cnp, __unused int flags, __unused vfs_context_t ctx) { return ENOTSUP; // XXX OBSOLETE } #if 0 /* *# *#% mknod dvp L U U *#% mknod vpp - X - *# */ struct vnop_mknod_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; vnode_t *a_vpp; struct componentname *a_cnp; struct vnode_attr *a_vap; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_MKNOD(vnode_t dvp, vnode_t * vpp, struct componentname * cnp, struct vnode_attr * vap, vfs_context_t ctx) { int _err; struct vnop_mknod_args a; a.a_desc = &vnop_mknod_desc; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = cnp; a.a_vap = vap; a.a_context = ctx; _err = (*dvp->v_op[vnop_mknod_desc.vdesc_offset])(&a); if (_err == 0 && *vpp) { DTRACE_FSINFO(mknod, vnode_t, *vpp); } post_event_if_success(dvp, _err, NOTE_WRITE); return _err; } #if 0 /* *# *#% open vp L L L *# */ struct vnop_open_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_mode; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_OPEN(vnode_t vp, int mode, vfs_context_t ctx) { int _err; struct vnop_open_args a; if (ctx == NULL) { ctx = vfs_context_current(); } a.a_desc = &vnop_open_desc; a.a_vp = vp; a.a_mode = mode; a.a_context = ctx; _err = (*vp->v_op[vnop_open_desc.vdesc_offset])(&a); DTRACE_FSINFO(open, vnode_t, vp); return _err; } #if 0 /* *# *#% close vp U U U *# */ struct vnop_close_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_fflag; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_CLOSE(vnode_t vp, int fflag, vfs_context_t ctx) { int _err; struct vnop_close_args a; if (ctx == NULL) { ctx = vfs_context_current(); } a.a_desc = &vnop_close_desc; a.a_vp = vp; a.a_fflag = fflag; a.a_context = ctx; _err = (*vp->v_op[vnop_close_desc.vdesc_offset])(&a); DTRACE_FSINFO(close, vnode_t, vp); return _err; } #if 0 /* *# *#% access vp L L L *# */ struct vnop_access_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_action; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_ACCESS(vnode_t vp, int action, vfs_context_t ctx) { int _err; struct vnop_access_args a; if (ctx == NULL) { ctx = vfs_context_current(); } a.a_desc = &vnop_access_desc; a.a_vp = vp; a.a_action = action; a.a_context = ctx; _err = (*vp->v_op[vnop_access_desc.vdesc_offset])(&a); DTRACE_FSINFO(access, vnode_t, vp); return _err; } #if 0 /* *# *#% getattr vp = = = *# */ struct vnop_getattr_args { struct vnodeop_desc *a_desc; vnode_t a_vp; struct vnode_attr *a_vap; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_GETATTR(vnode_t vp, struct vnode_attr * vap, vfs_context_t ctx) { int _err; struct vnop_getattr_args a; a.a_desc = &vnop_getattr_desc; a.a_vp = vp; a.a_vap = vap; a.a_context = ctx; _err = (*vp->v_op[vnop_getattr_desc.vdesc_offset])(&a); DTRACE_FSINFO(getattr, vnode_t, vp); return _err; } #if 0 /* *# *#% setattr vp L L L *# */ struct vnop_setattr_args { struct vnodeop_desc *a_desc; vnode_t a_vp; struct vnode_attr *a_vap; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_SETATTR(vnode_t vp, struct vnode_attr * vap, vfs_context_t ctx) { int _err; struct vnop_setattr_args a; a.a_desc = &vnop_setattr_desc; a.a_vp = vp; a.a_vap = vap; a.a_context = ctx; _err = (*vp->v_op[vnop_setattr_desc.vdesc_offset])(&a); DTRACE_FSINFO(setattr, vnode_t, vp); #if CONFIG_APPLEDOUBLE /* * Shadow uid/gid/mod change to extended attribute file. */ if (_err == 0 && !NATIVE_XATTR(vp)) { struct vnode_attr va; int change = 0; VATTR_INIT(&va); if (VATTR_IS_ACTIVE(vap, va_uid)) { VATTR_SET(&va, va_uid, vap->va_uid); change = 1; } if (VATTR_IS_ACTIVE(vap, va_gid)) { VATTR_SET(&va, va_gid, vap->va_gid); change = 1; } if (VATTR_IS_ACTIVE(vap, va_mode)) { VATTR_SET(&va, va_mode, vap->va_mode); change = 1; } if (change) { vnode_t dvp; const char *vname; dvp = vnode_getparent(vp); vname = vnode_getname(vp); xattrfile_setattr(dvp, vname, &va, ctx); if (dvp != NULLVP) { vnode_put(dvp); } if (vname != NULL) { vnode_putname(vname); } } } #endif /* CONFIG_APPLEDOUBLE */ /* * If we have changed any of the things about the file that are likely * to result in changes to authorization results, blow the vnode auth * cache */ if (_err == 0 && ( VATTR_IS_SUPPORTED(vap, va_mode) || VATTR_IS_SUPPORTED(vap, va_uid) || VATTR_IS_SUPPORTED(vap, va_gid) || VATTR_IS_SUPPORTED(vap, va_flags) || VATTR_IS_SUPPORTED(vap, va_acl) || VATTR_IS_SUPPORTED(vap, va_uuuid) || VATTR_IS_SUPPORTED(vap, va_guuid))) { vnode_uncache_authorized_action(vp, KAUTH_INVALIDATE_CACHED_RIGHTS); #if NAMEDSTREAMS if (vfs_authopaque(vp->v_mount) && vnode_hasnamedstreams(vp)) { vnode_t svp; if (vnode_getnamedstream(vp, &svp, XATTR_RESOURCEFORK_NAME, NS_OPEN, 0, ctx) == 0) { vnode_uncache_authorized_action(svp, KAUTH_INVALIDATE_CACHED_RIGHTS); vnode_put(svp); } } #endif /* NAMEDSTREAMS */ } post_event_if_success(vp, _err, NOTE_ATTRIB); return _err; } #if 0 /* *# *#% read vp L L L *# */ struct vnop_read_args { struct vnodeop_desc *a_desc; vnode_t a_vp; struct uio *a_uio; int a_ioflag; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_READ(vnode_t vp, struct uio * uio, int ioflag, vfs_context_t ctx) { int _err; struct vnop_read_args a; #if CONFIG_DTRACE user_ssize_t resid = uio_resid(uio); #endif if (ctx == NULL) { return EINVAL; } a.a_desc = &vnop_read_desc; a.a_vp = vp; a.a_uio = uio; a.a_ioflag = ioflag; a.a_context = ctx; _err = (*vp->v_op[vnop_read_desc.vdesc_offset])(&a); DTRACE_FSINFO_IO(read, vnode_t, vp, user_ssize_t, (resid - uio_resid(uio))); return _err; } #if 0 /* *# *#% write vp L L L *# */ struct vnop_write_args { struct vnodeop_desc *a_desc; vnode_t a_vp; struct uio *a_uio; int a_ioflag; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_WRITE(vnode_t vp, struct uio * uio, int ioflag, vfs_context_t ctx) { struct vnop_write_args a; int _err; #if CONFIG_DTRACE user_ssize_t resid = uio_resid(uio); #endif if (ctx == NULL) { return EINVAL; } a.a_desc = &vnop_write_desc; a.a_vp = vp; a.a_uio = uio; a.a_ioflag = ioflag; a.a_context = ctx; _err = (*vp->v_op[vnop_write_desc.vdesc_offset])(&a); DTRACE_FSINFO_IO(write, vnode_t, vp, user_ssize_t, (resid - uio_resid(uio))); post_event_if_success(vp, _err, NOTE_WRITE); return _err; } #if 0 /* *# *#% ioctl vp U U U *# */ struct vnop_ioctl_args { struct vnodeop_desc *a_desc; vnode_t a_vp; u_long a_command; caddr_t a_data; int a_fflag; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_IOCTL(vnode_t vp, u_long command, caddr_t data, int fflag, vfs_context_t ctx) { int _err; struct vnop_ioctl_args a; if (ctx == NULL) { ctx = vfs_context_current(); } /* * This check should probably have been put in the TTY code instead... * * We have to be careful about what we assume during startup and shutdown. * We have to be able to use the root filesystem's device vnode even when * devfs isn't mounted (yet/anymore), so we can't go looking at its mount * structure. If there is no data pointer, it doesn't matter whether * the device is 64-bit ready. Any command (like DKIOCSYNCHRONIZE) * which passes NULL for its data pointer can therefore be used during * mount or unmount of the root filesystem. * * Depending on what root filesystems need to do during mount/unmount, we * may need to loosen this check again in the future. */ if (vfs_context_is64bit(ctx) && !(vnode_ischr(vp) || vnode_isblk(vp))) { if (data != NULL && !vnode_vfs64bitready(vp)) { return ENOTTY; } } if ((command == DKIOCISSOLIDSTATE) && (vp == rootvp) && rootvp_is_ssd && data) { *data = 1; return 0; } a.a_desc = &vnop_ioctl_desc; a.a_vp = vp; a.a_command = command; a.a_data = data; a.a_fflag = fflag; a.a_context = ctx; _err = (*vp->v_op[vnop_ioctl_desc.vdesc_offset])(&a); DTRACE_FSINFO(ioctl, vnode_t, vp); return _err; } #if 0 /* *# *#% select vp U U U *# */ struct vnop_select_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_which; int a_fflags; void *a_wql; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_SELECT(vnode_t vp, int which, int fflags, void * wql, vfs_context_t ctx) { int _err; struct vnop_select_args a; if (ctx == NULL) { ctx = vfs_context_current(); } a.a_desc = &vnop_select_desc; a.a_vp = vp; a.a_which = which; a.a_fflags = fflags; a.a_context = ctx; a.a_wql = wql; _err = (*vp->v_op[vnop_select_desc.vdesc_offset])(&a); DTRACE_FSINFO(select, vnode_t, vp); return _err; } #if 0 /* *# *#% exchange fvp L L L *#% exchange tvp L L L *# */ struct vnop_exchange_args { struct vnodeop_desc *a_desc; vnode_t a_fvp; vnode_t a_tvp; int a_options; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_EXCHANGE(vnode_t fvp, vnode_t tvp, int options, vfs_context_t ctx) { int _err; struct vnop_exchange_args a; a.a_desc = &vnop_exchange_desc; a.a_fvp = fvp; a.a_tvp = tvp; a.a_options = options; a.a_context = ctx; _err = (*fvp->v_op[vnop_exchange_desc.vdesc_offset])(&a); DTRACE_FSINFO(exchange, vnode_t, fvp); /* Don't post NOTE_WRITE because file descriptors follow the data ... */ post_event_if_success(fvp, _err, NOTE_ATTRIB); post_event_if_success(tvp, _err, NOTE_ATTRIB); return _err; } #if 0 /* *# *#% revoke vp U U U *# */ struct vnop_revoke_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_REVOKE(vnode_t vp, int flags, vfs_context_t ctx) { struct vnop_revoke_args a; int _err; a.a_desc = &vnop_revoke_desc; a.a_vp = vp; a.a_flags = flags; a.a_context = ctx; _err = (*vp->v_op[vnop_revoke_desc.vdesc_offset])(&a); DTRACE_FSINFO(revoke, vnode_t, vp); return _err; } #if 0 /* *# *# mmap_check - vp U U U *# */ struct vnop_mmap_check_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_flags; vfs_context_t a_context; }; #endif /* 0 */ errno_t VNOP_MMAP_CHECK(vnode_t vp, int flags, vfs_context_t ctx) { int _err; struct vnop_mmap_check_args a; a.a_desc = &vnop_mmap_check_desc; a.a_vp = vp; a.a_flags = flags; a.a_context = ctx; _err = (*vp->v_op[vnop_mmap_check_desc.vdesc_offset])(&a); if (_err == ENOTSUP) { _err = 0; } DTRACE_FSINFO(mmap_check, vnode_t, vp); return _err; } #if 0 /* *# *# mmap - vp U U U *# */ struct vnop_mmap_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_fflags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_MMAP(vnode_t vp, int fflags, vfs_context_t ctx) { int _err; struct vnop_mmap_args a; a.a_desc = &vnop_mmap_desc; a.a_vp = vp; a.a_fflags = fflags; a.a_context = ctx; _err = (*vp->v_op[vnop_mmap_desc.vdesc_offset])(&a); DTRACE_FSINFO(mmap, vnode_t, vp); return _err; } #if 0 /* *# *# mnomap - vp U U U *# */ struct vnop_mnomap_args { struct vnodeop_desc *a_desc; vnode_t a_vp; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_MNOMAP(vnode_t vp, vfs_context_t ctx) { int _err; struct vnop_mnomap_args a; a.a_desc = &vnop_mnomap_desc; a.a_vp = vp; a.a_context = ctx; _err = (*vp->v_op[vnop_mnomap_desc.vdesc_offset])(&a); DTRACE_FSINFO(mnomap, vnode_t, vp); return _err; } #if 0 /* *# *#% fsync vp L L L *# */ struct vnop_fsync_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_waitfor; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_FSYNC(vnode_t vp, int waitfor, vfs_context_t ctx) { struct vnop_fsync_args a; int _err; a.a_desc = &vnop_fsync_desc; a.a_vp = vp; a.a_waitfor = waitfor; a.a_context = ctx; _err = (*vp->v_op[vnop_fsync_desc.vdesc_offset])(&a); DTRACE_FSINFO(fsync, vnode_t, vp); return _err; } #if 0 /* *# *#% remove dvp L U U *#% remove vp L U U *# */ struct vnop_remove_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; vnode_t a_vp; struct componentname *a_cnp; int a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_REMOVE(vnode_t dvp, vnode_t vp, struct componentname * cnp, int flags, vfs_context_t ctx) { int _err; struct vnop_remove_args a; a.a_desc = &vnop_remove_desc; a.a_dvp = dvp; a.a_vp = vp; a.a_cnp = cnp; a.a_flags = flags; a.a_context = ctx; _err = (*dvp->v_op[vnop_remove_desc.vdesc_offset])(&a); DTRACE_FSINFO(remove, vnode_t, vp); if (_err == 0) { vnode_setneedinactive(vp); #if CONFIG_APPLEDOUBLE if (!(NATIVE_XATTR(dvp))) { /* * Remove any associated extended attribute file (._ AppleDouble file). */ xattrfile_remove(dvp, cnp->cn_nameptr, ctx, 1); } #endif /* CONFIG_APPLEDOUBLE */ } post_event_if_success(vp, _err, NOTE_DELETE | NOTE_LINK); post_event_if_success(dvp, _err, NOTE_WRITE); return _err; } int VNOP_COMPOUND_REMOVE(vnode_t dvp, vnode_t *vpp, struct nameidata *ndp, int32_t flags, struct vnode_attr *vap, vfs_context_t ctx) { int _err; struct vnop_compound_remove_args a; int no_vp = (*vpp == NULLVP); a.a_desc = &vnop_compound_remove_desc; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = &ndp->ni_cnd; a.a_flags = flags; a.a_vap = vap; a.a_context = ctx; a.a_remove_authorizer = vn_authorize_unlink; _err = (*dvp->v_op[vnop_compound_remove_desc.vdesc_offset])(&a); if (_err == 0 && *vpp) { DTRACE_FSINFO(compound_remove, vnode_t, *vpp); } else { DTRACE_FSINFO(compound_remove, vnode_t, dvp); } if (_err == 0) { vnode_setneedinactive(*vpp); #if CONFIG_APPLEDOUBLE if (!(NATIVE_XATTR(dvp))) { /* * Remove any associated extended attribute file (._ AppleDouble file). */ xattrfile_remove(dvp, ndp->ni_cnd.cn_nameptr, ctx, 1); } #endif /* CONFIG_APPLEDOUBLE */ } post_event_if_success(*vpp, _err, NOTE_DELETE | NOTE_LINK); post_event_if_success(dvp, _err, NOTE_WRITE); if (no_vp) { lookup_compound_vnop_post_hook(_err, dvp, *vpp, ndp, 0); if (*vpp && _err && _err != EKEEPLOOKING) { vnode_put(*vpp); *vpp = NULLVP; } } //printf("VNOP_COMPOUND_REMOVE() returning %d\n", _err); return _err; } #if 0 /* *# *#% link vp U U U *#% link tdvp L U U *# */ struct vnop_link_args { struct vnodeop_desc *a_desc; vnode_t a_vp; vnode_t a_tdvp; struct componentname *a_cnp; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_LINK(vnode_t vp, vnode_t tdvp, struct componentname * cnp, vfs_context_t ctx) { int _err; struct vnop_link_args a; #if CONFIG_APPLEDOUBLE /* * For file systems with non-native extended attributes, * disallow linking to an existing "._" Apple Double file. */ if (!NATIVE_XATTR(tdvp) && (vp->v_type == VREG)) { const char *vname; vname = vnode_getname(vp); if (vname != NULL) { _err = 0; if (vname[0] == '.' && vname[1] == '_' && vname[2] != '\0') { _err = EPERM; } vnode_putname(vname); if (_err) { return _err; } } } #endif /* CONFIG_APPLEDOUBLE */ a.a_desc = &vnop_link_desc; a.a_vp = vp; a.a_tdvp = tdvp; a.a_cnp = cnp; a.a_context = ctx; _err = (*tdvp->v_op[vnop_link_desc.vdesc_offset])(&a); DTRACE_FSINFO(link, vnode_t, vp); post_event_if_success(vp, _err, NOTE_LINK); post_event_if_success(tdvp, _err, NOTE_WRITE); return _err; } errno_t vn_rename(struct vnode *fdvp, struct vnode **fvpp, struct componentname *fcnp, struct vnode_attr *fvap, struct vnode *tdvp, struct vnode **tvpp, struct componentname *tcnp, struct vnode_attr *tvap, vfs_rename_flags_t flags, vfs_context_t ctx) { int _err; struct nameidata *fromnd = NULL; struct nameidata *tond = NULL; #if CONFIG_APPLEDOUBLE vnode_t src_attr_vp = NULLVP; vnode_t dst_attr_vp = NULLVP; char smallname1[48]; char smallname2[48]; char *xfromname = NULL; char *xtoname = NULL; #endif /* CONFIG_APPLEDOUBLE */ int batched; uint32_t tdfflags; // Target directory file flags batched = vnode_compound_rename_available(fdvp); if (!batched) { if (*fvpp == NULLVP) { panic("Not batched, and no fvp?"); } } #if CONFIG_APPLEDOUBLE /* * We need to preflight any potential AppleDouble file for the source file * before doing the rename operation, since we could potentially be doing * this operation on a network filesystem, and would end up duplicating * the work. Also, save the source and destination names. Skip it if the * source has a "._" prefix. */ size_t xfromname_len = 0; size_t xtoname_len = 0; if (!NATIVE_XATTR(fdvp) && !(fcnp->cn_nameptr[0] == '.' && fcnp->cn_nameptr[1] == '_')) { int error; /* Get source attribute file name. */ xfromname_len = fcnp->cn_namelen + 3; if (xfromname_len > sizeof(smallname1)) { xfromname = kalloc_data(xfromname_len, Z_WAITOK); } else { xfromname = &smallname1[0]; } strlcpy(xfromname, "._", xfromname_len); strlcat(xfromname, fcnp->cn_nameptr, xfromname_len); /* Get destination attribute file name. */ xtoname_len = tcnp->cn_namelen + 3; if (xtoname_len > sizeof(smallname2)) { xtoname = kalloc_data(xtoname_len, Z_WAITOK); } else { xtoname = &smallname2[0]; } strlcpy(xtoname, "._", xtoname_len); strlcat(xtoname, tcnp->cn_nameptr, xtoname_len); /* * Look up source attribute file, keep reference on it if exists. * Note that we do the namei with the nameiop of RENAME, which is different than * in the rename syscall. It's OK if the source file does not exist, since this * is only for AppleDouble files. */ fromnd = kalloc_type(struct nameidata, Z_WAITOK); NDINIT(fromnd, RENAME, OP_RENAME, NOFOLLOW | USEDVP | CN_NBMOUNTLOOK, UIO_SYSSPACE, CAST_USER_ADDR_T(xfromname), ctx); fromnd->ni_dvp = fdvp; error = namei(fromnd); /* * If there was an error looking up source attribute file, * we'll behave as if it didn't exist. */ if (error == 0) { if (fromnd->ni_vp) { /* src_attr_vp indicates need to call vnode_put / nameidone later */ src_attr_vp = fromnd->ni_vp; if (fromnd->ni_vp->v_type != VREG) { src_attr_vp = NULLVP; vnode_put(fromnd->ni_vp); } } /* * Either we got an invalid vnode type (not a regular file) or the namei lookup * suppressed ENOENT as a valid error since we're renaming. Either way, we don't * have a vnode here, so we drop our namei buffer for the source attribute file */ if (src_attr_vp == NULLVP) { nameidone(fromnd); } } } #endif /* CONFIG_APPLEDOUBLE */ if (batched) { _err = VNOP_COMPOUND_RENAME(fdvp, fvpp, fcnp, fvap, tdvp, tvpp, tcnp, tvap, flags, ctx); if (_err != 0) { printf("VNOP_COMPOUND_RENAME() returned %d\n", _err); } } else { if (flags) { _err = VNOP_RENAMEX(fdvp, *fvpp, fcnp, tdvp, *tvpp, tcnp, flags, ctx); if (_err == ENOTSUP && flags == VFS_RENAME_SECLUDE) { // Legacy... if ((*fvpp)->v_mount->mnt_vtable->vfc_vfsflags & VFC_VFSVNOP_SECLUDE_RENAME) { fcnp->cn_flags |= CN_SECLUDE_RENAME; _err = VNOP_RENAME(fdvp, *fvpp, fcnp, tdvp, *tvpp, tcnp, ctx); } } } else { _err = VNOP_RENAME(fdvp, *fvpp, fcnp, tdvp, *tvpp, tcnp, ctx); } } /* * If moved to a new directory that is restricted, * set the restricted flag on the item moved. */ if (_err == 0) { _err = vnode_flags(tdvp, &tdfflags, ctx); if (_err == 0) { uint32_t inherit_flags = tdfflags & (UF_DATAVAULT | SF_RESTRICTED); if (inherit_flags) { uint32_t fflags; _err = vnode_flags(*fvpp, &fflags, ctx); if (_err == 0 && fflags != (fflags | inherit_flags)) { struct vnode_attr va; VATTR_INIT(&va); VATTR_SET(&va, va_flags, fflags | inherit_flags); _err = vnode_setattr(*fvpp, &va, ctx); } } } } #if CONFIG_MACF if (_err == 0) { mac_vnode_notify_rename( ctx, /* ctx */ *fvpp, /* fvp */ fdvp, /* fdvp */ fcnp, /* fcnp */ *tvpp, /* tvp */ tdvp, /* tdvp */ tcnp, /* tcnp */ (flags & VFS_RENAME_SWAP) /* swap */ ); } #endif #if CONFIG_APPLEDOUBLE /* * Rename any associated extended attribute file (._ AppleDouble file). */ if (_err == 0 && !NATIVE_XATTR(fdvp) && xfromname != NULL) { int error = 0; /* * Get destination attribute file vnode. * Note that tdvp already has an iocount reference. Make sure to check that we * get a valid vnode from namei. */ tond = kalloc_type(struct nameidata, Z_WAITOK); NDINIT(tond, RENAME, OP_RENAME, NOCACHE | NOFOLLOW | USEDVP | CN_NBMOUNTLOOK, UIO_SYSSPACE, CAST_USER_ADDR_T(xtoname), ctx); tond->ni_dvp = tdvp; error = namei(tond); if (error) { goto ad_error; } if (tond->ni_vp) { dst_attr_vp = tond->ni_vp; } if (src_attr_vp) { const char *old_name = src_attr_vp->v_name; vnode_t old_parent = src_attr_vp->v_parent; if (batched) { error = VNOP_COMPOUND_RENAME(fdvp, &src_attr_vp, &fromnd->ni_cnd, NULL, tdvp, &dst_attr_vp, &tond->ni_cnd, NULL, 0, ctx); } else { error = VNOP_RENAME(fdvp, src_attr_vp, &fromnd->ni_cnd, tdvp, dst_attr_vp, &tond->ni_cnd, ctx); } if (error == 0 && old_name == src_attr_vp->v_name && old_parent == src_attr_vp->v_parent) { int update_flags = VNODE_UPDATE_NAME; if (fdvp != tdvp) { update_flags |= VNODE_UPDATE_PARENT; } if ((src_attr_vp->v_mount->mnt_vtable->vfc_vfsflags & VFC_VFSVNOP_NOUPDATEID_RENAME) == 0) { vnode_update_identity(src_attr_vp, tdvp, tond->ni_cnd.cn_nameptr, tond->ni_cnd.cn_namelen, tond->ni_cnd.cn_hash, update_flags); } } /* kevent notifications for moving resource files * _err is zero if we're here, so no need to notify directories, code * below will do that. only need to post the rename on the source and * possibly a delete on the dest */ post_event_if_success(src_attr_vp, error, NOTE_RENAME); if (dst_attr_vp) { post_event_if_success(dst_attr_vp, error, NOTE_DELETE); } } else if (dst_attr_vp) { /* * Just delete destination attribute file vnode if it exists, since * we didn't have a source attribute file. * Note that tdvp already has an iocount reference. */ struct vnop_remove_args args; args.a_desc = &vnop_remove_desc; args.a_dvp = tdvp; args.a_vp = dst_attr_vp; args.a_cnp = &tond->ni_cnd; args.a_context = ctx; if (error == 0) { error = (*tdvp->v_op[vnop_remove_desc.vdesc_offset])(&args); if (error == 0) { vnode_setneedinactive(dst_attr_vp); } } /* kevent notification for deleting the destination's attribute file * if it existed. Only need to post the delete on the destination, since * the code below will handle the directories. */ post_event_if_success(dst_attr_vp, error, NOTE_DELETE); } } ad_error: if (src_attr_vp) { vnode_put(src_attr_vp); nameidone(fromnd); } if (dst_attr_vp) { vnode_put(dst_attr_vp); nameidone(tond); } if (xfromname && xfromname != &smallname1[0]) { kfree_data(xfromname, xfromname_len); } if (xtoname && xtoname != &smallname2[0]) { kfree_data(xtoname, xtoname_len); } #endif /* CONFIG_APPLEDOUBLE */ kfree_type(struct nameidata, fromnd); kfree_type(struct nameidata, tond); return _err; } #if 0 /* *# *#% rename fdvp U U U *#% rename fvp U U U *#% rename tdvp L U U *#% rename tvp X U U *# */ struct vnop_rename_args { struct vnodeop_desc *a_desc; vnode_t a_fdvp; vnode_t a_fvp; struct componentname *a_fcnp; vnode_t a_tdvp; vnode_t a_tvp; struct componentname *a_tcnp; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_RENAME(struct vnode *fdvp, struct vnode *fvp, struct componentname *fcnp, struct vnode *tdvp, struct vnode *tvp, struct componentname *tcnp, vfs_context_t ctx) { int _err = 0; struct vnop_rename_args a; a.a_desc = &vnop_rename_desc; a.a_fdvp = fdvp; a.a_fvp = fvp; a.a_fcnp = fcnp; a.a_tdvp = tdvp; a.a_tvp = tvp; a.a_tcnp = tcnp; a.a_context = ctx; /* do the rename of the main file. */ _err = (*fdvp->v_op[vnop_rename_desc.vdesc_offset])(&a); DTRACE_FSINFO(rename, vnode_t, fdvp); if (_err) { return _err; } return post_rename(fdvp, fvp, tdvp, tvp); } static errno_t post_rename(vnode_t fdvp, vnode_t fvp, vnode_t tdvp, vnode_t tvp) { if (tvp && tvp != fvp) { vnode_setneedinactive(tvp); } /* Wrote at least one directory. If transplanted a dir, also changed link counts */ int events = NOTE_WRITE; if (vnode_isdir(fvp)) { /* Link count on dir changed only if we are moving a dir and... * --Moved to new dir, not overwriting there * --Kept in same dir and DID overwrite */ if (((fdvp != tdvp) && (!tvp)) || ((fdvp == tdvp) && (tvp))) { events |= NOTE_LINK; } } lock_vnode_and_post(fdvp, events); if (fdvp != tdvp) { lock_vnode_and_post(tdvp, events); } /* If you're replacing the target, post a deletion for it */ if (tvp) { lock_vnode_and_post(tvp, NOTE_DELETE); } lock_vnode_and_post(fvp, NOTE_RENAME); return 0; } #if 0 /* *# *#% renamex fdvp U U U *#% renamex fvp U U U *#% renamex tdvp L U U *#% renamex tvp X U U *# */ struct vnop_renamex_args { struct vnodeop_desc *a_desc; vnode_t a_fdvp; vnode_t a_fvp; struct componentname *a_fcnp; vnode_t a_tdvp; vnode_t a_tvp; struct componentname *a_tcnp; vfs_rename_flags_t a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_RENAMEX(struct vnode *fdvp, struct vnode *fvp, struct componentname *fcnp, struct vnode *tdvp, struct vnode *tvp, struct componentname *tcnp, vfs_rename_flags_t flags, vfs_context_t ctx) { int _err = 0; struct vnop_renamex_args a; a.a_desc = &vnop_renamex_desc; a.a_fdvp = fdvp; a.a_fvp = fvp; a.a_fcnp = fcnp; a.a_tdvp = tdvp; a.a_tvp = tvp; a.a_tcnp = tcnp; a.a_flags = flags; a.a_context = ctx; /* do the rename of the main file. */ _err = (*fdvp->v_op[vnop_renamex_desc.vdesc_offset])(&a); DTRACE_FSINFO(renamex, vnode_t, fdvp); if (_err) { return _err; } return post_rename(fdvp, fvp, tdvp, tvp); } int VNOP_COMPOUND_RENAME( struct vnode *fdvp, struct vnode **fvpp, struct componentname *fcnp, struct vnode_attr *fvap, struct vnode *tdvp, struct vnode **tvpp, struct componentname *tcnp, struct vnode_attr *tvap, uint32_t flags, vfs_context_t ctx) { int _err = 0; int events; struct vnop_compound_rename_args a; int no_fvp, no_tvp; no_fvp = (*fvpp) == NULLVP; no_tvp = (*tvpp) == NULLVP; a.a_desc = &vnop_compound_rename_desc; a.a_fdvp = fdvp; a.a_fvpp = fvpp; a.a_fcnp = fcnp; a.a_fvap = fvap; a.a_tdvp = tdvp; a.a_tvpp = tvpp; a.a_tcnp = tcnp; a.a_tvap = tvap; a.a_flags = flags; a.a_context = ctx; a.a_rename_authorizer = vn_authorize_rename; a.a_reserved = NULL; /* do the rename of the main file. */ _err = (*fdvp->v_op[vnop_compound_rename_desc.vdesc_offset])(&a); DTRACE_FSINFO(compound_rename, vnode_t, fdvp); if (_err == 0) { if (*tvpp && *tvpp != *fvpp) { vnode_setneedinactive(*tvpp); } } /* Wrote at least one directory. If transplanted a dir, also changed link counts */ if (_err == 0 && *fvpp != *tvpp) { if (!*fvpp) { panic("No fvpp after compound rename?"); } events = NOTE_WRITE; if (vnode_isdir(*fvpp)) { /* Link count on dir changed only if we are moving a dir and... * --Moved to new dir, not overwriting there * --Kept in same dir and DID overwrite */ if (((fdvp != tdvp) && (!*tvpp)) || ((fdvp == tdvp) && (*tvpp))) { events |= NOTE_LINK; } } lock_vnode_and_post(fdvp, events); if (fdvp != tdvp) { lock_vnode_and_post(tdvp, events); } /* If you're replacing the target, post a deletion for it */ if (*tvpp) { lock_vnode_and_post(*tvpp, NOTE_DELETE); } lock_vnode_and_post(*fvpp, NOTE_RENAME); } if (no_fvp) { lookup_compound_vnop_post_hook(_err, fdvp, *fvpp, fcnp->cn_ndp, 0); } if (no_tvp && *tvpp != NULLVP) { lookup_compound_vnop_post_hook(_err, tdvp, *tvpp, tcnp->cn_ndp, 0); } if (_err && _err != EKEEPLOOKING) { if (*fvpp) { vnode_put(*fvpp); *fvpp = NULLVP; } if (*tvpp) { vnode_put(*tvpp); *tvpp = NULLVP; } } return _err; } int vn_mkdir(struct vnode *dvp, struct vnode **vpp, struct nameidata *ndp, struct vnode_attr *vap, vfs_context_t ctx) { if (ndp->ni_cnd.cn_nameiop != CREATE) { panic("Non-CREATE nameiop in vn_mkdir()?"); } if (vnode_compound_mkdir_available(dvp)) { return VNOP_COMPOUND_MKDIR(dvp, vpp, ndp, vap, ctx); } else { return VNOP_MKDIR(dvp, vpp, &ndp->ni_cnd, vap, ctx); } } #if 0 /* *# *#% mkdir dvp L U U *#% mkdir vpp - L - *# */ struct vnop_mkdir_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; vnode_t *a_vpp; struct componentname *a_cnp; struct vnode_attr *a_vap; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_MKDIR(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, struct vnode_attr *vap, vfs_context_t ctx) { int _err; struct vnop_mkdir_args a; a.a_desc = &vnop_mkdir_desc; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = cnp; a.a_vap = vap; a.a_context = ctx; _err = (*dvp->v_op[vnop_mkdir_desc.vdesc_offset])(&a); if (_err == 0 && *vpp) { DTRACE_FSINFO(mkdir, vnode_t, *vpp); } #if CONFIG_APPLEDOUBLE if (_err == 0 && !NATIVE_XATTR(dvp)) { /* * Remove stale Apple Double file (if any). */ xattrfile_remove(dvp, cnp->cn_nameptr, ctx, 0); } #endif /* CONFIG_APPLEDOUBLE */ post_event_if_success(dvp, _err, NOTE_LINK | NOTE_WRITE); return _err; } int VNOP_COMPOUND_MKDIR(struct vnode *dvp, struct vnode **vpp, struct nameidata *ndp, struct vnode_attr *vap, vfs_context_t ctx) { int _err; struct vnop_compound_mkdir_args a; a.a_desc = &vnop_compound_mkdir_desc; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = &ndp->ni_cnd; a.a_vap = vap; a.a_flags = 0; a.a_context = ctx; #if 0 a.a_mkdir_authorizer = vn_authorize_mkdir; #endif /* 0 */ a.a_reserved = NULL; _err = (*dvp->v_op[vnop_compound_mkdir_desc.vdesc_offset])(&a); if (_err == 0 && *vpp) { DTRACE_FSINFO(compound_mkdir, vnode_t, *vpp); } #if CONFIG_APPLEDOUBLE if (_err == 0 && !NATIVE_XATTR(dvp)) { /* * Remove stale Apple Double file (if any). */ xattrfile_remove(dvp, ndp->ni_cnd.cn_nameptr, ctx, 0); } #endif /* CONFIG_APPLEDOUBLE */ post_event_if_success(dvp, _err, NOTE_LINK | NOTE_WRITE); lookup_compound_vnop_post_hook(_err, dvp, *vpp, ndp, (_err == 0)); if (*vpp && _err && _err != EKEEPLOOKING) { vnode_put(*vpp); *vpp = NULLVP; } return _err; } int vn_rmdir(vnode_t dvp, vnode_t *vpp, struct nameidata *ndp, struct vnode_attr *vap, vfs_context_t ctx) { if (vnode_compound_rmdir_available(dvp)) { return VNOP_COMPOUND_RMDIR(dvp, vpp, ndp, vap, ctx); } else { if (*vpp == NULLVP) { panic("NULL vp, but not a compound VNOP?"); } if (vap != NULL) { panic("Non-NULL vap, but not a compound VNOP?"); } return VNOP_RMDIR(dvp, *vpp, &ndp->ni_cnd, ctx); } } #if 0 /* *# *#% rmdir dvp L U U *#% rmdir vp L U U *# */ struct vnop_rmdir_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; vnode_t a_vp; struct componentname *a_cnp; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_RMDIR(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, vfs_context_t ctx) { int _err; struct vnop_rmdir_args a; a.a_desc = &vnop_rmdir_desc; a.a_dvp = dvp; a.a_vp = vp; a.a_cnp = cnp; a.a_context = ctx; _err = (*vp->v_op[vnop_rmdir_desc.vdesc_offset])(&a); DTRACE_FSINFO(rmdir, vnode_t, vp); if (_err == 0) { vnode_setneedinactive(vp); #if CONFIG_APPLEDOUBLE if (!(NATIVE_XATTR(dvp))) { /* * Remove any associated extended attribute file (._ AppleDouble file). */ xattrfile_remove(dvp, cnp->cn_nameptr, ctx, 1); } #endif } /* If you delete a dir, it loses its "." reference --> NOTE_LINK */ post_event_if_success(vp, _err, NOTE_DELETE | NOTE_LINK); post_event_if_success(dvp, _err, NOTE_LINK | NOTE_WRITE); return _err; } int VNOP_COMPOUND_RMDIR(struct vnode *dvp, struct vnode **vpp, struct nameidata *ndp, struct vnode_attr *vap, vfs_context_t ctx) { int _err; struct vnop_compound_rmdir_args a; int no_vp; a.a_desc = &vnop_mkdir_desc; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = &ndp->ni_cnd; a.a_vap = vap; a.a_flags = 0; a.a_context = ctx; a.a_rmdir_authorizer = vn_authorize_rmdir; a.a_reserved = NULL; no_vp = (*vpp == NULLVP); _err = (*dvp->v_op[vnop_compound_rmdir_desc.vdesc_offset])(&a); if (_err == 0 && *vpp) { DTRACE_FSINFO(compound_rmdir, vnode_t, *vpp); } #if CONFIG_APPLEDOUBLE if (_err == 0 && !NATIVE_XATTR(dvp)) { /* * Remove stale Apple Double file (if any). */ xattrfile_remove(dvp, ndp->ni_cnd.cn_nameptr, ctx, 0); } #endif if (*vpp) { post_event_if_success(*vpp, _err, NOTE_DELETE | NOTE_LINK); } post_event_if_success(dvp, _err, NOTE_LINK | NOTE_WRITE); if (no_vp) { lookup_compound_vnop_post_hook(_err, dvp, *vpp, ndp, 0); #if 0 /* Removing orphaned ._ files requires a vp.... */ if (*vpp && _err && _err != EKEEPLOOKING) { vnode_put(*vpp); *vpp = NULLVP; } #endif /* 0 */ } return _err; } #if CONFIG_APPLEDOUBLE /* * Remove a ._ AppleDouble file */ #define AD_STALE_SECS (180) static void xattrfile_remove(vnode_t dvp, const char * basename, vfs_context_t ctx, int force) { vnode_t xvp; struct nameidata nd; char smallname[64]; char *filename = NULL; size_t alloc_len; size_t copy_len; if ((basename == NULL) || (basename[0] == '\0') || (basename[0] == '.' && basename[1] == '_')) { return; } filename = &smallname[0]; alloc_len = snprintf(filename, sizeof(smallname), "._%s", basename); if (alloc_len >= sizeof(smallname)) { alloc_len++; /* snprintf result doesn't include '\0' */ filename = kalloc_data(alloc_len, Z_WAITOK); copy_len = snprintf(filename, alloc_len, "._%s", basename); } NDINIT(&nd, DELETE, OP_UNLINK, WANTPARENT | LOCKLEAF | NOFOLLOW | USEDVP, UIO_SYSSPACE, CAST_USER_ADDR_T(filename), ctx); nd.ni_dvp = dvp; if (namei(&nd) != 0) { goto out2; } xvp = nd.ni_vp; nameidone(&nd); if (xvp->v_type != VREG) { goto out1; } /* * When creating a new object and a "._" file already * exists, check to see if it's a stale "._" file. These are * typically AppleDouble (AD) files generated via XNU's * VFS compatibility shims for storing XATTRs and streams * on filesystems that do not support them natively. */ if (!force) { struct vnode_attr va; VATTR_INIT(&va); VATTR_WANTED(&va, va_data_size); VATTR_WANTED(&va, va_modify_time); VATTR_WANTED(&va, va_change_time); if (VNOP_GETATTR(xvp, &va, ctx) == 0 && VATTR_IS_SUPPORTED(&va, va_data_size) && va.va_data_size != 0) { struct timeval tv_compare = {}; struct timeval tv_now = {}; /* * If the file exists (and has non-zero size), then use the newer of * chgtime / modtime to compare against present time. Note that setting XATTRs or updating * streams through the compatibility interfaces may not trigger chgtime to be updated, so * checking either modtime or chgtime is useful. */ if (VATTR_IS_SUPPORTED(&va, va_modify_time) && (va.va_modify_time.tv_sec)) { if (VATTR_IS_SUPPORTED(&va, va_change_time) && (va.va_change_time.tv_sec)) { tv_compare.tv_sec = va.va_change_time.tv_sec; if (tv_compare.tv_sec < va.va_modify_time.tv_sec) { tv_compare.tv_sec = va.va_modify_time.tv_sec; } } else { /* fall back to mod-time alone if chgtime not supported or set to 0 */ tv_compare.tv_sec = va.va_modify_time.tv_sec; } } /* Now, we have a time to compare against, compare against AD_STALE_SEC */ microtime(&tv_now); if ((tv_compare.tv_sec > 0) && (tv_now.tv_sec > tv_compare.tv_sec) && ((tv_now.tv_sec - tv_compare.tv_sec) > AD_STALE_SECS)) { force = 1; /* must be stale */ } } } if (force) { int error; error = VNOP_REMOVE(dvp, xvp, &nd.ni_cnd, 0, ctx); if (error == 0) { vnode_setneedinactive(xvp); } post_event_if_success(xvp, error, NOTE_DELETE); post_event_if_success(dvp, error, NOTE_WRITE); } out1: vnode_put(dvp); vnode_put(xvp); out2: if (filename && filename != &smallname[0]) { kfree_data(filename, alloc_len); } } /* * Shadow uid/gid/mod to a ._ AppleDouble file */ static void xattrfile_setattr(vnode_t dvp, const char * basename, struct vnode_attr * vap, vfs_context_t ctx) { vnode_t xvp; struct nameidata nd; char smallname[64]; char *filename = NULL; size_t alloc_len; size_t copy_len; if ((dvp == NULLVP) || (basename == NULL) || (basename[0] == '\0') || (basename[0] == '.' && basename[1] == '_')) { return; } filename = &smallname[0]; alloc_len = snprintf(filename, sizeof(smallname), "._%s", basename); if (alloc_len >= sizeof(smallname)) { alloc_len++; /* snprintf result doesn't include '\0' */ filename = kalloc_data(alloc_len, Z_WAITOK); copy_len = snprintf(filename, alloc_len, "._%s", basename); } NDINIT(&nd, LOOKUP, OP_SETATTR, NOFOLLOW | USEDVP, UIO_SYSSPACE, CAST_USER_ADDR_T(filename), ctx); nd.ni_dvp = dvp; if (namei(&nd) != 0) { goto out2; } xvp = nd.ni_vp; nameidone(&nd); if (xvp->v_type == VREG) { struct vnop_setattr_args a; a.a_desc = &vnop_setattr_desc; a.a_vp = xvp; a.a_vap = vap; a.a_context = ctx; (void) (*xvp->v_op[vnop_setattr_desc.vdesc_offset])(&a); } vnode_put(xvp); out2: if (filename && filename != &smallname[0]) { kfree_data(filename, alloc_len); } } #endif /* CONFIG_APPLEDOUBLE */ #if 0 /* *# *#% symlink dvp L U U *#% symlink vpp - U - *# */ struct vnop_symlink_args { struct vnodeop_desc *a_desc; vnode_t a_dvp; vnode_t *a_vpp; struct componentname *a_cnp; struct vnode_attr *a_vap; char *a_target; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_SYMLINK(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, struct vnode_attr *vap, char *target, vfs_context_t ctx) { int _err; struct vnop_symlink_args a; a.a_desc = &vnop_symlink_desc; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = cnp; a.a_vap = vap; a.a_target = target; a.a_context = ctx; _err = (*dvp->v_op[vnop_symlink_desc.vdesc_offset])(&a); DTRACE_FSINFO(symlink, vnode_t, dvp); #if CONFIG_APPLEDOUBLE if (_err == 0 && !NATIVE_XATTR(dvp)) { /* * Remove stale Apple Double file (if any). Posts its own knotes */ xattrfile_remove(dvp, cnp->cn_nameptr, ctx, 0); } #endif /* CONFIG_APPLEDOUBLE */ post_event_if_success(dvp, _err, NOTE_WRITE); return _err; } #if 0 /* *# *#% readdir vp L L L *# */ struct vnop_readdir_args { struct vnodeop_desc *a_desc; vnode_t a_vp; struct uio *a_uio; int a_flags; int *a_eofflag; int *a_numdirent; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_READDIR(struct vnode *vp, struct uio *uio, int flags, int *eofflag, int *numdirent, vfs_context_t ctx) { int _err; struct vnop_readdir_args a; #if CONFIG_DTRACE user_ssize_t resid = uio_resid(uio); #endif a.a_desc = &vnop_readdir_desc; a.a_vp = vp; a.a_uio = uio; a.a_flags = flags; a.a_eofflag = eofflag; a.a_numdirent = numdirent; a.a_context = ctx; _err = (*vp->v_op[vnop_readdir_desc.vdesc_offset])(&a); DTRACE_FSINFO_IO(readdir, vnode_t, vp, user_ssize_t, (resid - uio_resid(uio))); return _err; } #if 0 /* *# *#% readdirattr vp L L L *# */ struct vnop_readdirattr_args { struct vnodeop_desc *a_desc; vnode_t a_vp; struct attrlist *a_alist; struct uio *a_uio; uint32_t a_maxcount; uint32_t a_options; uint32_t *a_newstate; int *a_eofflag; uint32_t *a_actualcount; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_READDIRATTR(struct vnode *vp, struct attrlist *alist, struct uio *uio, uint32_t maxcount, uint32_t options, uint32_t *newstate, int *eofflag, uint32_t *actualcount, vfs_context_t ctx) { int _err; struct vnop_readdirattr_args a; #if CONFIG_DTRACE user_ssize_t resid = uio_resid(uio); #endif a.a_desc = &vnop_readdirattr_desc; a.a_vp = vp; a.a_alist = alist; a.a_uio = uio; a.a_maxcount = maxcount; a.a_options = options; a.a_newstate = newstate; a.a_eofflag = eofflag; a.a_actualcount = actualcount; a.a_context = ctx; _err = (*vp->v_op[vnop_readdirattr_desc.vdesc_offset])(&a); DTRACE_FSINFO_IO(readdirattr, vnode_t, vp, user_ssize_t, (resid - uio_resid(uio))); return _err; } #if 0 struct vnop_getttrlistbulk_args { struct vnodeop_desc *a_desc; vnode_t a_vp; struct attrlist *a_alist; struct vnode_attr *a_vap; struct uio *a_uio; void *a_private uint64_t a_options; int *a_eofflag; uint32_t *a_actualcount; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_GETATTRLISTBULK(struct vnode *vp, struct attrlist *alist, struct vnode_attr *vap, struct uio *uio, void *private, uint64_t options, int32_t *eofflag, int32_t *actualcount, vfs_context_t ctx) { int _err; struct vnop_getattrlistbulk_args a; #if CONFIG_DTRACE user_ssize_t resid = uio_resid(uio); #endif a.a_desc = &vnop_getattrlistbulk_desc; a.a_vp = vp; a.a_alist = alist; a.a_vap = vap; a.a_uio = uio; a.a_private = private; a.a_options = options; a.a_eofflag = eofflag; a.a_actualcount = actualcount; a.a_context = ctx; _err = (*vp->v_op[vnop_getattrlistbulk_desc.vdesc_offset])(&a); DTRACE_FSINFO_IO(getattrlistbulk, vnode_t, vp, user_ssize_t, (resid - uio_resid(uio))); return _err; } #if 0 /* *# *#% readlink vp L L L *# */ struct vnop_readlink_args { struct vnodeop_desc *a_desc; vnode_t a_vp; struct uio *a_uio; vfs_context_t a_context; }; #endif /* 0 */ /* * Returns: 0 Success * lock_fsnode:ENOENT No such file or directory [only for VFS * that is not thread safe & vnode is * currently being/has been terminated] * :EINVAL * :??? * * Note: The return codes from the underlying VFS's readlink routine * can't be fully enumerated here, since third party VFS authors * may not limit their error returns to the ones documented here, * even though this may result in some programs functioning * incorrectly. * * The return codes documented above are those which may currently * be returned by HFS from hfs_vnop_readlink, not including * additional error code which may be propagated from underlying * routines. */ errno_t VNOP_READLINK(struct vnode *vp, struct uio *uio, vfs_context_t ctx) { int _err; struct vnop_readlink_args a; #if CONFIG_DTRACE user_ssize_t resid = uio_resid(uio); #endif a.a_desc = &vnop_readlink_desc; a.a_vp = vp; a.a_uio = uio; a.a_context = ctx; _err = (*vp->v_op[vnop_readlink_desc.vdesc_offset])(&a); DTRACE_FSINFO_IO(readlink, vnode_t, vp, user_ssize_t, (resid - uio_resid(uio))); return _err; } #if 0 /* *# *#% inactive vp L U U *# */ struct vnop_inactive_args { struct vnodeop_desc *a_desc; vnode_t a_vp; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_INACTIVE(struct vnode *vp, vfs_context_t ctx) { int _err; struct vnop_inactive_args a; a.a_desc = &vnop_inactive_desc; a.a_vp = vp; a.a_context = ctx; _err = (*vp->v_op[vnop_inactive_desc.vdesc_offset])(&a); DTRACE_FSINFO(inactive, vnode_t, vp); #if NAMEDSTREAMS /* For file systems that do not support namedstream natively, mark * the shadow stream file vnode to be recycled as soon as the last * reference goes away. To avoid re-entering reclaim code, do not * call recycle on terminating namedstream vnodes. */ if (vnode_isnamedstream(vp) && (vp->v_parent != NULLVP) && vnode_isshadow(vp) && ((vp->v_lflag & VL_TERMINATE) == 0)) { vnode_recycle(vp); } #endif return _err; } #if 0 /* *# *#% reclaim vp U U U *# */ struct vnop_reclaim_args { struct vnodeop_desc *a_desc; vnode_t a_vp; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_RECLAIM(struct vnode *vp, vfs_context_t ctx) { int _err; struct vnop_reclaim_args a; a.a_desc = &vnop_reclaim_desc; a.a_vp = vp; a.a_context = ctx; _err = (*vp->v_op[vnop_reclaim_desc.vdesc_offset])(&a); DTRACE_FSINFO(reclaim, vnode_t, vp); return _err; } /* * Returns: 0 Success * lock_fsnode:ENOENT No such file or directory [only for VFS * that is not thread safe & vnode is * currently being/has been terminated] * :??? [per FS implementation specific] */ #if 0 /* *# *#% pathconf vp L L L *# */ struct vnop_pathconf_args { struct vnodeop_desc *a_desc; vnode_t a_vp; int a_name; int32_t *a_retval; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_PATHCONF(struct vnode *vp, int name, int32_t *retval, vfs_context_t ctx) { int _err; struct vnop_pathconf_args a; a.a_desc = &vnop_pathconf_desc; a.a_vp = vp; a.a_name = name; a.a_retval = retval; a.a_context = ctx; _err = (*vp->v_op[vnop_pathconf_desc.vdesc_offset])(&a); DTRACE_FSINFO(pathconf, vnode_t, vp); return _err; } /* * Returns: 0 Success * err_advlock:ENOTSUP * lf_advlock:??? * :??? * * Notes: VFS implementations of advisory locking using calls through * because lock enforcement does not occur * locally should try to limit themselves to the return codes * documented above for lf_advlock and err_advlock. */ #if 0 /* *# *#% advlock vp U U U *# */ struct vnop_advlock_args { struct vnodeop_desc *a_desc; vnode_t a_vp; caddr_t a_id; int a_op; struct flock *a_fl; int a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_ADVLOCK(struct vnode *vp, caddr_t id, int op, struct flock *fl, int flags, vfs_context_t ctx, struct timespec *timeout) { int _err; struct vnop_advlock_args a; a.a_desc = &vnop_advlock_desc; a.a_vp = vp; a.a_id = id; a.a_op = op; a.a_fl = fl; a.a_flags = flags; a.a_context = ctx; a.a_timeout = timeout; /* Disallow advisory locking on non-seekable vnodes */ if (vnode_isfifo(vp)) { _err = err_advlock(&a); } else { if ((vp->v_flag & VLOCKLOCAL)) { /* Advisory locking done at this layer */ _err = lf_advlock(&a); } else if (flags & F_OFD_LOCK) { /* Non-local locking doesn't work for OFD locks */ _err = err_advlock(&a); } else if (op == F_TRANSFER) { /* Non-local locking doesn't have F_TRANSFER */ _err = err_advlock(&a); } else { /* Advisory locking done by underlying filesystem */ _err = (*vp->v_op[vnop_advlock_desc.vdesc_offset])(&a); } DTRACE_FSINFO(advlock, vnode_t, vp); if (op == F_UNLCK && (flags & (F_FLOCK | F_OFD_LOCK)) != 0) { post_event_if_success(vp, _err, NOTE_FUNLOCK); } } return _err; } #if 0 /* *# *#% allocate vp L L L *# */ struct vnop_allocate_args { struct vnodeop_desc *a_desc; vnode_t a_vp; off_t a_length; u_int32_t a_flags; off_t *a_bytesallocated; off_t a_offset; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_ALLOCATE(struct vnode *vp, off_t length, u_int32_t flags, off_t *bytesallocated, off_t offset, vfs_context_t ctx) { int _err; struct vnop_allocate_args a; a.a_desc = &vnop_allocate_desc; a.a_vp = vp; a.a_length = length; a.a_flags = flags; a.a_bytesallocated = bytesallocated; a.a_offset = offset; a.a_context = ctx; _err = (*vp->v_op[vnop_allocate_desc.vdesc_offset])(&a); DTRACE_FSINFO(allocate, vnode_t, vp); #if CONFIG_FSE if (_err == 0) { add_fsevent(FSE_STAT_CHANGED, ctx, FSE_ARG_VNODE, vp, FSE_ARG_DONE); } #endif return _err; } #if 0 /* *# *#% pagein vp = = = *# */ struct vnop_pagein_args { struct vnodeop_desc *a_desc; vnode_t a_vp; upl_t a_pl; upl_offset_t a_pl_offset; off_t a_f_offset; size_t a_size; int a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_PAGEIN(struct vnode *vp, upl_t pl, upl_offset_t pl_offset, off_t f_offset, size_t size, int flags, vfs_context_t ctx) { int _err; struct vnop_pagein_args a; a.a_desc = &vnop_pagein_desc; a.a_vp = vp; a.a_pl = pl; a.a_pl_offset = pl_offset; a.a_f_offset = f_offset; a.a_size = size; a.a_flags = flags; a.a_context = ctx; _err = (*vp->v_op[vnop_pagein_desc.vdesc_offset])(&a); DTRACE_FSINFO(pagein, vnode_t, vp); return _err; } #if 0 /* *# *#% pageout vp = = = *# */ struct vnop_pageout_args { struct vnodeop_desc *a_desc; vnode_t a_vp; upl_t a_pl; upl_offset_t a_pl_offset; off_t a_f_offset; size_t a_size; int a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_PAGEOUT(struct vnode *vp, upl_t pl, upl_offset_t pl_offset, off_t f_offset, size_t size, int flags, vfs_context_t ctx) { int _err; struct vnop_pageout_args a; a.a_desc = &vnop_pageout_desc; a.a_vp = vp; a.a_pl = pl; a.a_pl_offset = pl_offset; a.a_f_offset = f_offset; a.a_size = size; a.a_flags = flags; a.a_context = ctx; _err = (*vp->v_op[vnop_pageout_desc.vdesc_offset])(&a); DTRACE_FSINFO(pageout, vnode_t, vp); post_event_if_success(vp, _err, NOTE_WRITE); return _err; } int vn_remove(vnode_t dvp, vnode_t *vpp, struct nameidata *ndp, int32_t flags, struct vnode_attr *vap, vfs_context_t ctx) { if (vnode_compound_remove_available(dvp)) { return VNOP_COMPOUND_REMOVE(dvp, vpp, ndp, flags, vap, ctx); } else { return VNOP_REMOVE(dvp, *vpp, &ndp->ni_cnd, flags, ctx); } } #if CONFIG_SEARCHFS #if 0 /* *# *#% searchfs vp L L L *# */ struct vnop_searchfs_args { struct vnodeop_desc *a_desc; vnode_t a_vp; void *a_searchparams1; void *a_searchparams2; struct attrlist *a_searchattrs; uint32_t a_maxmatches; struct timeval *a_timelimit; struct attrlist *a_returnattrs; uint32_t *a_nummatches; uint32_t a_scriptcode; uint32_t a_options; struct uio *a_uio; struct searchstate *a_searchstate; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_SEARCHFS(struct vnode *vp, void *searchparams1, void *searchparams2, struct attrlist *searchattrs, uint32_t maxmatches, struct timeval *timelimit, struct attrlist *returnattrs, uint32_t *nummatches, uint32_t scriptcode, uint32_t options, struct uio *uio, struct searchstate *searchstate, vfs_context_t ctx) { int _err; struct vnop_searchfs_args a; a.a_desc = &vnop_searchfs_desc; a.a_vp = vp; a.a_searchparams1 = searchparams1; a.a_searchparams2 = searchparams2; a.a_searchattrs = searchattrs; a.a_maxmatches = maxmatches; a.a_timelimit = timelimit; a.a_returnattrs = returnattrs; a.a_nummatches = nummatches; a.a_scriptcode = scriptcode; a.a_options = options; a.a_uio = uio; a.a_searchstate = searchstate; a.a_context = ctx; _err = (*vp->v_op[vnop_searchfs_desc.vdesc_offset])(&a); DTRACE_FSINFO(searchfs, vnode_t, vp); return _err; } #endif /* CONFIG_SEARCHFS */ #if 0 /* *# *#% copyfile fvp U U U *#% copyfile tdvp L U U *#% copyfile tvp X U U *# */ struct vnop_copyfile_args { struct vnodeop_desc *a_desc; vnode_t a_fvp; vnode_t a_tdvp; vnode_t a_tvp; struct componentname *a_tcnp; int a_mode; int a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_COPYFILE(struct vnode *fvp, struct vnode *tdvp, struct vnode *tvp, struct componentname *tcnp, int mode, int flags, vfs_context_t ctx) { int _err; struct vnop_copyfile_args a; a.a_desc = &vnop_copyfile_desc; a.a_fvp = fvp; a.a_tdvp = tdvp; a.a_tvp = tvp; a.a_tcnp = tcnp; a.a_mode = mode; a.a_flags = flags; a.a_context = ctx; _err = (*fvp->v_op[vnop_copyfile_desc.vdesc_offset])(&a); DTRACE_FSINFO(copyfile, vnode_t, fvp); return _err; } #if 0 struct vnop_clonefile_args { struct vnodeop_desc *a_desc; vnode_t a_fvp; vnode_t a_dvp; vnode_t *a_vpp; struct componentname *a_cnp; struct vnode_attr *a_vap; uint32_t a_flags; vfs_context_t a_context; int (*a_dir_clone_authorizer)( /* Authorization callback */ struct vnode_attr *vap, /* attribute to be authorized */ kauth_action_t action, /* action for which attribute is to be authorized */ struct vnode_attr *dvap, /* target directory attributes */ vnode_t sdvp, /* source directory vnode pointer (optional) */ mount_t mp, /* mount point of filesystem */ dir_clone_authorizer_op_t vattr_op, /* specific operation requested : setup, authorization or cleanup */ uint32_t flags; /* value passed in a_flags to the VNOP */ vfs_context_t ctx, /* As passed to VNOP */ void *reserved); /* Always NULL */ void *a_reserved; /* Currently unused */ }; #endif /* 0 */ errno_t VNOP_CLONEFILE(vnode_t fvp, vnode_t dvp, vnode_t *vpp, struct componentname *cnp, struct vnode_attr *vap, uint32_t flags, vfs_context_t ctx) { int _err; struct vnop_clonefile_args a; a.a_desc = &vnop_clonefile_desc; a.a_fvp = fvp; a.a_dvp = dvp; a.a_vpp = vpp; a.a_cnp = cnp; a.a_vap = vap; a.a_flags = flags; a.a_context = ctx; if (vnode_vtype(fvp) == VDIR) { a.a_dir_clone_authorizer = vnode_attr_authorize_dir_clone; } else { a.a_dir_clone_authorizer = NULL; } _err = (*dvp->v_op[vnop_clonefile_desc.vdesc_offset])(&a); if (_err == 0 && *vpp) { DTRACE_FSINFO(clonefile, vnode_t, *vpp); if (kdebug_enable) { kdebug_lookup(*vpp, cnp); } } post_event_if_success(dvp, _err, NOTE_WRITE); return _err; } errno_t VNOP_GETXATTR(vnode_t vp, const char *name, uio_t uio, size_t *size, int options, vfs_context_t ctx) { struct vnop_getxattr_args a; int error; a.a_desc = &vnop_getxattr_desc; a.a_vp = vp; a.a_name = name; a.a_uio = uio; a.a_size = size; a.a_options = options; a.a_context = ctx; error = (*vp->v_op[vnop_getxattr_desc.vdesc_offset])(&a); DTRACE_FSINFO(getxattr, vnode_t, vp); return error; } errno_t VNOP_SETXATTR(vnode_t vp, const char *name, uio_t uio, int options, vfs_context_t ctx) { struct vnop_setxattr_args a; int error; a.a_desc = &vnop_setxattr_desc; a.a_vp = vp; a.a_name = name; a.a_uio = uio; a.a_options = options; a.a_context = ctx; error = (*vp->v_op[vnop_setxattr_desc.vdesc_offset])(&a); DTRACE_FSINFO(setxattr, vnode_t, vp); if (error == 0) { vnode_uncache_authorized_action(vp, KAUTH_INVALIDATE_CACHED_RIGHTS); } post_event_if_success(vp, error, NOTE_ATTRIB); return error; } errno_t VNOP_REMOVEXATTR(vnode_t vp, const char *name, int options, vfs_context_t ctx) { struct vnop_removexattr_args a; int error; a.a_desc = &vnop_removexattr_desc; a.a_vp = vp; a.a_name = name; a.a_options = options; a.a_context = ctx; error = (*vp->v_op[vnop_removexattr_desc.vdesc_offset])(&a); DTRACE_FSINFO(removexattr, vnode_t, vp); post_event_if_success(vp, error, NOTE_ATTRIB); return error; } errno_t VNOP_LISTXATTR(vnode_t vp, uio_t uio, size_t *size, int options, vfs_context_t ctx) { struct vnop_listxattr_args a; int error; a.a_desc = &vnop_listxattr_desc; a.a_vp = vp; a.a_uio = uio; a.a_size = size; a.a_options = options; a.a_context = ctx; error = (*vp->v_op[vnop_listxattr_desc.vdesc_offset])(&a); DTRACE_FSINFO(listxattr, vnode_t, vp); return error; } #if 0 /* *# *#% blktooff vp = = = *# */ struct vnop_blktooff_args { struct vnodeop_desc *a_desc; vnode_t a_vp; daddr64_t a_lblkno; off_t *a_offset; }; #endif /* 0*/ errno_t VNOP_BLKTOOFF(struct vnode *vp, daddr64_t lblkno, off_t *offset) { int _err; struct vnop_blktooff_args a; a.a_desc = &vnop_blktooff_desc; a.a_vp = vp; a.a_lblkno = lblkno; a.a_offset = offset; _err = (*vp->v_op[vnop_blktooff_desc.vdesc_offset])(&a); DTRACE_FSINFO(blktooff, vnode_t, vp); return _err; } #if 0 /* *# *#% offtoblk vp = = = *# */ struct vnop_offtoblk_args { struct vnodeop_desc *a_desc; vnode_t a_vp; off_t a_offset; daddr64_t *a_lblkno; }; #endif /* 0*/ errno_t VNOP_OFFTOBLK(struct vnode *vp, off_t offset, daddr64_t *lblkno) { int _err; struct vnop_offtoblk_args a; a.a_desc = &vnop_offtoblk_desc; a.a_vp = vp; a.a_offset = offset; a.a_lblkno = lblkno; _err = (*vp->v_op[vnop_offtoblk_desc.vdesc_offset])(&a); DTRACE_FSINFO(offtoblk, vnode_t, vp); return _err; } #if 0 /* *# *#% ap vp L L L *# */ struct vnop_verify_args { struct vnodeop_desc *a_desc; vnode_t a_vp; off_t a_foffset; char *a_buf; size_t a_bufsize; size_t *a_verifyblksize; void **a_verify_ctxp; int a_flags; vfs_context_t a_context; }; #endif errno_t VNOP_VERIFY(struct vnode *vp, off_t foffset, uint8_t *buf, size_t bufsize, size_t *verify_block_size, void **verify_ctxp, vnode_verify_flags_t flags, vfs_context_t ctx) { int _err; struct vnop_verify_args a; if (ctx == NULL) { ctx = vfs_context_kernel(); } a.a_desc = &vnop_verify_desc; a.a_vp = vp; a.a_foffset = foffset; a.a_buf = buf; a.a_bufsize = bufsize; a.a_verifyblksize = verify_block_size; a.a_flags = flags; a.a_verify_ctxp = verify_ctxp; a.a_context = ctx; _err = (*vp->v_op[vnop_verify_desc.vdesc_offset])(&a); DTRACE_FSINFO(verify, vnode_t, vp); /* It is not an error for a filesystem to not support this VNOP */ if (_err == ENOTSUP) { if (!buf && verify_block_size) { *verify_block_size = 0; } _err = 0; } return _err; } #if 0 /* *# *#% blockmap vp L L L *# */ struct vnop_blockmap_args { struct vnodeop_desc *a_desc; vnode_t a_vp; off_t a_foffset; size_t a_size; daddr64_t *a_bpn; size_t *a_run; void *a_poff; int a_flags; vfs_context_t a_context; }; #endif /* 0*/ errno_t VNOP_BLOCKMAP(struct vnode *vp, off_t foffset, size_t size, daddr64_t *bpn, size_t *run, void *poff, int flags, vfs_context_t ctx) { int _err; struct vnop_blockmap_args a; size_t localrun = 0; if (ctx == NULL) { ctx = vfs_context_current(); } a.a_desc = &vnop_blockmap_desc; a.a_vp = vp; a.a_foffset = foffset; a.a_size = size; a.a_bpn = bpn; a.a_run = &localrun; a.a_poff = poff; a.a_flags = flags; a.a_context = ctx; _err = (*vp->v_op[vnop_blockmap_desc.vdesc_offset])(&a); DTRACE_FSINFO(blockmap, vnode_t, vp); /* * We used a local variable to request information from the underlying * filesystem about the length of the I/O run in question. If * we get malformed output from the filesystem, we cap it to the length * requested, at most. Update 'run' on the way out. */ if (_err == 0) { if (localrun > size) { localrun = size; } if (run) { *run = localrun; } } return _err; } #if 0 struct vnop_strategy_args { struct vnodeop_desc *a_desc; struct buf *a_bp; }; #endif /* 0*/ errno_t VNOP_STRATEGY(struct buf *bp) { int _err; struct vnop_strategy_args a; vnode_t vp = buf_vnode(bp); a.a_desc = &vnop_strategy_desc; a.a_bp = bp; _err = (*vp->v_op[vnop_strategy_desc.vdesc_offset])(&a); DTRACE_FSINFO(strategy, vnode_t, vp); return _err; } #if 0 struct vnop_bwrite_args { struct vnodeop_desc *a_desc; buf_t a_bp; }; #endif /* 0*/ errno_t VNOP_BWRITE(struct buf *bp) { int _err; struct vnop_bwrite_args a; vnode_t vp = buf_vnode(bp); a.a_desc = &vnop_bwrite_desc; a.a_bp = bp; _err = (*vp->v_op[vnop_bwrite_desc.vdesc_offset])(&a); DTRACE_FSINFO(bwrite, vnode_t, vp); return _err; } #if 0 struct vnop_kqfilt_add_args { struct vnodeop_desc *a_desc; struct vnode *a_vp; struct knote *a_kn; vfs_context_t a_context; }; #endif errno_t VNOP_KQFILT_ADD(struct vnode *vp, struct knote *kn, vfs_context_t ctx) { int _err; struct vnop_kqfilt_add_args a; a.a_desc = VDESC(vnop_kqfilt_add); a.a_vp = vp; a.a_kn = kn; a.a_context = ctx; _err = (*vp->v_op[vnop_kqfilt_add_desc.vdesc_offset])(&a); DTRACE_FSINFO(kqfilt_add, vnode_t, vp); return _err; } #if 0 struct vnop_kqfilt_remove_args { struct vnodeop_desc *a_desc; struct vnode *a_vp; uintptr_t a_ident; vfs_context_t a_context; }; #endif errno_t VNOP_KQFILT_REMOVE(struct vnode *vp, uintptr_t ident, vfs_context_t ctx) { int _err; struct vnop_kqfilt_remove_args a; a.a_desc = VDESC(vnop_kqfilt_remove); a.a_vp = vp; a.a_ident = ident; a.a_context = ctx; _err = (*vp->v_op[vnop_kqfilt_remove_desc.vdesc_offset])(&a); DTRACE_FSINFO(kqfilt_remove, vnode_t, vp); return _err; } errno_t VNOP_MONITOR(vnode_t vp, uint32_t events, uint32_t flags, void *handle, vfs_context_t ctx) { int _err; struct vnop_monitor_args a; a.a_desc = VDESC(vnop_monitor); a.a_vp = vp; a.a_events = events; a.a_flags = flags; a.a_handle = handle; a.a_context = ctx; _err = (*vp->v_op[vnop_monitor_desc.vdesc_offset])(&a); DTRACE_FSINFO(monitor, vnode_t, vp); return _err; } #if 0 struct vnop_setlabel_args { struct vnodeop_desc *a_desc; struct vnode *a_vp; struct label *a_vl; vfs_context_t a_context; }; #endif errno_t VNOP_SETLABEL(struct vnode *vp, struct label *label, vfs_context_t ctx) { int _err; struct vnop_setlabel_args a; a.a_desc = VDESC(vnop_setlabel); a.a_vp = vp; a.a_vl = label; a.a_context = ctx; _err = (*vp->v_op[vnop_setlabel_desc.vdesc_offset])(&a); DTRACE_FSINFO(setlabel, vnode_t, vp); return _err; } #if NAMEDSTREAMS /* * Get a named streamed */ errno_t VNOP_GETNAMEDSTREAM(vnode_t vp, vnode_t *svpp, const char *name, enum nsoperation operation, int flags, vfs_context_t ctx) { int _err; struct vnop_getnamedstream_args a; a.a_desc = &vnop_getnamedstream_desc; a.a_vp = vp; a.a_svpp = svpp; a.a_name = name; a.a_operation = operation; a.a_flags = flags; a.a_context = ctx; _err = (*vp->v_op[vnop_getnamedstream_desc.vdesc_offset])(&a); DTRACE_FSINFO(getnamedstream, vnode_t, vp); return _err; } /* * Create a named streamed */ errno_t VNOP_MAKENAMEDSTREAM(vnode_t vp, vnode_t *svpp, const char *name, int flags, vfs_context_t ctx) { int _err; struct vnop_makenamedstream_args a; a.a_desc = &vnop_makenamedstream_desc; a.a_vp = vp; a.a_svpp = svpp; a.a_name = name; a.a_flags = flags; a.a_context = ctx; _err = (*vp->v_op[vnop_makenamedstream_desc.vdesc_offset])(&a); DTRACE_FSINFO(makenamedstream, vnode_t, vp); return _err; } /* * Remove a named streamed */ errno_t VNOP_REMOVENAMEDSTREAM(vnode_t vp, vnode_t svp, const char *name, int flags, vfs_context_t ctx) { int _err; struct vnop_removenamedstream_args a; a.a_desc = &vnop_removenamedstream_desc; a.a_vp = vp; a.a_svp = svp; a.a_name = name; a.a_flags = flags; a.a_context = ctx; _err = (*vp->v_op[vnop_removenamedstream_desc.vdesc_offset])(&a); DTRACE_FSINFO(removenamedstream, vnode_t, vp); return _err; } #endif