xfs: byte range granularity for XFS_IOC_ZERO_RANGE
[cascardo/linux.git] / fs / xfs / xfs_vnodeops.c
index 2688079..d95f565 100644 (file)
@@ -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       end;
        xfs_trans_t     *tp;
        struct iattr    iattr;
-       int             prealloc_type;
 
        if (!S_ISREG(ip->i_d.di_mode))
                return XFS_ERROR(EINVAL);
@@ -2172,31 +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;
-               end = round_down(startoffset + bf->l_len, PAGE_SIZE) - 1;
-               if (startoffset <= end)
-                       truncate_pagecache_range(VFS_I(ip), startoffset, end);
-               /* 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;