extractedLnx/linux-2.6.26/fs/xfs/xfs_vnodeops.c_xfs_setattr.c
int
xfs_setattr(
xfs_inode_t *ip,
bhv_vattr_t *vap,
int flags,
cred_t *credp)
{
xfs_mount_t *mp = ip->i_mount;
xfs_trans_t *tp;
int mask;
int code;
uint lock_flags;
uint commit_flags=0;
uid_t uid=0, iuid=0;
gid_t gid=0, igid=0;
int timeflags = 0;
xfs_prid_t projid=0, iprojid=0;
struct xfs_dquot *udqp, *gdqp, *olddquot1, *olddquot2;
int file_owner;
int need_iolock = 1;
xfs_itrace_entry(ip);
if (mp->m_flags & XFS_MOUNT_RDONLY)
return XFS_ERROR(EROFS);
/*
* Cannot set certain attributes.
*/
mask = vap->va_mask;
if (mask & XFS_AT_NOSET) {
return XFS_ERROR(EINVAL);
}
if (XFS_FORCED_SHUTDOWN(mp))
return XFS_ERROR(EIO);
/*
* Timestamps do not need to be logged and hence do not
* need to be done within a transaction.
*/
if (mask & XFS_AT_UPDTIMES) {
ASSERT((mask & ~XFS_AT_UPDTIMES) == 0);
timeflags = ((mask & XFS_AT_UPDATIME) ? XFS_ICHGTIME_ACC : 0) |
((mask & XFS_AT_UPDCTIME) ? XFS_ICHGTIME_CHG : 0) |
((mask & XFS_AT_UPDMTIME) ? XFS_ICHGTIME_MOD : 0);
xfs_ichgtime(ip, timeflags);
return 0;
}
olddquot1 = olddquot2 = NULL;
udqp = gdqp = NULL;
/*
* If disk quotas is on, we make sure that the dquots do exist on disk,
* before we start any other transactions. Trying to do this later
* is messy. We don't care to take a readlock to look at the ids
* in inode here, because we can't hold it across the trans_reserve.
* If the IDs do change before we take the ilock, we're covered
* because the i_*dquot fields will get updated anyway.
*/
if (XFS_IS_QUOTA_ON(mp) &&
(mask & (XFS_AT_UID|XFS_AT_GID|XFS_AT_PROJID))) {
uint qflags = 0;
if ((mask & XFS_AT_UID) && XFS_IS_UQUOTA_ON(mp)) {
uid = vap->va_uid;
qflags |= XFS_QMOPT_UQUOTA;
} else {
uid = ip->i_d.di_uid;
}
if ((mask & XFS_AT_GID) && XFS_IS_GQUOTA_ON(mp)) {
gid = vap->va_gid;
qflags |= XFS_QMOPT_GQUOTA;
} else {
gid = ip->i_d.di_gid;
}
if ((mask & XFS_AT_PROJID) && XFS_IS_PQUOTA_ON(mp)) {
projid = vap->va_projid;
qflags |= XFS_QMOPT_PQUOTA;
} else {
projid = ip->i_d.di_projid;
}
/*
* We take a reference when we initialize udqp and gdqp,
* so it is important that we never blindly double trip on
* the same variable. See xfs_create() for an example.
*/
ASSERT(udqp == NULL);
ASSERT(gdqp == NULL);
code = XFS_QM_DQVOPALLOC(mp, ip, uid, gid, projid, qflags,
&udqp, &gdqp);
if (code)
return code;
}
/*
* For the other attributes, we acquire the inode lock and
* first do an error checking pass.
*/
tp = NULL;
lock_flags = XFS_ILOCK_EXCL;
if (flags & ATTR_NOLOCK)
need_iolock = 0;
if (!(mask & XFS_AT_SIZE)) {
if ((mask != (XFS_AT_CTIME|XFS_AT_ATIME|XFS_AT_MTIME)) ||
(mp->m_flags & XFS_MOUNT_WSYNC)) {
tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_NOT_SIZE);
commit_flags = 0;
if ((code = xfs_trans_reserve(tp, 0,
XFS_ICHANGE_LOG_RES(mp), 0,
0, 0))) {
lock_flags = 0;
goto error_return;
}
}
} else {
if (DM_EVENT_ENABLED(ip, DM_EVENT_TRUNCATE) &&
!(flags & ATTR_DMI)) {
int dmflags = AT_DELAY_FLAG(flags) | DM_SEM_FLAG_WR;
code = XFS_SEND_DATA(mp, DM_EVENT_TRUNCATE, ip,
vap->va_size, 0, dmflags, NULL);
if (code) {
lock_flags = 0;
goto error_return;
}
}
if (need_iolock)
lock_flags |= XFS_IOLOCK_EXCL;
}
xfs_ilock(ip, lock_flags);
/* boolean: are we the file owner? */
file_owner = (current_fsuid(credp) == ip->i_d.di_uid);
/*
* Change various properties of a file.
* Only the owner or users with CAP_FOWNER
* capability may do these things.
*/
if (mask &
(XFS_AT_MODE|XFS_AT_XFLAGS|XFS_AT_EXTSIZE|XFS_AT_UID|
XFS_AT_GID|XFS_AT_PROJID)) {
/*
* CAP_FOWNER overrides the following restrictions:
*
* The user ID of the calling process must be equal
* to the file owner ID, except in cases where the
* CAP_FSETID capability is applicable.
*/
if (!file_owner && !capable(CAP_FOWNER)) {
code = XFS_ERROR(EPERM);
goto error_return;
}
/*
* CAP_FSETID overrides the following restrictions:
*
* The effective user ID of the calling process shall match
* the file owner when setting the set-user-ID and
* set-group-ID bits on that file.
*
* The effective group ID or one of the supplementary group
* IDs of the calling process shall match the group owner of
* the file when setting the set-group-ID bit on that file
*/
if (mask & XFS_AT_MODE) {
mode_t m = 0;
if ((vap->va_mode & S_ISUID) && !file_owner)
m |= S_ISUID;
if ((vap->va_mode & S_ISGID) &&
!in_group_p((gid_t)ip->i_d.di_gid))
m |= S_ISGID;
#if 0
/* Linux allows this, Irix doesn't. */
if ((vap->va_mode & S_ISVTX) && !S_ISDIR(ip->i_d.di_mode))
m |= S_ISVTX;
#endif
if (m && !capable(CAP_FSETID))
vap->va_mode &= ~m;
}
}
/*
* Change file ownership. Must be the owner or privileged.
* If the system was configured with the "restricted_chown"
* 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 & (XFS_AT_UID|XFS_AT_GID|XFS_AT_PROJID)) {
/*
* These IDs could have changed since we last looked at them.
* But, we're assured that if the ownership did change
* while we didn't have the inode locked, inode's dquot(s)
* would have changed also.
*/
iuid = ip->i_d.di_uid;
iprojid = ip->i_d.di_projid;
igid = ip->i_d.di_gid;
gid = (mask & XFS_AT_GID) ? vap->va_gid : igid;
uid = (mask & XFS_AT_UID) ? vap->va_uid : iuid;
projid = (mask & XFS_AT_PROJID) ? (xfs_prid_t)vap->va_projid :
iprojid;
/*
* CAP_CHOWN overrides the following restrictions:
*
* If _POSIX_CHOWN_RESTRICTED is defined, this capability
* shall override the restriction that a process cannot
* change the user ID of a file it owns and the restriction
* that the group ID supplied to the chown() function
* shall be equal to either the group ID or one of the
* supplementary group IDs of the calling process.
*/
if (restricted_chown &&
(iuid != uid || (igid != gid &&
!in_group_p((gid_t)gid))) &&
!capable(CAP_CHOWN)) {
code = XFS_ERROR(EPERM);
goto error_return;
}
/*
* Do a quota reservation only if uid/projid/gid is actually
* going to change.
*/
if ((XFS_IS_UQUOTA_ON(mp) && iuid != uid) ||
(XFS_IS_PQUOTA_ON(mp) && iprojid != projid) ||
(XFS_IS_GQUOTA_ON(mp) && igid != gid)) {
ASSERT(tp);
code = XFS_QM_DQVOPCHOWNRESV(mp, tp, ip, udqp, gdqp,
capable(CAP_FOWNER) ?
XFS_QMOPT_FORCE_RES : 0);
if (code) /* out of quota */
goto error_return;
}
}
/*
* Truncate file. Must have write permission and not be a directory.
*/
if (mask & XFS_AT_SIZE) {
/* Short circuit the truncate case for zero length files */
if ((vap->va_size == 0) &&
(ip->i_size == 0) && (ip->i_d.di_nextents == 0)) {
xfs_iunlock(ip, XFS_ILOCK_EXCL);
lock_flags &= ~XFS_ILOCK_EXCL;
if (mask & XFS_AT_CTIME)
xfs_ichgtime(ip, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
code = 0;
goto error_return;
}
if (S_ISDIR(ip->i_d.di_mode)) {
code = XFS_ERROR(EISDIR);
goto error_return;
} else if (!S_ISREG(ip->i_d.di_mode)) {
code = XFS_ERROR(EINVAL);
goto error_return;
}
/*
* Make sure that the dquots are attached to the inode.
*/
if ((code = XFS_QM_DQATTACH(mp, ip, XFS_QMOPT_ILOCKED)))
goto error_return;
}
/*
* Change file access or modified times.
*/
if (mask & (XFS_AT_ATIME|XFS_AT_MTIME)) {
if (!file_owner) {
if ((flags & ATTR_UTIME) &&
!capable(CAP_FOWNER)) {
code = XFS_ERROR(EPERM);
goto error_return;
}
}
}
/*
* Change extent size or realtime flag.
*/
if (mask & (XFS_AT_EXTSIZE|XFS_AT_XFLAGS)) {
/*
* Can't change extent size if any extents are allocated.
*/
if (ip->i_d.di_nextents && (mask & XFS_AT_EXTSIZE) &&
((ip->i_d.di_extsize << mp->m_sb.sb_blocklog) !=
vap->va_extsize) ) {
code = XFS_ERROR(EINVAL); /* EFBIG? */
goto error_return;
}
/*
* Can't change realtime flag if any extents are allocated.
*/
if ((ip->i_d.di_nextents || ip->i_delayed_blks) &&
(mask & XFS_AT_XFLAGS) &&
(XFS_IS_REALTIME_INODE(ip)) !=
(vap->va_xflags & XFS_XFLAG_REALTIME)) {
code = XFS_ERROR(EINVAL); /* EFBIG? */
goto error_return;
}
/*
* Extent size must be a multiple of the appropriate block
* size, if set at all.
*/
if ((mask & XFS_AT_EXTSIZE) && vap->va_extsize != 0) {
xfs_extlen_t size;
if (XFS_IS_REALTIME_INODE(ip) ||
((mask & XFS_AT_XFLAGS) &&
(vap->va_xflags & XFS_XFLAG_REALTIME))) {
size = mp->m_sb.sb_rextsize <<
mp->m_sb.sb_blocklog;
} else {
size = mp->m_sb.sb_blocksize;
}
if (vap->va_extsize % size) {
code = XFS_ERROR(EINVAL);
goto error_return;
}
}
/*
* If realtime flag is set then must have realtime data.
*/
if ((mask & XFS_AT_XFLAGS) &&
(vap->va_xflags & XFS_XFLAG_REALTIME)) {
if ((mp->m_sb.sb_rblocks == 0) ||
(mp->m_sb.sb_rextsize == 0) ||
(ip->i_d.di_extsize % mp->m_sb.sb_rextsize)) {
code = XFS_ERROR(EINVAL);
goto error_return;
}
}
/*
* Can't modify an immutable/append-only file unless
* we have appropriate permission.
*/
if ((mask & XFS_AT_XFLAGS) &&
(ip->i_d.di_flags &
(XFS_DIFLAG_IMMUTABLE|XFS_DIFLAG_APPEND) ||
(vap->va_xflags &
(XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND))) &&
!capable(CAP_LINUX_IMMUTABLE)) {
code = XFS_ERROR(EPERM);
goto error_return;
}
}
/*
* Now we can make the changes. Before we join the inode
* to the transaction, if XFS_AT_SIZE is set then take care of
* the part of the truncation that must be done without the
* inode lock. This needs to be done before joining the inode
* to the transaction, because the inode cannot be unlocked
* once it is a part of the transaction.
*/
if (mask & XFS_AT_SIZE) {
code = 0;
if ((vap->va_size > ip->i_size) &&
(flags & ATTR_NOSIZETOK) == 0) {
code = xfs_igrow_start(ip, vap->va_size, credp);
}
xfs_iunlock(ip, XFS_ILOCK_EXCL);
/*
* We are going to log the inode size change in this
* transaction so any previous writes that are beyond the on
* disk EOF and the new EOF that have not been written out need
* to be written here. If we do not write the data out, we
* expose ourselves to the null files problem.
*
* Only flush from the on disk size to the smaller of the in
* memory file size or the new size as that's the range we
* really care about here and prevents waiting for other data
* not within the range we care about here.
*/
if (!code &&
(ip->i_size != ip->i_d.di_size) &&
(vap->va_size > ip->i_d.di_size)) {
code = xfs_flush_pages(ip,
ip->i_d.di_size, vap->va_size,
XFS_B_ASYNC, FI_NONE);
}
/* wait for all I/O to complete */
vn_iowait(ip);
if (!code)
code = xfs_itruncate_data(ip, vap->va_size);
if (code) {
ASSERT(tp == NULL);
lock_flags &= ~XFS_ILOCK_EXCL;
ASSERT(lock_flags == XFS_IOLOCK_EXCL);
goto error_return;
}
tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_SIZE);
if ((code = xfs_trans_reserve(tp, 0,
XFS_ITRUNCATE_LOG_RES(mp), 0,
XFS_TRANS_PERM_LOG_RES,
XFS_ITRUNCATE_LOG_COUNT))) {
xfs_trans_cancel(tp, 0);
if (need_iolock)
xfs_iunlock(ip, XFS_IOLOCK_EXCL);
return code;
}
commit_flags = XFS_TRANS_RELEASE_LOG_RES;
xfs_ilock(ip, XFS_ILOCK_EXCL);
}
if (tp) {
xfs_trans_ijoin(tp, ip, lock_flags);
xfs_trans_ihold(tp, ip);
}
/*
* Truncate file. Must have write permission and not be a directory.
*/
if (mask & XFS_AT_SIZE) {
/*
* Only change the c/mtime if we are changing the size
* or we are explicitly asked to change it. This handles
* the semantic difference between truncate() and ftruncate()
* as implemented in the VFS.
*/
if (vap->va_size != ip->i_size || (mask & XFS_AT_CTIME))
timeflags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
if (vap->va_size > ip->i_size) {
xfs_igrow_finish(tp, ip, vap->va_size,
!(flags & ATTR_DMI));
} else if ((vap->va_size <= ip->i_size) ||
((vap->va_size == 0) && ip->i_d.di_nextents)) {
/*
* signal a sync transaction unless
* we're truncating an already unlinked
* file on a wsync filesystem
*/
code = xfs_itruncate_finish(&tp, ip,
(xfs_fsize_t)vap->va_size,
XFS_DATA_FORK,
((ip->i_d.di_nlink != 0 ||
!(mp->m_flags & XFS_MOUNT_WSYNC))
? 1 : 0));
if (code)
goto abort_return;
/*
* Truncated "down", so we're removing references
* to old data here - if we now delay flushing for
* a long time, we expose ourselves unduly to the
* notorious NULL files problem. So, we mark this
* vnode and flush it when the file is closed, and
* do not wait the usual (long) time for writeout.
*/
xfs_iflags_set(ip, XFS_ITRUNCATED);
}
}
/*
* Change file access modes.
*/
if (mask & XFS_AT_MODE) {
ip->i_d.di_mode &= S_IFMT;
ip->i_d.di_mode |= vap->va_mode & ~S_IFMT;
xfs_trans_log_inode (tp, ip, XFS_ILOG_CORE);
timeflags |= XFS_ICHGTIME_CHG;
}
/*
* Change file ownership. Must be the owner or privileged.
* If the system was configured with the "restricted_chown"
* 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 & (XFS_AT_UID|XFS_AT_GID|XFS_AT_PROJID)) {
/*
* CAP_FSETID overrides the following restrictions:
*
* The set-user-ID and set-group-ID bits of a file will be
* cleared upon successful return from chown()
*/
if ((ip->i_d.di_mode & (S_ISUID|S_ISGID)) &&
!capable(CAP_FSETID)) {
ip->i_d.di_mode &= ~(S_ISUID|S_ISGID);
}
/*
* Change the ownerships and register quota modifications
* in the transaction.
*/
if (iuid != uid) {
if (XFS_IS_UQUOTA_ON(mp)) {
ASSERT(mask & XFS_AT_UID);
ASSERT(udqp);
olddquot1 = XFS_QM_DQVOPCHOWN(mp, tp, ip,
&ip->i_udquot, udqp);
}
ip->i_d.di_uid = uid;
}
if (igid != gid) {
if (XFS_IS_GQUOTA_ON(mp)) {
ASSERT(!XFS_IS_PQUOTA_ON(mp));
ASSERT(mask & XFS_AT_GID);
ASSERT(gdqp);
olddquot2 = XFS_QM_DQVOPCHOWN(mp, tp, ip,
&ip->i_gdquot, gdqp);
}
ip->i_d.di_gid = gid;
}
if (iprojid != projid) {
if (XFS_IS_PQUOTA_ON(mp)) {
ASSERT(!XFS_IS_GQUOTA_ON(mp));
ASSERT(mask & XFS_AT_PROJID);
ASSERT(gdqp);
olddquot2 = XFS_QM_DQVOPCHOWN(mp, tp, ip,
&ip->i_gdquot, gdqp);
}
ip->i_d.di_projid = projid;
/*
* We may have to rev the inode as well as
* the superblock version number since projids didn't
* exist before DINODE_VERSION_2 and SB_VERSION_NLINK.
*/
if (ip->i_d.di_version == XFS_DINODE_VERSION_1)
xfs_bump_ino_vers2(tp, ip);
}
xfs_trans_log_inode (tp, ip, XFS_ILOG_CORE);
timeflags |= XFS_ICHGTIME_CHG;
}
/*
* Change file access or modified times.
*/
if (mask & (XFS_AT_ATIME|XFS_AT_MTIME)) {
if (mask & XFS_AT_ATIME) {
ip->i_d.di_atime.t_sec = vap->va_atime.tv_sec;
ip->i_d.di_atime.t_nsec = vap->va_atime.tv_nsec;
ip->i_update_core = 1;
timeflags &= ~XFS_ICHGTIME_ACC;
}
if (mask & XFS_AT_MTIME) {
ip->i_d.di_mtime.t_sec = vap->va_mtime.tv_sec;
ip->i_d.di_mtime.t_nsec = vap->va_mtime.tv_nsec;
timeflags &= ~XFS_ICHGTIME_MOD;
timeflags |= XFS_ICHGTIME_CHG;
}
if (tp && (flags & ATTR_UTIME))
xfs_trans_log_inode (tp, ip, XFS_ILOG_CORE);
}
/*
* Change XFS-added attributes.
*/
if (mask & (XFS_AT_EXTSIZE|XFS_AT_XFLAGS)) {
if (mask & XFS_AT_EXTSIZE) {
/*
* Converting bytes to fs blocks.
*/
ip->i_d.di_extsize = vap->va_extsize >>
mp->m_sb.sb_blocklog;
}
if (mask & XFS_AT_XFLAGS) {
uint di_flags;
/* can't set PREALLOC this way, just preserve it */
di_flags = (ip->i_d.di_flags & XFS_DIFLAG_PREALLOC);
if (vap->va_xflags & XFS_XFLAG_IMMUTABLE)
di_flags |= XFS_DIFLAG_IMMUTABLE;
if (vap->va_xflags & XFS_XFLAG_APPEND)
di_flags |= XFS_DIFLAG_APPEND;
if (vap->va_xflags & XFS_XFLAG_SYNC)
di_flags |= XFS_DIFLAG_SYNC;
if (vap->va_xflags & XFS_XFLAG_NOATIME)
di_flags |= XFS_DIFLAG_NOATIME;
if (vap->va_xflags & XFS_XFLAG_NODUMP)
di_flags |= XFS_DIFLAG_NODUMP;
if (vap->va_xflags & XFS_XFLAG_PROJINHERIT)
di_flags |= XFS_DIFLAG_PROJINHERIT;
if (vap->va_xflags & XFS_XFLAG_NODEFRAG)
di_flags |= XFS_DIFLAG_NODEFRAG;
if (vap->va_xflags & XFS_XFLAG_FILESTREAM)
di_flags |= XFS_DIFLAG_FILESTREAM;
if ((ip->i_d.di_mode & S_IFMT) == S_IFDIR) {
if (vap->va_xflags & XFS_XFLAG_RTINHERIT)
di_flags |= XFS_DIFLAG_RTINHERIT;
if (vap->va_xflags & XFS_XFLAG_NOSYMLINKS)
di_flags |= XFS_DIFLAG_NOSYMLINKS;
if (vap->va_xflags & XFS_XFLAG_EXTSZINHERIT)
di_flags |= XFS_DIFLAG_EXTSZINHERIT;
} else if ((ip->i_d.di_mode & S_IFMT) == S_IFREG) {
if (vap->va_xflags & XFS_XFLAG_REALTIME)
di_flags |= XFS_DIFLAG_REALTIME;
if (vap->va_xflags & XFS_XFLAG_EXTSIZE)
di_flags |= XFS_DIFLAG_EXTSIZE;
}
ip->i_d.di_flags = di_flags;
}
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
timeflags |= XFS_ICHGTIME_CHG;
}
/*
* Change file inode change time only if XFS_AT_CTIME set
* AND we have been called by a DMI function.
*/
if ( (flags & ATTR_DMI) && (mask & XFS_AT_CTIME) ) {
ip->i_d.di_ctime.t_sec = vap->va_ctime.tv_sec;
ip->i_d.di_ctime.t_nsec = vap->va_ctime.tv_nsec;
ip->i_update_core = 1;
timeflags &= ~XFS_ICHGTIME_CHG;
}
/*
* Send out timestamp changes that need to be set to the
* current time. Not done when called by a DMI function.
*/
if (timeflags && !(flags & ATTR_DMI))
xfs_ichgtime(ip, timeflags);
XFS_STATS_INC(xs_ig_attrchg);
/*
* If this is a synchronous mount, make sure that the
* transaction goes to disk before returning to the user.
* This is slightly sub-optimal in that truncates require
* two sync transactions instead of one for wsync filesystems.
* One for the truncate and one for the timestamps since we
* don't want to change the timestamps unless we're sure the
* truncate worked. Truncates are less than 1% of the laddis
* mix so this probably isn't worth the trouble to optimize.
*/
code = 0;
if (tp) {
if (mp->m_flags & XFS_MOUNT_WSYNC)
xfs_trans_set_sync(tp);
code = xfs_trans_commit(tp, commit_flags);
}
xfs_iunlock(ip, lock_flags);
/*
* Release any dquot(s) the inode had kept before chown.
*/
XFS_QM_DQRELE(mp, olddquot1);
XFS_QM_DQRELE(mp, olddquot2);
XFS_QM_DQRELE(mp, udqp);
XFS_QM_DQRELE(mp, gdqp);
if (code) {
return code;
}
if (DM_EVENT_ENABLED(ip, DM_EVENT_ATTRIBUTE) &&
!(flags & ATTR_DMI)) {
(void) XFS_SEND_NAMESP(mp, DM_EVENT_ATTRIBUTE, ip, DM_RIGHT_NULL,
NULL, DM_RIGHT_NULL, NULL, NULL,
0, 0, AT_DELAY_FLAG(flags));
}
return 0;
abort_return:
commit_flags |= XFS_TRANS_ABORT;
/* FALLTHROUGH */
error_return:
XFS_QM_DQRELE(mp, udqp);
XFS_QM_DQRELE(mp, gdqp);
if (tp) {
xfs_trans_cancel(tp, commit_flags);
}
if (lock_flags != 0) {
xfs_iunlock(ip, lock_flags);
}
return code;
}
Generated by GNU enscript 1.6.4.