diff options
Diffstat (limited to 'fs/xfs/xfs_ioctl.c')
-rw-r--r-- | fs/xfs/xfs_ioctl.c | 121 |
1 files changed, 106 insertions, 15 deletions
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 478d04e07..bcb6c19ce 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -114,7 +114,7 @@ xfs_find_handle( handle.ha_fid.fid_len = sizeof(xfs_fid_t) - sizeof(handle.ha_fid.fid_len); handle.ha_fid.fid_pad = 0; - handle.ha_fid.fid_gen = ip->i_d.di_gen; + handle.ha_fid.fid_gen = inode->i_generation; handle.ha_fid.fid_ino = ip->i_ino; hsize = XFS_HSIZE(handle); @@ -963,7 +963,7 @@ xfs_set_diflags( di_flags |= XFS_DIFLAG_NODEFRAG; if (xflags & FS_XFLAG_FILESTREAM) di_flags |= XFS_DIFLAG_FILESTREAM; - if (S_ISDIR(ip->i_d.di_mode)) { + if (S_ISDIR(VFS_I(ip)->i_mode)) { if (xflags & FS_XFLAG_RTINHERIT) di_flags |= XFS_DIFLAG_RTINHERIT; if (xflags & FS_XFLAG_NOSYMLINKS) @@ -972,7 +972,7 @@ xfs_set_diflags( di_flags |= XFS_DIFLAG_EXTSZINHERIT; if (xflags & FS_XFLAG_PROJINHERIT) di_flags |= XFS_DIFLAG_PROJINHERIT; - } else if (S_ISREG(ip->i_d.di_mode)) { + } else if (S_ISREG(VFS_I(ip)->i_mode)) { if (xflags & FS_XFLAG_REALTIME) di_flags |= XFS_DIFLAG_REALTIME; if (xflags & FS_XFLAG_EXTSIZE) @@ -1060,23 +1060,86 @@ xfs_ioctl_setattr_xflags( } /* + * If we are changing DAX flags, we have to ensure the file is clean and any + * cached objects in the address space are invalidated and removed. This + * requires us to lock out other IO and page faults similar to a truncate + * operation. The locks need to be held until the transaction has been committed + * so that the cache invalidation is atomic with respect to the DAX flag + * manipulation. + */ +static int +xfs_ioctl_setattr_dax_invalidate( + struct xfs_inode *ip, + struct fsxattr *fa, + int *join_flags) +{ + struct inode *inode = VFS_I(ip); + int error; + + *join_flags = 0; + + /* + * It is only valid to set the DAX flag on regular files and + * directories on filesystems where the block size is equal to the page + * size. On directories it serves as an inherit hint. + */ + if (fa->fsx_xflags & FS_XFLAG_DAX) { + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) + return -EINVAL; + if (ip->i_mount->m_sb.sb_blocksize != PAGE_SIZE) + return -EINVAL; + } + + /* If the DAX state is not changing, we have nothing to do here. */ + if ((fa->fsx_xflags & FS_XFLAG_DAX) && IS_DAX(inode)) + return 0; + if (!(fa->fsx_xflags & FS_XFLAG_DAX) && !IS_DAX(inode)) + return 0; + + /* lock, flush and invalidate mapping in preparation for flag change */ + xfs_ilock(ip, XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL); + error = filemap_write_and_wait(inode->i_mapping); + if (error) + goto out_unlock; + error = invalidate_inode_pages2(inode->i_mapping); + if (error) + goto out_unlock; + + *join_flags = XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL; + return 0; + +out_unlock: + xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL); + return error; + +} + +/* * Set up the transaction structure for the setattr operation, checking that we * have permission to do so. On success, return a clean transaction and the * inode locked exclusively ready for further operation specific checks. On * failure, return an error without modifying or locking the inode. + * + * The inode might already be IO locked on call. If this is the case, it is + * indicated in @join_flags and we take full responsibility for ensuring they + * are unlocked from now on. Hence if we have an error here, we still have to + * unlock them. Otherwise, once they are joined to the transaction, they will + * be unlocked on commit/cancel. */ static struct xfs_trans * xfs_ioctl_setattr_get_trans( - struct xfs_inode *ip) + struct xfs_inode *ip, + int join_flags) { struct xfs_mount *mp = ip->i_mount; struct xfs_trans *tp; - int error; + int error = -EROFS; if (mp->m_flags & XFS_MOUNT_RDONLY) - return ERR_PTR(-EROFS); + goto out_unlock; + error = -EIO; if (XFS_FORCED_SHUTDOWN(mp)) - return ERR_PTR(-EIO); + goto out_unlock; tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_NOT_SIZE); error = xfs_trans_reserve(tp, &M_RES(mp)->tr_ichange, 0, 0); @@ -1084,7 +1147,8 @@ xfs_ioctl_setattr_get_trans( goto out_cancel; xfs_ilock(ip, XFS_ILOCK_EXCL); - xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL); + xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL | join_flags); + join_flags = 0; /* * CAP_FOWNER overrides the following restrictions: @@ -1104,6 +1168,9 @@ xfs_ioctl_setattr_get_trans( out_cancel: xfs_trans_cancel(tp); +out_unlock: + if (join_flags) + xfs_iunlock(ip, join_flags); return ERR_PTR(error); } @@ -1128,14 +1195,14 @@ xfs_ioctl_setattr_check_extsize( { struct xfs_mount *mp = ip->i_mount; - if ((fa->fsx_xflags & FS_XFLAG_EXTSIZE) && !S_ISREG(ip->i_d.di_mode)) + if ((fa->fsx_xflags & FS_XFLAG_EXTSIZE) && !S_ISREG(VFS_I(ip)->i_mode)) return -EINVAL; if ((fa->fsx_xflags & FS_XFLAG_EXTSZINHERIT) && - !S_ISDIR(ip->i_d.di_mode)) + !S_ISDIR(VFS_I(ip)->i_mode)) return -EINVAL; - if (S_ISREG(ip->i_d.di_mode) && ip->i_d.di_nextents && + if (S_ISREG(VFS_I(ip)->i_mode) && ip->i_d.di_nextents && ((ip->i_d.di_extsize << mp->m_sb.sb_blocklog) != fa->fsx_extsize)) return -EINVAL; @@ -1202,6 +1269,7 @@ xfs_ioctl_setattr( struct xfs_dquot *pdqp = NULL; struct xfs_dquot *olddquot = NULL; int code; + int join_flags = 0; trace_xfs_ioctl_setattr(ip); @@ -1225,7 +1293,18 @@ xfs_ioctl_setattr( return code; } - tp = xfs_ioctl_setattr_get_trans(ip); + /* + * Changing DAX config may require inode locking for mapping + * invalidation. These need to be held all the way to transaction commit + * or cancel time, so need to be passed through to + * xfs_ioctl_setattr_get_trans() so it can apply them to the join call + * appropriately. + */ + code = xfs_ioctl_setattr_dax_invalidate(ip, fa, &join_flags); + if (code) + goto error_free_dquots; + + tp = xfs_ioctl_setattr_get_trans(ip, join_flags); if (IS_ERR(tp)) { code = PTR_ERR(tp); goto error_free_dquots; @@ -1256,9 +1335,9 @@ xfs_ioctl_setattr( * successful return from chown() */ - if ((ip->i_d.di_mode & (S_ISUID|S_ISGID)) && + if ((VFS_I(ip)->i_mode & (S_ISUID|S_ISGID)) && !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID)) - ip->i_d.di_mode &= ~(S_ISUID|S_ISGID); + VFS_I(ip)->i_mode &= ~(S_ISUID|S_ISGID); /* Change the ownerships and register project quota modifications */ if (xfs_get_projid(ip) != fa->fsx_projid) { @@ -1341,6 +1420,7 @@ xfs_ioc_setxflags( struct xfs_trans *tp; struct fsxattr fa; unsigned int flags; + int join_flags = 0; int error; if (copy_from_user(&flags, arg, sizeof(flags))) @@ -1357,7 +1437,18 @@ xfs_ioc_setxflags( if (error) return error; - tp = xfs_ioctl_setattr_get_trans(ip); + /* + * Changing DAX config may require inode locking for mapping + * invalidation. These need to be held all the way to transaction commit + * or cancel time, so need to be passed through to + * xfs_ioctl_setattr_get_trans() so it can apply them to the join call + * appropriately. + */ + error = xfs_ioctl_setattr_dax_invalidate(ip, &fa, &join_flags); + if (error) + goto out_drop_write; + + tp = xfs_ioctl_setattr_get_trans(ip, join_flags); if (IS_ERR(tp)) { error = PTR_ERR(tp); goto out_drop_write; |