Ejemplo n.º 1
0
/*ARGSUSED*/
static int
xmem_setattr(struct vnode *vp, struct vattr *vap, int flags, struct cred *cred,
	caller_context_t *ct)
{
	struct xmount *xm = (struct xmount *)VTOXM(vp);
	struct xmemnode *xp = (struct xmemnode *)VTOXN(vp);
	int error;
	struct vattr *get;
	register long int mask = vap->va_mask;

	/*
	 * Cannot set these attributes
	 */
	if (mask & AT_NOSET)
		return (EINVAL);

	mutex_enter(&xp->xn_tlock);

	get = &xp->xn_attr;

	error = secpolicy_vnode_setattr(cred, vp, vap, get, flags,
			xmem_xaccess, xp);

	if (error != 0)
		goto out;

	mask = vap->va_mask;

	/*
	 * Change file access modes.
	 */
	if (mask & AT_MODE) {
		/* prevent execute permission to be set for regular files */
		if (S_ISREG(get->va_mode))
			vap->va_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);

		XMEMPRINTF(1, ("xmem_setattr: va_mode old %x new %x\n",
				get->va_mode, vap->va_mode));

		get->va_mode &= S_IFMT;
		get->va_mode |= vap->va_mode & ~S_IFMT;
	}

	if (mask & AT_UID)
		get->va_uid = vap->va_uid;
	if (mask & AT_GID)
		get->va_gid = vap->va_gid;
	if (mask & AT_ATIME)
		get->va_atime = vap->va_atime;
	if (mask & AT_MTIME)
		get->va_mtime = vap->va_mtime;
	if (mask & (AT_UID | AT_GID | AT_MODE | AT_MTIME))
		gethrestime(&get->va_ctime);

	if (mask & AT_SIZE) {
		if (vp->v_type == VDIR) {
			error =  EISDIR;
			goto out;
		}
		/* Don't support large files. */
		if (vap->va_size > MAXOFF_T) {
			error = EFBIG;
			goto out;
		}
		if (error = xmem_xaccess(xp, VWRITE, cred))
			goto out;
		mutex_exit(&xp->xn_tlock);

		rw_enter(&xp->xn_rwlock, RW_WRITER);
		rw_enter(&xp->xn_contents, RW_WRITER);
		error = xmemnode_trunc(xm, xp, vap->va_size);
		rw_exit(&xp->xn_contents);
		rw_exit(&xp->xn_rwlock);
		goto out1;
	}
out:
	mutex_exit(&xp->xn_tlock);
