Merge tag 'virt' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
[cascardo/linux.git] / fs / xfs / xfs_vnodeops.c
index 2a5c637..77ad748 100644 (file)
@@ -47,6 +47,7 @@
 #include "xfs_filestream.h"
 #include "xfs_vnodeops.h"
 #include "xfs_trace.h"
+#include "xfs_icache.h"
 
 /*
  * The maximum pathlen is 1024 bytes. Since the minimum file system
@@ -79,7 +80,7 @@ xfs_readlink_bmap(
                d = XFS_FSB_TO_DADDR(mp, mval[n].br_startblock);
                byte_cnt = XFS_FSB_TO_B(mp, mval[n].br_blockcount);
 
-               bp = xfs_buf_read(mp->m_ddev_targp, d, BTOBB(byte_cnt), 0);
+               bp = xfs_buf_read(mp->m_ddev_targp, d, BTOBB(byte_cnt), 0, NULL);
                if (!bp)
                        return XFS_ERROR(ENOMEM);
                error = bp->b_error;
@@ -150,7 +151,7 @@ xfs_readlink(
  * when the link count isn't zero and by xfs_dm_punch_hole() when
  * punching a hole to EOF.
  */
-STATIC int
+int
 xfs_free_eofblocks(
        xfs_mount_t     *mp,
        xfs_inode_t     *ip,
@@ -199,7 +200,7 @@ xfs_free_eofblocks(
                if (need_iolock) {
                        if (!xfs_ilock_nowait(ip, XFS_IOLOCK_EXCL)) {
                                xfs_trans_cancel(tp, 0);
-                               return 0;
+                               return EAGAIN;
                        }
                }
 
@@ -237,6 +238,8 @@ xfs_free_eofblocks(
                } else {
                        error = xfs_trans_commit(tp,
                                                XFS_TRANS_RELEASE_LOG_RES);
+                       if (!error)
+                               xfs_inode_clear_eofblocks_tag(ip);
                }
 
                xfs_iunlock(ip, XFS_ILOCK_EXCL);
@@ -425,19 +428,18 @@ xfs_release(
                truncated = xfs_iflags_test_and_clear(ip, XFS_ITRUNCATED);
                if (truncated) {
                        xfs_iflags_clear(ip, XFS_IDIRTY_RELEASE);
-                       if (VN_DIRTY(VFS_I(ip)) && ip->i_delayed_blks > 0)
-                               xfs_flush_pages(ip, 0, -1, XBF_ASYNC, FI_NONE);
+                       if (VN_DIRTY(VFS_I(ip)) && ip->i_delayed_blks > 0) {
+                               error = -filemap_flush(VFS_I(ip)->i_mapping);
+                               if (error)
+                                       return error;
+                       }
                }
        }
 
        if (ip->i_d.di_nlink == 0)
                return 0;
 
-       if ((S_ISREG(ip->i_d.di_mode) &&
-            (VFS_I(ip)->i_size > 0 ||
-             (VN_CACHED(VFS_I(ip)) > 0 || ip->i_delayed_blks > 0)) &&
-            (ip->i_df.if_flags & XFS_IFEXTENTS))  &&
-           (!(ip->i_d.di_flags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)))) {
+       if (xfs_can_free_eofblocks(ip, false)) {
 
                /*
                 * If we can't get the iolock just skip truncating the blocks
@@ -464,7 +466,7 @@ xfs_release(
                        return 0;
 
                error = xfs_free_eofblocks(mp, ip, true);
-               if (error)
+               if (error && error != EAGAIN)
                        return error;
 
                /* delalloc blocks after truncation means it really is dirty */
@@ -513,13 +515,12 @@ xfs_inactive(
                goto out;
 
        if (ip->i_d.di_nlink != 0) {
-               if ((S_ISREG(ip->i_d.di_mode) &&
-                   (VFS_I(ip)->i_size > 0 ||
-                    (VN_CACHED(VFS_I(ip)) > 0 || ip->i_delayed_blks > 0)) &&
-                   (ip->i_df.if_flags & XFS_IFEXTENTS) &&
-                   (!(ip->i_d.di_flags &
-                               (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)) ||
-                    ip->i_delayed_blks != 0))) {
+               /*
+                * force is true because we are evicting an inode from the
+                * cache. Post-eof blocks must be freed, lest we end up with
+                * broken free space accounting.
+                */
+               if (xfs_can_free_eofblocks(ip, true)) {
                        error = xfs_free_eofblocks(mp, ip, false);
                        if (error)
                                return VN_INACTIVE_CACHE;
@@ -724,7 +725,7 @@ xfs_create(
        int                     error;
        xfs_bmap_free_t         free_list;
        xfs_fsblock_t           first_block;
-       boolean_t               unlock_dp_on_error = B_FALSE;
+       bool                    unlock_dp_on_error = false;
        uint                    cancel_flags;
        int                     committed;
        prid_t                  prid;
@@ -777,7 +778,7 @@ xfs_create(
                        XFS_TRANS_PERM_LOG_RES, log_count);
        if (error == ENOSPC) {
                /* flush outstanding delalloc blocks and retry */
-               xfs_flush_inodes(dp);
+               xfs_flush_inodes(mp);
                error = xfs_trans_reserve(tp, resblks, log_res, 0,
                                XFS_TRANS_PERM_LOG_RES, log_count);
        }
@@ -793,7 +794,7 @@ xfs_create(
        }
 
        xfs_ilock(dp, XFS_ILOCK_EXCL | XFS_ILOCK_PARENT);
-       unlock_dp_on_error = B_TRUE;
+       unlock_dp_on_error = true;
 
        xfs_bmap_init(&free_list, &first_block);
 
@@ -829,7 +830,7 @@ xfs_create(
         * error path.
         */
        xfs_trans_ijoin(tp, dp, XFS_ILOCK_EXCL);
-       unlock_dp_on_error = B_FALSE;
+       unlock_dp_on_error = false;
 
        error = xfs_dir_createname(tp, dp, name, ip->i_ino,
                                        &first_block, &free_list, resblks ?
@@ -1366,7 +1367,7 @@ xfs_symlink(
        int                     pathlen;
        xfs_bmap_free_t         free_list;
        xfs_fsblock_t           first_block;
-       boolean_t               unlock_dp_on_error = B_FALSE;
+       bool                    unlock_dp_on_error = false;
        uint                    cancel_flags;
        int                     committed;
        xfs_fileoff_t           first_fsb;
@@ -1437,7 +1438,7 @@ xfs_symlink(
        }
 
        xfs_ilock(dp, XFS_ILOCK_EXCL | XFS_ILOCK_PARENT);
-       unlock_dp_on_error = B_TRUE;
+       unlock_dp_on_error = true;
 
        /*
         * Check whether the directory allows new symlinks or not.
@@ -1483,7 +1484,7 @@ xfs_symlink(
         * error path.
         */
        xfs_trans_ijoin(tp, dp, XFS_ILOCK_EXCL);
-       unlock_dp_on_error = B_FALSE;
+       unlock_dp_on_error = false;
 
        /*
         * Also attach the dquot(s) to it, if applicable.
@@ -1957,12 +1958,11 @@ xfs_free_file_space(
 
        rounding = max_t(uint, 1 << mp->m_sb.sb_blocklog, PAGE_CACHE_SIZE);
        ioffset = offset & ~(rounding - 1);
-
-       if (VN_CACHED(VFS_I(ip)) != 0) {
-               error = xfs_flushinval_pages(ip, ioffset, -1, FI_REMAPF_LOCKED);
-               if (error)
-                       goto out_unlock_iolock;
-       }
+       error = -filemap_write_and_wait_range(VFS_I(ip)->i_mapping,
+                                             ioffset, -1);
+       if (error)
+               goto out_unlock_iolock;
+       truncate_pagecache_range(VFS_I(ip), ioffset, -1);
 
        /*
         * Need to zero the stuff we're not freeing, on disk.
@@ -2095,6 +2095,73 @@ xfs_free_file_space(
        return error;
 }
 
+
+STATIC int
+xfs_zero_file_space(
+       struct xfs_inode        *ip,
+       xfs_off_t               offset,
+       xfs_off_t               len,
+       int                     attr_flags)
+{
+       struct xfs_mount        *mp = ip->i_mount;
+       uint                    granularity;
+       xfs_off_t               start_boundary;
+       xfs_off_t               end_boundary;
+       int                     error;
+
+       granularity = max_t(uint, 1 << mp->m_sb.sb_blocklog, PAGE_CACHE_SIZE);
+
+       /*
+        * Round the range of extents we are going to convert inwards.  If the
+        * offset is aligned, then it doesn't get changed so we zero from the
+        * start of the block offset points to.
+        */
+       start_boundary = round_up(offset, granularity);
+       end_boundary = round_down(offset + len, granularity);
+
+       ASSERT(start_boundary >= offset);
+       ASSERT(end_boundary <= offset + len);
+
+       if (!(attr_flags & XFS_ATTR_NOLOCK))
+               xfs_ilock(ip, XFS_IOLOCK_EXCL);
+
+       if (start_boundary < end_boundary - 1) {
+               /* punch out the page cache over the conversion range */
+               truncate_pagecache_range(VFS_I(ip), start_boundary,
+                                        end_boundary - 1);
+               /* convert the blocks */
+               error = xfs_alloc_file_space(ip, start_boundary,
+                                       end_boundary - start_boundary - 1,
+                                       XFS_BMAPI_PREALLOC | XFS_BMAPI_CONVERT,
+                                       attr_flags);
+               if (error)
+                       goto out_unlock;
+
+               /* We've handled the interior of the range, now for the edges */
+               if (start_boundary != offset)
+                       error = xfs_iozero(ip, offset, start_boundary - offset);
+               if (error)
+                       goto out_unlock;
+
+               if (end_boundary != offset + len)
+                       error = xfs_iozero(ip, end_boundary,
+                                          offset + len - end_boundary);
+
+       } else {
+               /*
+                * It's either a sub-granularity range or the range spanned lies
+                * partially across two adjacent blocks.
+                */
+               error = xfs_iozero(ip, offset, len);
+       }
+
+out_unlock:
+       if (!(attr_flags & XFS_ATTR_NOLOCK))
+               xfs_iunlock(ip, XFS_IOLOCK_EXCL);
+       return error;
+
+}
+
 /*
  * xfs_change_file_space()
  *      This routine allocates or frees disk space for the given file.
@@ -2120,10 +2187,8 @@ xfs_change_file_space(
        xfs_fsize_t     fsize;
        int             setprealloc;
        xfs_off_t       startoffset;
-       xfs_off_t       llen;
        xfs_trans_t     *tp;
        struct iattr    iattr;
-       int             prealloc_type;
 
        if (!S_ISREG(ip->i_d.di_mode))
                return XFS_ERROR(EINVAL);
@@ -2141,12 +2206,30 @@ xfs_change_file_space(
                return XFS_ERROR(EINVAL);
        }
 
-       llen = bf->l_len > 0 ? bf->l_len - 1 : bf->l_len;
+       /*
+        * length of <= 0 for resv/unresv/zero is invalid.  length for
+        * alloc/free is ignored completely and we have no idea what userspace
+        * might have set it to, so set it to zero to allow range
+        * checks to pass.
+        */
+       switch (cmd) {
+       case XFS_IOC_ZERO_RANGE:
+       case XFS_IOC_RESVSP:
+       case XFS_IOC_RESVSP64:
+       case XFS_IOC_UNRESVSP:
+       case XFS_IOC_UNRESVSP64:
+               if (bf->l_len <= 0)
+                       return XFS_ERROR(EINVAL);
+               break;
+       default:
+               bf->l_len = 0;
+               break;
+       }
 
        if (bf->l_start < 0 ||
            bf->l_start > mp->m_super->s_maxbytes ||
-           bf->l_start + llen < 0 ||
-           bf->l_start + llen > mp->m_super->s_maxbytes)
+           bf->l_start + bf->l_len < 0 ||
+           bf->l_start + bf->l_len >= mp->m_super->s_maxbytes)
                return XFS_ERROR(EINVAL);
 
        bf->l_whence = 0;
@@ -2154,29 +2237,20 @@ xfs_change_file_space(
        startoffset = bf->l_start;
        fsize = XFS_ISIZE(ip);
 
-       /*
-        * XFS_IOC_RESVSP and XFS_IOC_UNRESVSP will reserve or unreserve
-        * file space.
-        * These calls do NOT zero the data space allocated to the file,
-        * nor do they change the file size.
-        *
-        * XFS_IOC_ALLOCSP and XFS_IOC_FREESP will allocate and free file
-        * space.
-        * These calls cause the new file data to be zeroed and the file
-        * size to be changed.
-        */
        setprealloc = clrprealloc = 0;
-       prealloc_type = XFS_BMAPI_PREALLOC;
-
        switch (cmd) {
        case XFS_IOC_ZERO_RANGE:
-               prealloc_type |= XFS_BMAPI_CONVERT;
-               xfs_tosspages(ip, startoffset, startoffset + bf->l_len, 0);
-               /* FALLTHRU */
+               error = xfs_zero_file_space(ip, startoffset, bf->l_len,
+                                               attr_flags);
+               if (error)
+                       return error;
+               setprealloc = 1;
+               break;
+
        case XFS_IOC_RESVSP:
        case XFS_IOC_RESVSP64:
                error = xfs_alloc_file_space(ip, startoffset, bf->l_len,
-                                               prealloc_type, attr_flags);
+                                               XFS_BMAPI_PREALLOC, attr_flags);
                if (error)
                        return error;
                setprealloc = 1;