/* * Perform chown operation on inode ip; * inode must be locked prior to call. */ static int ptyfs_chown(struct vnode *vp, uid_t uid, gid_t gid, kauth_cred_t cred, struct lwp *l) { struct ptyfsnode *ptyfs = VTOPTYFS(vp); int error, ismember = 0; if (uid == (uid_t)VNOVAL) uid = ptyfs->ptyfs_uid; if (gid == (gid_t)VNOVAL) gid = ptyfs->ptyfs_gid; /* * If we don't own the file, are trying to change the owner * of the file, or are not a member of the target group, * the caller's credentials must imply super-user privilege * or the call fails. */ if ((kauth_cred_geteuid(cred) != ptyfs->ptyfs_uid || uid != ptyfs->ptyfs_uid || (gid != ptyfs->ptyfs_gid && !(kauth_cred_getegid(cred) == gid || (kauth_cred_ismember_gid(cred, gid, &ismember) == 0 && ismember)))) && ((error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL)) != 0)) return error; ptyfs->ptyfs_gid = gid; ptyfs->ptyfs_uid = uid; return 0; }
int ptyfs_read(void *v) { struct vop_read_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; kauth_cred_t a_cred; } */ *ap = v; struct timespec ts; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); int error; if (vp->v_type == VDIR) return EISDIR; ptyfs->ptyfs_status |= PTYFS_ACCESS; /* hardclock() resolution is good enough for ptyfs */ getnanotime(&ts); (void)ptyfs_update(vp, &ts, &ts, 0); switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: VOP_UNLOCK(vp); error = cdev_read(vp->v_rdev, ap->a_uio, ap->a_ioflag); vn_lock(vp, LK_RETRY|LK_EXCLUSIVE); return error; default: return EOPNOTSUPP; } }
int ptyfs_close(void *v) { struct vop_close_args /* { struct vnode *a_vp; int a_fflag; kauth_cred_t a_cred; } */ *ap = v; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); mutex_enter(&vp->v_interlock); if (vp->v_usecount > 1) PTYFS_ITIMES(ptyfs, NULL, NULL, NULL); mutex_exit(&vp->v_interlock); switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: return spec_close(v); case PTYFSroot: return 0; default: return EINVAL; } }
int ptyfs_write(void *v) { struct vop_write_args /* { struct vnode *a_vp; struct uio *a_uio; int a_ioflag; kauth_cred_t a_cred; } */ *ap = v; struct timespec ts; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); int error; ptyfs->ptyfs_flag |= PTYFS_MODIFY; getnanotime(&ts); (void)ptyfs_update(vp, &ts, &ts, 0); switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: VOP_UNLOCK(vp, 0); error = cdev_write(vp->v_rdev, ap->a_uio, ap->a_ioflag); vn_lock(vp, LK_RETRY|LK_EXCLUSIVE); return error; default: return EOPNOTSUPP; } }
int ptyfs_freevp(struct vnode *vp) { struct ptyfsnode *ptyfs = VTOPTYFS(vp); ptyfs_hashrem(ptyfs); vp->v_data = NULL; return 0; }
/* * lookup. this is incredibly complicated in the * general case, however for most pseudo-filesystems * very little needs to be done. * * Locking isn't hard here, just poorly documented. * * If we're looking up ".", just vref the parent & return it. * * If we're looking up "..", unlock the parent, and lock "..". If everything * went ok, try to re-lock the parent. We do this to prevent lock races. * * For anything else, get the needed node. * * We try to exit with the parent locked in error cases. */ int ptyfs_lookup(void *v) { struct vop_lookup_v2_args /* { struct vnode * a_dvp; struct vnode ** a_vpp; struct componentname * a_cnp; } */ *ap = v; struct componentname *cnp = ap->a_cnp; struct vnode **vpp = ap->a_vpp; struct vnode *dvp = ap->a_dvp; const char *pname = cnp->cn_nameptr; struct ptyfsnode *ptyfs; int pty, error; *vpp = NULL; if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME) return EROFS; if (cnp->cn_namelen == 1 && *pname == '.') { *vpp = dvp; vref(dvp); return 0; } ptyfs = VTOPTYFS(dvp); switch (ptyfs->ptyfs_type) { case PTYFSroot: /* * Shouldn't get here with .. in the root node. */ if (cnp->cn_flags & ISDOTDOT) return EIO; pty = atoi(pname, cnp->cn_namelen); if (pty < 0 || pty >= npty || pty_isfree(pty, 1)) break; error = ptyfs_allocvp(dvp->v_mount, vpp, PTYFSpts, pty, curlwp); if (error) return error; VOP_UNLOCK(*vpp); return 0; default: return ENOTDIR; } return cnp->cn_nameiop == LOOKUP ? ENOENT : EROFS; }
/* * Invent attributes for ptyfsnode (vp) and store * them in (vap). * Directories lengths are returned as zero since * any real length would require the genuine size * to be computed, and nothing cares anyway. * * this is relatively minimal for ptyfs. */ int ptyfs_getattr(void *v) { struct vop_getattr_args /* { struct vnode *a_vp; struct vattr *a_vap; kauth_cred_t a_cred; } */ *ap = v; struct ptyfsnode *ptyfs = VTOPTYFS(ap->a_vp); struct vattr *vap = ap->a_vap; PTYFS_ITIMES(ptyfs, NULL, NULL, NULL); /* start by zeroing out the attributes */ VATTR_NULL(vap); /* next do all the common fields */ vap->va_type = ap->a_vp->v_type; vap->va_fsid = ap->a_vp->v_mount->mnt_stat.f_fsidx.__fsid_val[0]; vap->va_fileid = ptyfs->ptyfs_fileno; vap->va_gen = 0; vap->va_flags = 0; vap->va_nlink = 1; vap->va_blocksize = PAGE_SIZE; vap->va_atime = ptyfs->ptyfs_atime; vap->va_mtime = ptyfs->ptyfs_mtime; vap->va_ctime = ptyfs->ptyfs_ctime; vap->va_birthtime = ptyfs->ptyfs_birthtime; vap->va_mode = ptyfs->ptyfs_mode; vap->va_flags = ptyfs->ptyfs_flags; vap->va_uid = ptyfs->ptyfs_uid; vap->va_gid = ptyfs->ptyfs_gid; switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: if (pty_isfree(ptyfs->ptyfs_pty, 1)) return ENOENT; vap->va_bytes = vap->va_size = 0; vap->va_rdev = ap->a_vp->v_rdev; break; case PTYFSroot: vap->va_rdev = 0; vap->va_bytes = vap->va_size = DEV_BSIZE; break; default: return EOPNOTSUPP; } return 0; }
static int ptyfs_update(struct vnode *vp, const struct timespec *acc, const struct timespec *mod, int flags) { struct ptyfsnode *ptyfs = VTOPTYFS(vp); if (vp->v_mount->mnt_flag & MNT_RDONLY) return 0; PTYFS_ITIMES(ptyfs, acc, mod, NULL); return 0; }
/* * _print is used for debugging. * just print a readable description * of (vp). */ int ptyfs_print(void *v) { struct vop_print_args /* { struct vnode *a_vp; } */ *ap = v; struct ptyfsnode *ptyfs = VTOPTYFS(ap->a_vp); printf("tag VT_PTYFS, type %d, pty %d\n", ptyfs->ptyfs_type, ptyfs->ptyfs_pty); return 0; }
int ptyfs_filestat(struct vnode *vp, struct filestat *fsp) { struct ptyfsnode pn; struct specnode sn; struct mount mt; if (!KVM_READ(VTOPTYFS(vp), &pn, sizeof(pn))) { dprintf("can't read ptyfs_node at %p for pid %d", VTOPTYFS(vp), Pid); return 0; } if (!KVM_READ(vp->v_mount, &mt, sizeof(mt))) { dprintf("can't read mount at %p for pid %d", VTOPTYFS(vp), Pid); return 0; } fsp->fsid = mt.mnt_stat.f_fsidx.__fsid_val[0]; fsp->fileid = pn.ptyfs_fileno; fsp->mode = pn.ptyfs_mode; fsp->size = 0; switch (pn.ptyfs_type) { case PTYFSpts: case PTYFSptc: if (!KVM_READ(vp->v_specnode, &sn, sizeof(sn))) { dprintf("can't read specnode at %p for pid %d", vp->v_specnode, Pid); return 0; } fsp->rdev = sn.sn_rdev; fsp->mode |= S_IFCHR; break; case PTYFSroot: fsp->rdev = 0; fsp->mode |= S_IFDIR; break; } return 1; }
/* * Change the mode on a file. * Inode must be locked before calling. */ static int ptyfs_chmod(struct vnode *vp, mode_t mode, kauth_cred_t cred, struct lwp *l) { struct ptyfsnode *ptyfs = VTOPTYFS(vp); int error; if (kauth_cred_geteuid(cred) != ptyfs->ptyfs_uid && (error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL)) != 0) return error; ptyfs->ptyfs_mode &= ~ALLPERMS; ptyfs->ptyfs_mode |= (mode & ALLPERMS); return 0; }
/* * support advisory locking on pty nodes */ int ptyfs_advlock(void *v) { struct vop_print_args /* { struct vnode *a_vp; } */ *ap = v; struct ptyfsnode *ptyfs = VTOPTYFS(ap->a_vp); switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: return spec_advlock(v); default: return EOPNOTSUPP; } }
/* * Change the mode on a file. * Inode must be locked before calling. */ static int ptyfs_chmod(struct vnode *vp, mode_t mode, kauth_cred_t cred, struct lwp *l) { struct ptyfsnode *ptyfs = VTOPTYFS(vp); int error; error = kauth_authorize_vnode(cred, KAUTH_VNODE_WRITE_SECURITY, vp, NULL, genfs_can_chmod(vp->v_type, cred, ptyfs->ptyfs_uid, ptyfs->ptyfs_gid, mode)); if (error) return (error); ptyfs->ptyfs_mode &= ~ALLPERMS; ptyfs->ptyfs_mode |= (mode & ALLPERMS); return 0; }
int ptyfs_kqfilter(void *v) { struct vop_kqfilter_args /* { struct vnode *a_vp; struct knote *a_kn; } */ *ap = v; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: return cdev_kqfilter(vp->v_rdev, ap->a_kn); default: return genfs_kqfilter(v); } }
int ptyfs_poll(void *v) { struct vop_poll_args /* { struct vnode *a_vp; int a_events; } */ *ap = v; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: return cdev_poll(vp->v_rdev, ap->a_events, curlwp); default: return genfs_poll(v); } }
int ptyfs_open(void *v) { struct vop_open_args /* { struct vnode *a_vp; int a_mode; kauth_cred_t a_cred; } */ *ap = v; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: return spec_open(v); case PTYFSroot: return 0; default: return EINVAL; } }
int ptyfs_ioctl(void *v) { struct vop_ioctl_args /* { struct vnode *a_vp; u_long a_command; void *a_data; int a_fflag; kauth_cred_t a_cred; } */ *ap = v; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); switch (ptyfs->ptyfs_type) { case PTYFSpts: case PTYFSptc: return cdev_ioctl(vp->v_rdev, ap->a_command, ap->a_data, ap->a_fflag, curlwp); default: return EOPNOTSUPP; } }
/* * Perform chown operation on inode ip; * inode must be locked prior to call. */ static int ptyfs_chown(struct vnode *vp, uid_t uid, gid_t gid, kauth_cred_t cred, struct lwp *l) { struct ptyfsnode *ptyfs = VTOPTYFS(vp); int error; if (uid == (uid_t)VNOVAL) uid = ptyfs->ptyfs_uid; if (gid == (gid_t)VNOVAL) gid = ptyfs->ptyfs_gid; error = kauth_authorize_vnode(cred, KAUTH_VNODE_CHANGE_OWNERSHIP, vp, NULL, genfs_can_chown(cred, ptyfs->ptyfs_uid, ptyfs->ptyfs_gid, uid, gid)); if (error) return (error); ptyfs->ptyfs_gid = gid; ptyfs->ptyfs_uid = uid; return 0; }
/* * readdir returns directory entries from ptyfsnode (vp). * * the strategy here with ptyfs is to generate a single * directory entry at a time (struct dirent) and then * copy that out to userland using uiomove. a more efficent * though more complex implementation, would try to minimize * the number of calls to uiomove(). for ptyfs, this is * hardly worth the added code complexity. * * this should just be done through read() */ int ptyfs_readdir(void *v) { struct vop_readdir_args /* { struct vnode *a_vp; struct uio *a_uio; kauth_cred_t a_cred; int *a_eofflag; off_t **a_cookies; int *a_ncookies; } */ *ap = v; struct uio *uio = ap->a_uio; struct dirent *dp; struct ptyfsnode *ptyfs; off_t i; int error; off_t *cookies = NULL; int ncookies; struct vnode *vp; int nc = 0; vp = ap->a_vp; ptyfs = VTOPTYFS(vp); if (uio->uio_resid < UIO_MX) return EINVAL; if (uio->uio_offset < 0) return EINVAL; dp = malloc(sizeof(struct dirent), M_PTYFSTMP, M_WAITOK | M_ZERO); error = 0; i = uio->uio_offset; dp->d_reclen = UIO_MX; ncookies = uio->uio_resid / UIO_MX; if (ptyfs->ptyfs_type != PTYFSroot) { error = ENOTDIR; goto out; } if (i >= npty) goto out; if (ap->a_ncookies) { ncookies = min(ncookies, (npty + 2 - i)); cookies = malloc(ncookies * sizeof (off_t), M_TEMP, M_WAITOK); *ap->a_cookies = cookies; } for (; i < 2; i++) { /* `.' and/or `..' */ dp->d_fileno = PTYFS_FILENO(0, PTYFSroot); dp->d_namlen = i + 1; (void)memcpy(dp->d_name, "..", dp->d_namlen); dp->d_name[i + 1] = '\0'; dp->d_type = DT_DIR; if ((error = uiomove(dp, UIO_MX, uio)) != 0) goto out; if (cookies) *cookies++ = i + 1; nc++; } for (; uio->uio_resid >= UIO_MX && i < npty; i++) { /* check for used ptys */ if (pty_isfree(i - 2, 1)) continue; dp->d_fileno = PTYFS_FILENO(i - 2, PTYFSpts); dp->d_namlen = snprintf(dp->d_name, sizeof(dp->d_name), "%lld", (long long)(i - 2)); dp->d_type = DT_CHR; if ((error = uiomove(dp, UIO_MX, uio)) != 0) goto out; if (cookies) *cookies++ = i + 1; nc++; } out: /* not pertinent in error cases */ ncookies = nc; if (ap->a_ncookies) { if (error) { if (cookies) free(*ap->a_cookies, M_TEMP); *ap->a_ncookies = 0; *ap->a_cookies = NULL; } else *ap->a_ncookies = ncookies; } uio->uio_offset = i; free(dp, M_PTYFSTMP); return error; }
/*ARGSUSED*/ int ptyfs_setattr(void *v) { struct vop_setattr_args /* { struct vnodeop_desc *a_desc; struct vnode *a_vp; struct vattr *a_vap; kauth_cred_t a_cred; } */ *ap = v; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); struct vattr *vap = ap->a_vap; kauth_cred_t cred = ap->a_cred; struct lwp *l = curlwp; int error; if (vap->va_size != VNOVAL) { switch (ptyfs->ptyfs_type) { case PTYFSroot: return EISDIR; case PTYFSpts: case PTYFSptc: break; default: return EINVAL; } } if (vap->va_flags != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return EROFS; if (kauth_cred_geteuid(cred) != ptyfs->ptyfs_uid && (error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL)) != 0) return error; /* Immutable and append-only flags are not supported on ptyfs. */ if (vap->va_flags & (IMMUTABLE | APPEND)) return EINVAL; if (kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL) == 0) { /* Snapshot flag cannot be set or cleared */ if ((vap->va_flags & SF_SNAPSHOT) != (ptyfs->ptyfs_flags & SF_SNAPSHOT)) return EPERM; ptyfs->ptyfs_flags = vap->va_flags; } else { if ((ptyfs->ptyfs_flags & SF_SETTABLE) != (vap->va_flags & SF_SETTABLE)) return EPERM; ptyfs->ptyfs_flags &= SF_SETTABLE; ptyfs->ptyfs_flags |= (vap->va_flags & UF_SETTABLE); } ptyfs->ptyfs_flag |= PTYFS_CHANGE; } /* * Go through the fields and update iff not VNOVAL. */ if (vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return EROFS; if (ptyfs->ptyfs_type == PTYFSroot) return EPERM; error = ptyfs_chown(vp, vap->va_uid, vap->va_gid, cred, l); if (error) return error; } if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL || vap->va_birthtime.tv_sec != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return EROFS; if ((ptyfs->ptyfs_flags & SF_SNAPSHOT) != 0) return EPERM; if (kauth_cred_geteuid(cred) != ptyfs->ptyfs_uid && (error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL)) && ((vap->va_vaflags & VA_UTIMES_NULL) == 0 || (error = VOP_ACCESS(vp, VWRITE, cred)) != 0)) return (error); if (vap->va_atime.tv_sec != VNOVAL) if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) ptyfs->ptyfs_flag |= PTYFS_ACCESS; if (vap->va_mtime.tv_sec != VNOVAL) ptyfs->ptyfs_flag |= PTYFS_CHANGE | PTYFS_MODIFY; if (vap->va_birthtime.tv_sec != VNOVAL) ptyfs->ptyfs_birthtime = vap->va_birthtime; ptyfs->ptyfs_flag |= PTYFS_CHANGE; error = ptyfs_update(vp, &vap->va_atime, &vap->va_mtime, 0); if (error) return error; } if (vap->va_mode != (mode_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return EROFS; if (ptyfs->ptyfs_type == PTYFSroot) return EPERM; if ((ptyfs->ptyfs_flags & SF_SNAPSHOT) != 0 && (vap->va_mode & (S_IXUSR|S_IWUSR|S_IXGRP|S_IWGRP|S_IXOTH|S_IWOTH))) return EPERM; error = ptyfs_chmod(vp, vap->va_mode, cred, l); if (error) return error; } VN_KNOTE(vp, NOTE_ATTRIB); return 0; }
/*ARGSUSED*/ int ptyfs_setattr(void *v) { struct vop_setattr_args /* { struct vnodeop_desc *a_desc; struct vnode *a_vp; struct vattr *a_vap; kauth_cred_t a_cred; } */ *ap = v; struct vnode *vp = ap->a_vp; struct ptyfsnode *ptyfs = VTOPTYFS(vp); struct vattr *vap = ap->a_vap; kauth_cred_t cred = ap->a_cred; struct lwp *l = curlwp; int error; kauth_action_t action = KAUTH_VNODE_WRITE_FLAGS; bool changing_sysflags = false; if (vap->va_size != VNOVAL) { switch (ptyfs->ptyfs_type) { case PTYFSroot: return EISDIR; case PTYFSpts: case PTYFSptc: break; default: return EINVAL; } } if (vap->va_flags != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return EROFS; /* Immutable and append-only flags are not supported on ptyfs. */ if (vap->va_flags & (IMMUTABLE | APPEND)) return EINVAL; /* Snapshot flag cannot be set or cleared */ if ((vap->va_flags & SF_SNAPSHOT) != (ptyfs->ptyfs_flags & SF_SNAPSHOT)) return EPERM; if ((ptyfs->ptyfs_flags & SF_SETTABLE) != (vap->va_flags & SF_SETTABLE)) { changing_sysflags = true; action |= KAUTH_VNODE_WRITE_SYSFLAGS; } error = kauth_authorize_vnode(cred, action, vp, NULL, genfs_can_chflags(cred, vp->v_type, ptyfs->ptyfs_uid, changing_sysflags)); if (error) return error; if (changing_sysflags) { ptyfs->ptyfs_flags = vap->va_flags; } else { ptyfs->ptyfs_flags &= SF_SETTABLE; ptyfs->ptyfs_flags |= (vap->va_flags & UF_SETTABLE); } ptyfs->ptyfs_status |= PTYFS_CHANGE; } /* * Go through the fields and update iff not VNOVAL. */ if (vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return EROFS; if (ptyfs->ptyfs_type == PTYFSroot) return EPERM; error = ptyfs_chown(vp, vap->va_uid, vap->va_gid, cred, l); if (error) return error; } if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL || vap->va_birthtime.tv_sec != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return EROFS; if ((ptyfs->ptyfs_flags & SF_SNAPSHOT) != 0) return EPERM; error = kauth_authorize_vnode(cred, KAUTH_VNODE_WRITE_TIMES, vp, NULL, genfs_can_chtimes(vp, vap->va_vaflags, ptyfs->ptyfs_uid, cred)); if (error) return (error); if (vap->va_atime.tv_sec != VNOVAL) if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) ptyfs->ptyfs_status |= PTYFS_ACCESS; if (vap->va_mtime.tv_sec != VNOVAL) { ptyfs->ptyfs_status |= PTYFS_CHANGE | PTYFS_MODIFY; if (vp->v_mount->mnt_flag & MNT_RELATIME) ptyfs->ptyfs_status |= PTYFS_ACCESS; } if (vap->va_birthtime.tv_sec != VNOVAL) ptyfs->ptyfs_birthtime = vap->va_birthtime; ptyfs->ptyfs_status |= PTYFS_CHANGE; error = ptyfs_update(vp, &vap->va_atime, &vap->va_mtime, 0); if (error) return error; } if (vap->va_mode != (mode_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) return EROFS; if (ptyfs->ptyfs_type == PTYFSroot) return EPERM; if ((ptyfs->ptyfs_flags & SF_SNAPSHOT) != 0 && (vap->va_mode & (S_IXUSR|S_IWUSR|S_IXGRP|S_IWGRP|S_IXOTH|S_IWOTH))) return EPERM; error = ptyfs_chmod(vp, vap->va_mode, cred, l); if (error) return error; } VN_KNOTE(vp, NOTE_ATTRIB); return 0; }