out1:
	return (error);
}
Ejemplo n.º 2
0
int				/* ERRNO if error, 0 if successful. */
sam_setattr_ino(
	sam_node_t *ip,		/* pointer to inode. */
	vattr_t *vap,		/* vattr pointer. */
	int flags,		/* flags. */
	cred_t *credp)		/* credentials pointer. */
{
	uint_t mask;
	int error = 0;
	vnode_t *vp;
	sam_mode_t oldmode, mode;
	timespec_t  system_time;
	vattr_t oldva;

	oldva.va_mode = ip->di.mode;
	oldva.va_uid = ip->di.uid;
	oldva.va_gid = ip->di.gid;

	vp = SAM_ITOV(ip);
	if (vap->va_mask & AT_NOSET) {
		return (EINVAL);
	}
	mode = vap->va_mode & ~S_IFMT;
	SAM_HRESTIME(&system_time);

	/*
	 * Enforce the "read only" portion of WORM files.
	 */
	if (ip->di.status.b.worm_rdonly && !S_ISDIR(ip->di.mode)) {
		error = sam_chk_worm(mode, vap->va_mask, ip);
		if (error) {
			return (error);
		}
	}

	/*
	 * Generic setattr security policy check.
	 */
	if (error = secpolicy_vnode_setattr(credp, vp, vap,
	    &oldva, flags, sam_access_ino_ul, ip)) {
		return (error);
	}

	mask = vap->va_mask;

	if (mask & AT_SIZE) {		/* -----Change size */
		if (error == 0) {
			/* Can only truncate a regular file */
			if (S_ISREQ(ip->di.mode)) {
				error = EINVAL;
				goto out;
			} else if (SAM_PRIVILEGE_INO(ip->di.version,
			    ip->di.id.ino)) {
				error = EPERM;	/* Can't trunc priv'ed inodes */
				goto out;
			}
			if (S_ISSEGI(&ip->di) && (vap->va_size != 0)) {
				/*
				 * If file is segment access and not truncating
				 * to zero--fix.
				 */
				error = EINVAL;
				goto out;
			}
			/*
			 * Might need to do TRANS_ITRUNC here for LQFS....
			 */
			if ((error = sam_clear_ino(ip, (offset_t)vap->va_size,
			    STALE_ARCHIVE, credp))) {
				goto out;
			}
		}
	}

	if (mask & AT_MODE) {				/* -----Change mode */
		/* Cannot change .inodes file */
		if (ip->di.id.ino == SAM_INO_INO) {
			error = EPERM;
			goto out;
		}
		oldmode = ip->di.mode;
		ip->di.mode &= S_IFMT;
		ip->di.mode |= vap->va_mode & ~S_IFMT;
		if (ip->di.status.b.worm_rdonly) {
			if (!S_ISDIR(ip->di.mode)) {
				ip->di.mode &= ~WMASK;
			}
			if (oldmode & S_ISUID) {
				ip->di.mode |= S_ISUID;
			}
		}

		/*
		 * In 4.6 there are two modes of WORM trigger operation.
		 * One is compatible with the 53xx SUN NAS series.  This
		 * mode uses the SUID bit by itself.  The second mode is
		 * called compatibility mode.  This mode uses the transition
		 * from a writeable mode as the trigger. Note, copying a
		 * read-only file to a WORM capable volume does *NOT*
		 * initiate the WORM trigger in this mode.
		 */
		if (samgt.license.license.lic_u.b.WORM_fs &&
		    (ip->di.version >= SAM_INODE_VERS_2) &&
		    (((vap->va_mode == S_ISUID) &&
		    (ip->mp->mt.fi_config & MT_ALLWORM)) ||
		    ((ip->mp->mt.fi_config & MT_ALLEMUL) &&
		    (((oldmode & RWXALLMASK) == RWXALLMASK) ||
		    ((vap->va_mode != S_ISUID) &&
		    (oldmode & WMASK) && !(ip->di.mode & WMASK)))))) {
			error = sam_worm_trigger(ip, oldmode, system_time);
			if (error) {
				ip->di.mode = oldmode;
				goto out;
			} else if ((ip->mp->mt.fi_config & MT_ALLEMUL) &&
			    ((oldmode & RWXALLMASK) == RWXALLMASK)) {
				ip->di.mode = oldmode;
			} else if ((vap->va_mode == S_ISUID) &&
			    (!S_ISDIR(ip->di.mode))) {
				if (ip->mp->mt.fi_config & MT_ALLEMUL) {
					ip->di.mode = oldmode &
					    (S_IFMT | RMASK);
				} else {
					ip->di.mode = S_ISUID |
					    (oldmode & (S_IFMT | RMASK));
				}
			}
		}
		TRANS_INODE(ip->mp, ip);
		if (S_ISATTRDIR(oldmode)) {
			ip->di.mode |= S_IFATTRDIR;
		}
		sam_mark_ino(ip, SAM_CHANGED);
	}

	if (mask & (AT_UID | AT_GID)) {		/* -----Change uid/gid */
		int ouid, ogid;

		if (vap->va_mask & AT_MODE) {
			ip->di.mode = (ip->di.mode & S_IFMT) |
			    (vap->va_mode & ~S_IFMT);
		}
		/*
		 * To change file ownership, a process must have
		 * privilege if:
		 *
		 * If it is not the owner of the file, or
		 * if doing restricted chown semantics and
		 * either changing the ownership to someone else or
		 * changing the group to a group that we are not
		 * currently in.
		 */
		if (crgetuid(credp) != ip->di.uid ||
		    (rstchown &&
		    (((mask & AT_UID) && vap->va_uid != ip->di.uid) ||
		    ((mask & AT_GID) && !groupmember(vap->va_gid, credp))))) {
			error = secpolicy_vnode_owner(credp, vap->va_uid);
			if (error) {
				goto out;
			}
		}

		ouid = ip->di.uid;
		ogid = ip->di.gid;
		if (error = sam_quota_chown(ip->mp, ip,
		    (mask&AT_UID) ? vap->va_uid : ouid,
		    (mask&AT_GID) ? vap->va_gid : ogid, credp)) {
			goto out;
		}
		if (mask & AT_UID)  ip->di.uid = vap->va_uid;
		if (mask & AT_GID)  ip->di.gid = vap->va_gid;
		ip->di.status.b.archdone = 0;
		TRANS_INODE(ip->mp, ip);
		sam_mark_ino(ip, SAM_CHANGED);
		/*
		 * Notify arfind and event daemon of setattr.
		 */
		sam_send_to_arfind(ip, AE_change, 0);
		if (ip->mp->ms.m_fsev_buf) {
			sam_send_event(ip->mp, &ip->di, ev_change, 0, 0,
			    ip->di.change_time.tv_sec);
		}
	}

	if (mask & (AT_ATIME | AT_MTIME)) {	/* -----Modify times */
		/*
		 * Synchronously flush pages so dates do not get changed after
		 * utime.  If staging, finish stage and then flush pages.
		 */
		if (ip->flags.b.staging) {
			/*
			 * Might need to do TRANS_ITRUNC or similar here
			 * for LQFS
			 */
			if ((error = sam_clear_file(ip, ip->di.rm.size,
			    MAKE_ONLINE, credp))) {
				goto out;
			}
		}
		sam_flush_pages(ip, 0);
		if (mask & AT_ATIME) {
			/*
			 * The access time field is used by WORM operations to
			 * store the retention timestamp.  This is intercepted
			 * here and stored in the inode's retention period
			 * time fields if either the field hasn't been set or
			 * the provided value exceeds what is currently there
			 * (i.e. we're extending the period).
			 */
			error = sam_check_worm_capable(ip, TRUE);
			if (!error) {
				boolean_t   lite_mode =
				    ((ip->mp->mt.fi_config &
				    MT_LITE_WORM) != 0);
				boolean_t   is_priv =
				    (secpolicy_fs_config(credp,
				    ip->mp->mi.m_vfsp) == 0);

				if (S_ISREG(ip->di.mode) && !WORM(ip)) {
					/*
					 * Regular file in WORM capable
					 * directory.  Set access time per the
					 * request.
					 */
					ip->di.access_time.tv_sec =
					    vap->va_atime.tv_sec;
					ip->di.access_time.tv_nsec =
					    vap->va_atime.tv_nsec;
					ip->flags.b.accessed = 1;
				} else if (WORM(ip) &&
				    (ip->di.version >= SAM_INODE_VERS_2)) {
					boolean_t	extend_period;
					/*
					 * Extend the retention period if so
					 * requested. If lite mode and a
					 * privileged user or a directory allow
					 * the retention period to be shortened.
					 */

					if (vap->va_atime.tv_sec >
					    ip->di2.rperiod_start_time +
					    ip->di2.rperiod_duration * 60) {
						extend_period = 1;
					} else {
						extend_period = 0;
					}

					if (S_ISREG(ip->di.mode) &&
					    extend_period) {
						error = sam_set_rperiod(ip,
						    vap, is_priv &&
						    lite_mode);
					} else if (S_ISDIR(ip->di.mode) ||
					    (S_ISREG(ip->di.mode) &&
					    is_priv && lite_mode)) {
						/*
						 * If the requested time would
						 * result in a non- negative
						 * retention period, set the
						 * period to the difference of
						 * the request and current time.
						 * A negative retention period
						 * is not allowed.
						 */
						if (vap->va_atime.tv_sec >
						    system_time.tv_sec) {


			if (vap->va_atime.tv_sec == INT_MAX) {
				ip->di2.rperiod_duration = 0;
			} else {
				ip->di2.rperiod_duration = 1 +
				    (vap->va_atime.tv_sec -
				    system_time.tv_sec)/60;
			}


						} else {
							error = EINVAL;
						}
					}
					if (error) {
						goto out;
					}
				} else {
					/*
					 * Shouldn't get here, invalid request.
					 */
					error = EINVAL;
					goto out;
				}
				TRANS_INODE(ip->mp, ip);
				sam_mark_ino(ip, SAM_CHANGED);
			} else {
				error = 0;
				ip->di.access_time.tv_sec =
				    vap->va_atime.tv_sec;
				ip->di.access_time.tv_nsec =
				    vap->va_atime.tv_nsec;
				ip->flags.b.accessed = 1;
				TRANS_INODE(ip->mp, ip);
			}
		}
		if (mask & AT_MTIME) {
			if (!ip->di.status.b.worm_rdonly) {
				ip->di.modify_time.tv_sec =
				    vap->va_mtime.tv_sec;
				ip->di.modify_time.tv_nsec =
				    vap->va_mtime.tv_nsec;
				ip->di.change_time.tv_sec = system_time.tv_sec;
				ip->di.change_time.tv_nsec =
				    system_time.tv_nsec;
				ip->flags.b.updated = 1;
				/* Modify time has been set */
				ip->flags.b.dirty = 1;
			}
			TRANS_INODE(ip->mp, ip);
		}
	}

	/*
	 * Check for and apply ACL info, if present.
	 */
	if (ip->di.status.b.acl && !ip->di.status.b.worm_rdonly) {
		if (SAM_IS_SHARED_FS(ip->mp) && SAM_IS_SHARED_SERVER(ip->mp)) {
			RW_UNLOCK_OS(&ip->inode_rwl, RW_WRITER);
			sam_callout_acl(ip, ip->mp->ms.m_client_ord);
			RW_LOCK_OS(&ip->inode_rwl, RW_WRITER);
		}
		if (error = sam_acl_setattr(ip, vap)) {
			goto out;
		}
	}
	if (ip->mp->mt.fi_config & MT_SHARED_WRITER) {
		if ((error == 0) &&
		    (ip->flags.bits & (SAM_ACCESSED|SAM_UPDATED|SAM_CHANGED))) {
			(void) sam_update_inode(ip, SAM_SYNC_ONE, FALSE);
		}
	}
out:

	return (error);
}
Ejemplo n.º 3
0
/*ARGSUSED4*/
static int
devfs_setattr(
	struct vnode *vp,
	struct vattr *vap,
	int flags,
	struct cred *cr,
	caller_context_t *ct)
{
	struct dv_node	*dv = VTODV(vp);
	struct dv_node	*ddv;
	struct vnode	*dvp;
	struct vattr	*map;
	long int	mask;
	int		error = 0;
	struct vattr	*free_vattr = NULL;
	struct vattr	*vattrp = NULL;
	mperm_t		mp;
	int		persist;

	/*
	 * Message goes to console only. Otherwise, the message
	 * causes devfs_getattr to be invoked again... infinite loop
	 */
	dcmn_err2(("?devfs_setattr %s\n", dv->dv_name));
	ASSERT(dv->dv_attr || dv->dv_attrvp);

	if (!(vp->v_type == VDIR || vp->v_type == VCHR || vp->v_type == VBLK)) {
		cmn_err(CE_WARN,	/* panic ? */
		    "?%s: getattr on vnode type %d", dvnm, vp->v_type);
		return (ENOENT);
	}

	if (vap->va_mask & AT_NOSET)
		return (EINVAL);

	/*
	 * If we are changing something we don't care about
	 * the persistence of, return success.
	 */
	if ((vap->va_mask &
	    (AT_MODE|AT_UID|AT_GID|AT_ATIME|AT_MTIME)) == 0)
		return (0);

	/*
	 * If driver overrides fs perm, disallow chmod
	 * and do not create attribute nodes.
	 */
	if (dv->dv_flags & DV_NO_FSPERM) {
		ASSERT(dv->dv_attr);
		if (vap->va_mask & (AT_MODE | AT_UID | AT_GID))
			return (EPERM);
		if ((vap->va_mask & (AT_ATIME|AT_MTIME)) == 0)
			return (0);
		rw_enter(&dv->dv_contents, RW_WRITER);
		if (vap->va_mask & AT_ATIME)
			dv->dv_attr->va_atime = vap->va_atime;
		if (vap->va_mask & AT_MTIME)
			dv->dv_attr->va_mtime = vap->va_mtime;
		rw_exit(&dv->dv_contents);
		return (0);
	}

	/*
	 * Directories are always created but device nodes are
	 * only used to persist non-default permissions.
	 */
	if (vp->v_type == VDIR) {
		ASSERT(dv->dv_attr || dv->dv_attrvp);
		return (devfs_setattr_dir(dv, vp, vap, flags, cr));
	}

	/*
	 * Allocate now before we take any locks
	 */
	vattrp = kmem_zalloc(sizeof (*vattrp), KM_SLEEP);

	/* to ensure consistency, single thread setting of attributes */
	rw_enter(&dv->dv_contents, RW_WRITER);

	/*
	 * We don't need to create an attribute node
	 * to persist access or modification times.
	 */
	persist = (vap->va_mask & (AT_MODE | AT_UID | AT_GID));

	/*
	 * If persisting something, get the default permissions
	 * for this minor to compare against what the attributes
	 * are now being set to.  Default ordering is:
	 *	- minor_perm match for this minor
	 *	- mode supplied by ddi_create_priv_minor_node
	 *	- devfs defaults
	 */
	if (persist) {
		if (dev_minorperm(dv->dv_devi, dv->dv_name, &mp) != 0) {
			mp.mp_uid = dv_vattr_file.va_uid;
			mp.mp_gid = dv_vattr_file.va_gid;
			mp.mp_mode = dv_vattr_file.va_mode;
			if (dv->dv_flags & DV_DFLT_MODE) {
				ASSERT((dv->dv_dflt_mode & ~S_IAMB) == 0);
				mp.mp_mode &= ~S_IAMB;
				mp.mp_mode |= dv->dv_dflt_mode;
				dcmn_err5(("%s: setattr priv default 0%o\n",
				    dv->dv_name, mp.mp_mode));
			} else {
				dcmn_err5(("%s: setattr devfs default 0%o\n",
				    dv->dv_name, mp.mp_mode));
			}
		} else {
			dcmn_err5(("%s: setattr minor perm default 0%o\n",
			    dv->dv_name, mp.mp_mode));
		}
	}

	/*
	 * If we don't have a vattr for this node, construct one.
	 */
	if (dv->dv_attr) {
		free_vattr = vattrp;
		vattrp = NULL;
	} else {
		ASSERT(dv->dv_attrvp);
		ASSERT(vp->v_type != VDIR);
		*vattrp = dv_vattr_file;
		error = VOP_GETATTR(dv->dv_attrvp, vattrp, 0, cr);
		dsysdebug(error, ("vop_getattr %s %d\n",
			dv->dv_name, error));
		if (error)
			goto out;
		dv->dv_attr = vattrp;
		dv_vattr_merge(dv, dv->dv_attr);
		vattrp = NULL;
	}

	error = secpolicy_vnode_setattr(cr, vp, vap, dv->dv_attr,
					flags, devfs_unlocked_access, dv);
	if (error) {
		dsysdebug(error, ("devfs_setattr %s secpolicy error %d\n",
			dv->dv_name, error));
		goto out;
	}

	/*
	 * Apply changes to the memory based attribute. This code
	 * is modeled after the tmpfs implementation of memory
	 * based vnodes
	 */
	map = dv->dv_attr;
	mask = vap->va_mask;

	/* Change file access modes. */
	if (mask & AT_MODE) {
		map->va_mode &= S_IFMT;
		map->va_mode |= vap->va_mode & ~S_IFMT;
	}
	if (mask & AT_UID)
		map->va_uid = vap->va_uid;
	if (mask & AT_GID)
		map->va_gid = vap->va_gid;
	if (mask & AT_ATIME)
		map->va_atime = vap->va_atime;
	if (mask & AT_MTIME)
		map->va_mtime = vap->va_mtime;

	if (mask & (AT_MODE | AT_UID | AT_GID | AT_MTIME)) {
		gethrestime(&map->va_ctime);
	}

	/*
	 * A setattr to defaults means we no longer need the
	 * shadow node as a persistent store, unless there
	 * are ACLs.  Otherwise create a shadow node if one
	 * doesn't exist yet.
	 */
	if (persist) {
		if ((dv_setattr_cmp(map, &mp) == 0) &&
		    ((dv->dv_flags & DV_ACL) == 0)) {

			if (dv->dv_attrvp) {
				ddv = dv->dv_dotdot;
				ASSERT(ddv->dv_attrvp);
				error = VOP_REMOVE(ddv->dv_attrvp,
				    dv->dv_name, cr);
				dsysdebug(error,
				    ("vop_remove %s %s %d\n",
				    ddv->dv_name, dv->dv_name, error));

				if (error == EROFS)
					error = 0;
				VN_RELE(dv->dv_attrvp);
				dv->dv_attrvp = NULL;
			}
			ASSERT(dv->dv_attr);
		} else {
			if (mask & AT_MODE)
				dcmn_err5(("%s persisting mode 0%o\n",
					dv->dv_name, vap->va_mode));
			if (mask & AT_UID)
				dcmn_err5(("%s persisting uid %d\n",
					dv->dv_name, vap->va_uid));
			if (mask & AT_GID)
				dcmn_err5(("%s persisting gid %d\n",
					dv->dv_name, vap->va_gid));

			if (dv->dv_attrvp == NULL) {
				dvp = DVTOV(dv->dv_dotdot);
				dv_shadow_node(dvp, dv->dv_name, vp,
				    NULL, NULLVP, cr,
				    DV_SHADOW_CREATE | DV_SHADOW_WRITE_HELD);
			}
			if (dv->dv_attrvp) {
				error = VOP_SETATTR(dv->dv_attrvp,
				    vap, flags, cr, NULL);
				dsysdebug(error, ("vop_setattr %s %d\n",
				    dv->dv_name, error));
			}
			/*
			 * Some file systems may return EROFS for a setattr
			 * on a readonly file system.  In this case save
			 * as our own memory based attribute.
			 * NOTE: ufs is NOT one of these (see ufs_iupdat).
			 */
			if (dv->dv_attr && dv->dv_attrvp && error == 0) {
				vattrp = dv->dv_attr;
				dv->dv_attr = NULL;
			} else if (error == EROFS)
				error = 0;
		}
	}

out:
	rw_exit(&dv->dv_contents);

	if (vattrp)
		kmem_free(vattrp, sizeof (*vattrp));
	if (free_vattr)
		kmem_free(free_vattr, sizeof (*free_vattr));
	return (error);
}
Ejemplo n.º 4
0
/* ARGSUSED */
static int
nm_setattr(
	vnode_t *vp,
	vattr_t *vap,
	int flags,
	cred_t *crp,
	caller_context_t *ctp)
{
	struct namenode *nodep = VTONM(vp);
	struct vattr *nmvap = &nodep->nm_vattr;
	long mask = vap->va_mask;
	int error = 0;

	/*
	 * Cannot set these attributes.
	 */
	if (mask & (AT_NOSET|AT_SIZE))
		return (EINVAL);

	(void) VOP_RWLOCK(nodep->nm_filevp, V_WRITELOCK_TRUE, ctp);
	mutex_enter(&nodep->nm_lock);

	/*
	 * Change ownership/group/time/access mode of mounted file
	 * descriptor.
	 */

	error = secpolicy_vnode_setattr(crp, vp, vap, nmvap, flags,
	    nm_access_unlocked, nodep);
	if (error)
		goto out;

	mask = vap->va_mask;
	/*
	 * If request to change mode, copy new
	 * mode into existing attribute structure.
	 */
	if (mask & AT_MODE)
		nmvap->va_mode = vap->va_mode & ~VSVTX;

	/*
	 * If request was to change user or group, turn off suid and sgid
	 * bits.
	 * If the system was configured with the "rstchown" option, the
	 * owner is not permitted to give away the file, and can change
	 * the group id only to a group of which he or she is a member.
	 */
	if (mask & AT_UID)
		nmvap->va_uid = vap->va_uid;
	if (mask & AT_GID)
		nmvap->va_gid = vap->va_gid;
	/*
	 * If request is to modify times, make sure user has write
	 * permissions on the file.
	 */
	if (mask & AT_ATIME)
		nmvap->va_atime = vap->va_atime;
	if (mask & AT_MTIME) {
		nmvap->va_mtime = vap->va_mtime;
		gethrestime(&nmvap->va_ctime);
	}
out:
	mutex_exit(&nodep->nm_lock);
	VOP_RWUNLOCK(nodep->nm_filevp, V_WRITELOCK_TRUE, ctp);
	return (error);
}
Ejemplo n.º 5
0
/*ARGSUSED4*/
static int
devfs_setattr_dir(
	struct dv_node *dv,
	struct vnode *vp,
	struct vattr *vap,
	int flags,
	struct cred *cr)
{
	struct vattr	*map;
	long int	mask;
	int		error = 0;
	struct vattr	vattr;

