Merge remote-tracking branch 'asoc/fix/core' into asoc-next
[firefly-linux-kernel-4.4.55.git] / fs / xfs / xfs_iomap.c
index 364818eef40e55720a6a4509afbde63a1b48cd05..5a30dd899d2b32c38dbc1fc552e323d233f33e50 100644 (file)
@@ -310,6 +310,62 @@ xfs_iomap_eof_want_preallocate(
        return 0;
 }
 
+/*
+ * Determine the initial size of the preallocation. We are beyond the current
+ * EOF here, but we need to take into account whether this is a sparse write or
+ * an extending write when determining the preallocation size.  Hence we need to
+ * look up the extent that ends at the current write offset and use the result
+ * to determine the preallocation size.
+ *
+ * If the extent is a hole, then preallocation is essentially disabled.
+ * Otherwise we take the size of the preceeding data extent as the basis for the
+ * preallocation size. If the size of the extent is greater than half the
+ * maximum extent length, then use the current offset as the basis. This ensures
+ * that for large files the preallocation size always extends to MAXEXTLEN
+ * rather than falling short due to things like stripe unit/width alignment of
+ * real extents.
+ */
+STATIC xfs_fsblock_t
+xfs_iomap_eof_prealloc_initial_size(
+       struct xfs_mount        *mp,
+       struct xfs_inode        *ip,
+       xfs_off_t               offset,
+       xfs_bmbt_irec_t         *imap,
+       int                     nimaps)
+{
+       xfs_fileoff_t   start_fsb;
+       int             imaps = 1;
+       int             error;
+
+       ASSERT(nimaps >= imaps);
+
+       /* if we are using a specific prealloc size, return now */
+       if (mp->m_flags & XFS_MOUNT_DFLT_IOSIZE)
+               return 0;
+
+       /*
+        * As we write multiple pages, the offset will always align to the
+        * start of a page and hence point to a hole at EOF. i.e. if the size is
+        * 4096 bytes, we only have one block at FSB 0, but XFS_B_TO_FSB(4096)
+        * will return FSB 1. Hence if there are blocks in the file, we want to
+        * point to the block prior to the EOF block and not the hole that maps
+        * directly at @offset.
+        */
+       start_fsb = XFS_B_TO_FSB(mp, offset);
+       if (start_fsb)
+               start_fsb--;
+       error = xfs_bmapi_read(ip, start_fsb, 1, imap, &imaps, XFS_BMAPI_ENTIRE);
+       if (error)
+               return 0;
+
+       ASSERT(imaps == 1);
+       if (imap[0].br_startblock == HOLESTARTBLOCK)
+               return 0;
+       if (imap[0].br_blockcount <= (MAXEXTLEN >> 1))
+               return imap[0].br_blockcount;
+       return XFS_B_TO_FSB(mp, offset);
+}
+
 /*
  * If we don't have a user specified preallocation size, dynamically increase
  * the preallocation size as the size of the file grows. Cap the maximum size
@@ -319,20 +375,19 @@ xfs_iomap_eof_want_preallocate(
 STATIC xfs_fsblock_t
 xfs_iomap_prealloc_size(
        struct xfs_mount        *mp,
-       struct xfs_inode        *ip)
+       struct xfs_inode        *ip,
+       xfs_off_t               offset,
+       struct xfs_bmbt_irec    *imap,
+       int                     nimaps)
 {
        xfs_fsblock_t           alloc_blocks = 0;
 
-       if (!(mp->m_flags & XFS_MOUNT_DFLT_IOSIZE)) {
+       alloc_blocks = xfs_iomap_eof_prealloc_initial_size(mp, ip, offset,
+                                                          imap, nimaps);
+       if (alloc_blocks > 0) {
                int shift = 0;
                int64_t freesp;
 
-               /*
-                * rounddown_pow_of_two() returns an undefined result
-                * if we pass in alloc_blocks = 0. Hence the "+ 1" to
-                * ensure we always pass in a non-zero value.
-                */
-               alloc_blocks = XFS_B_TO_FSB(mp, XFS_ISIZE(ip)) + 1;
                alloc_blocks = XFS_FILEOFF_MIN(MAXEXTLEN,
                                        rounddown_pow_of_two(alloc_blocks));
 
@@ -358,7 +413,7 @@ xfs_iomap_prealloc_size(
                 * have a large file on a small filesystem and the above
                 * lowspace thresholds are smaller than MAXEXTLEN.
                 */
-               while (alloc_blocks >= freesp)
+               while (alloc_blocks && alloc_blocks >= freesp)
                        alloc_blocks >>= 4;
        }
 
@@ -399,7 +454,6 @@ xfs_iomap_write_delay(
        extsz = xfs_get_extsz_hint(ip);
        offset_fsb = XFS_B_TO_FSBT(mp, offset);
 
-
        error = xfs_iomap_eof_want_preallocate(mp, ip, offset, count,
                                imap, XFS_WRITE_IMAPS, &prealloc);
        if (error)
@@ -407,7 +461,10 @@ xfs_iomap_write_delay(
 
 retry:
        if (prealloc) {
-               xfs_fsblock_t   alloc_blocks = xfs_iomap_prealloc_size(mp, ip);
+               xfs_fsblock_t   alloc_blocks;
+
+               alloc_blocks = xfs_iomap_prealloc_size(mp, ip, offset, imap,
+                                                      XFS_WRITE_IMAPS);
 
                aligned_offset = XFS_WRITEIO_ALIGN(mp, (offset + count - 1));
                ioalign = XFS_B_TO_FSBT(mp, aligned_offset);