]> www.infradead.org Git - users/hch/xfsprogs.git/commitdiff
xfs: fix realtime file data space leak
authorOmar Sandoval <osandov@fb.com>
Wed, 22 Jan 2020 16:29:46 +0000 (11:29 -0500)
committerEric Sandeen <sandeen@redhat.com>
Wed, 22 Jan 2020 16:29:46 +0000 (11:29 -0500)
Source kernel commit: 0c4da70c83d41a8461fdf50a3f7b292ecb04e378

Realtime files in XFS allocate extents in rextsize units. However, the
written/unwritten state of those extents is still tracked in blocksize
units. Therefore, a realtime file can be split up into written and
unwritten extents that are not necessarily aligned to the realtime
extent size. __xfs_bunmapi() has some logic to handle these various
corner cases. Consider how it handles the following case:

1. The last extent is unwritten.
2. The last extent is smaller than the realtime extent size.
3. startblock of the last extent is not aligned to the realtime extent
size, but startblock + blockcount is.

In this case, __xfs_bunmapi() calls xfs_bmap_add_extent_unwritten_real()
to set the second-to-last extent to unwritten. This should merge the
last and second-to-last extents, so __xfs_bunmapi() moves on to the
second-to-last extent.

However, if the size of the last and second-to-last extents combined is
greater than MAXEXTLEN, xfs_bmap_add_extent_unwritten_real() does not
merge the two extents. When that happens, __xfs_bunmapi() skips past the
last extent without unmapping it, thus leaking the space.

Fix it by only unwriting the minimum amount needed to align the last
extent to the realtime extent size, which is guaranteed to merge with
the last extent.

Signed-off-by: Omar Sandoval <osandov@fb.com>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Eric Sandeen <sandeen@sandeen.net>
include/platform_defs.h.in
libxfs/xfs_bmap.c

index adb0018132b2fcb061e12e03cec74eeaadc88118..1f7ceafb1fbc75c93b277a22005e3dbd19cce194 100644 (file)
@@ -65,6 +65,7 @@ typedef unsigned short umode_t;
 #define min(a,b)       (((a)<(b))?(a):(b))
 #define max(a,b)       (((a)>(b))?(a):(b))
 #endif
+#define max3(a,b,c)    max(max(a, b), c)
 
 /* If param.h doesn't provide it, i.e. for Android */
 #ifndef NBBY
index 4e70561d91b8e3f451108df548e2173ed62fcc9b..f961f8aa4d588b1a5d17f54ac1ee25fec79f4637 100644 (file)
@@ -5472,16 +5472,17 @@ __xfs_bunmapi(
                }
                div_u64_rem(del.br_startblock, mp->m_sb.sb_rextsize, &mod);
                if (mod) {
+                       xfs_extlen_t off = mp->m_sb.sb_rextsize - mod;
+
                        /*
                         * Realtime extent is lined up at the end but not
                         * at the front.  We'll get rid of full extents if
                         * we can.
                         */
-                       mod = mp->m_sb.sb_rextsize - mod;
-                       if (del.br_blockcount > mod) {
-                               del.br_blockcount -= mod;
-                               del.br_startoff += mod;
-                               del.br_startblock += mod;
+                       if (del.br_blockcount > off) {
+                               del.br_blockcount -= off;
+                               del.br_startoff += off;
+                               del.br_startblock += off;
                        } else if (del.br_startoff == start &&
                                   (del.br_state == XFS_EXT_UNWRITTEN ||
                                    tp->t_blk_res == 0)) {
@@ -5499,6 +5500,7 @@ __xfs_bunmapi(
                                continue;
                        } else if (del.br_state == XFS_EXT_UNWRITTEN) {
                                struct xfs_bmbt_irec    prev;
+                               xfs_fileoff_t           unwrite_start;
 
                                /*
                                 * This one is already unwritten.
@@ -5512,12 +5514,13 @@ __xfs_bunmapi(
                                ASSERT(!isnullstartblock(prev.br_startblock));
                                ASSERT(del.br_startblock ==
                                       prev.br_startblock + prev.br_blockcount);
-                               if (prev.br_startoff < start) {
-                                       mod = start - prev.br_startoff;
-                                       prev.br_blockcount -= mod;
-                                       prev.br_startblock += mod;
-                                       prev.br_startoff = start;
-                               }
+                               unwrite_start = max3(start,
+                                                    del.br_startoff - mod,
+                                                    prev.br_startoff);
+                               mod = unwrite_start - prev.br_startoff;
+                               prev.br_startoff = unwrite_start;
+                               prev.br_startblock += mod;
+                               prev.br_blockcount -= mod;
                                prev.br_state = XFS_EXT_UNWRITTEN;
                                error = xfs_bmap_add_extent_unwritten_real(tp,
                                                ip, whichfork, &icur, &cur,