	ASSERT(dv->dv_attr || dv->dv_attrvp);

	ASSERT(vp->v_type == VDIR);
	ASSERT((dv->dv_flags & DV_NO_FSPERM) == 0);

	if (vap->va_mask & AT_NOSET)
		return (EINVAL);

	/* to ensure consistency, single thread setting of attributes */
	rw_enter(&dv->dv_contents, RW_WRITER);

again:	if (dv->dv_attr) {

		error = secpolicy_vnode_setattr(cr, vp, vap, dv->dv_attr,
					flags, devfs_unlocked_access, dv);

		if (error)
			goto out;

		/*
		 * Apply changes to the memory based attribute. This code
		 * is modeled after the tmpfs implementation of memory
		 * based vnodes
		 */
		map = dv->dv_attr;
		mask = vap->va_mask;

		/* Change file access modes. */
		if (mask & AT_MODE) {
			map->va_mode &= S_IFMT;
			map->va_mode |= vap->va_mode & ~S_IFMT;
		}
		if (mask & AT_UID)
			map->va_uid = vap->va_uid;
		if (mask & AT_GID)
			map->va_gid = vap->va_gid;
		if (mask & AT_ATIME)
			map->va_atime = vap->va_atime;
		if (mask & AT_MTIME)
			map->va_mtime = vap->va_mtime;

		if (mask & (AT_MODE | AT_UID | AT_GID | AT_MTIME))
			gethrestime(&map->va_ctime);
	} else {
		/* use the backing attribute store */
		ASSERT(dv->dv_attrvp);

		/*
		 * See if we are changing something we care about
		 * the persistence of - return success if we don't care.
		 */
		if (vap->va_mask & (AT_MODE|AT_UID|AT_GID|AT_ATIME|AT_MTIME)) {
			/* Set the attributes */
			error = VOP_SETATTR(dv->dv_attrvp,
				vap, flags, cr, NULL);
			dsysdebug(error,
				("vop_setattr %s %d\n", dv->dv_name, error));

			/*
			 * Some file systems may return EROFS for a setattr
			 * on a readonly file system.  In this case we create
			 * our own memory based attribute.
			 */
			if (error == EROFS) {
				/*
				 * obtain attributes from existing file
				 * that we will modify and switch to memory
				 * based attribute until attribute store is
				 * read/write.
				 */
				vattr = dv_vattr_dir;
				if (VOP_GETATTR(dv->dv_attrvp, &vattr,
				    flags, cr) == 0) {
					dv->dv_attr = kmem_alloc(
					    sizeof (struct vattr), KM_SLEEP);
					*dv->dv_attr = vattr;
					dv_vattr_merge(dv, dv->dv_attr);
					goto again;
				}
			}
		}
	}
out:
	rw_exit(&dv->dv_contents);
	return (error);
}