Skip to content

Commit 6f30b7e

Browse files
osandovtytso
authored andcommitted
ext4: fix indirect punch hole corruption
Commit 4f579ae (ext4: fix punch hole on files with indirect mapping) rewrote FALLOC_FL_PUNCH_HOLE for ext4 files with indirect mapping. However, there are bugs in several corner cases. This fixes 5 distinct bugs: 1. When there is at least one entire level of indirection between the start and end of the punch range and the end of the punch range is the first block of its level, we can't return early; we have to free the intervening levels. 2. When the end is at a higher level of indirection than the start and ext4_find_shared returns a top branch for the end, we still need to free the rest of the shared branch it returns; we can't decrement partial2. 3. When a punch happens within one level of indirection, we need to converge on an indirect block that contains the start and end. However, because the branches returned from ext4_find_shared do not necessarily start at the same level (e.g., the partial2 chain will be shallower if the last block occurs at the beginning of an indirect group), the walk of the two chains can end up "missing" each other and freeing a bunch of extra blocks in the process. This mismatch can be handled by first making sure that the chains are at the same level, then walking them together until they converge. 4. When the punch happens within one level of indirection and ext4_find_shared returns a top branch for the start, we must free it, but only if the end does not occur within that branch. 5. When the punch happens within one level of indirection and ext4_find_shared returns a top branch for the end, then we shouldn't free the block referenced by the end of the returned chain (this mirrors the different levels case). Signed-off-by: Omar Sandoval <osandov@osandov.com>
1 parent 2d5b86e commit 6f30b7e

File tree

1 file changed

+71
-34
lines changed

1 file changed

+71
-34
lines changed

fs/ext4/indirect.c

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,10 +1393,7 @@ int ext4_ind_remove_space(handle_t *handle, struct inode *inode,
13931393
* to free. Everything was covered by the start
13941394
* of the range.
13951395
*/
1396-
return 0;
1397-
} else {
1398-
/* Shared branch grows from an indirect block */
1399-
partial2--;
1396+
goto do_indirects;
14001397
}
14011398
} else {
14021399
/*
@@ -1427,56 +1424,96 @@ int ext4_ind_remove_space(handle_t *handle, struct inode *inode,
14271424
/* Punch happened within the same level (n == n2) */
14281425
partial = ext4_find_shared(inode, n, offsets, chain, &nr);
14291426
partial2 = ext4_find_shared(inode, n2, offsets2, chain2, &nr2);
1430-
/*
1431-
* ext4_find_shared returns Indirect structure which
1432-
* points to the last element which should not be
1433-
* removed by truncate. But this is end of the range
1434-
* in punch_hole so we need to point to the next element
1435-
*/
1436-
partial2->p++;
1437-
while ((partial > chain) || (partial2 > chain2)) {
1438-
/* We're at the same block, so we're almost finished */
1439-
if ((partial->bh && partial2->bh) &&
1440-
(partial->bh->b_blocknr == partial2->bh->b_blocknr)) {
1441-
if ((partial > chain) && (partial2 > chain2)) {
1427+
1428+
/* Free top, but only if partial2 isn't its subtree. */
1429+
if (nr) {
1430+
int level = min(partial - chain, partial2 - chain2);
1431+
int i;
1432+
int subtree = 1;
1433+
1434+
for (i = 0; i <= level; i++) {
1435+
if (offsets[i] != offsets2[i]) {
1436+
subtree = 0;
1437+
break;
1438+
}
1439+
}
1440+
1441+
if (!subtree) {
1442+
if (partial == chain) {
1443+
/* Shared branch grows from the inode */
1444+
ext4_free_branches(handle, inode, NULL,
1445+
&nr, &nr+1,
1446+
(chain+n-1) - partial);
1447+
*partial->p = 0;
1448+
} else {
1449+
/* Shared branch grows from an indirect block */
1450+
BUFFER_TRACE(partial->bh, "get_write_access");
14421451
ext4_free_branches(handle, inode, partial->bh,
1443-
partial->p + 1,
1444-
partial2->p,
1452+
partial->p,
1453+
partial->p+1,
14451454
(chain+n-1) - partial);
1446-
BUFFER_TRACE(partial->bh, "call brelse");
1447-
brelse(partial->bh);
1448-
BUFFER_TRACE(partial2->bh, "call brelse");
1449-
brelse(partial2->bh);
14501455
}
1451-
return 0;
14521456
}
1457+
}
1458+
1459+
if (!nr2) {
14531460
/*
1454-
* Clear the ends of indirect blocks on the shared branch
1455-
* at the start of the range
1461+
* ext4_find_shared returns Indirect structure which
1462+
* points to the last element which should not be
1463+
* removed by truncate. But this is end of the range
1464+
* in punch_hole so we need to point to the next element
14561465
*/
1457-
if (partial > chain) {
1466+
partial2->p++;
1467+
}
1468+
1469+
while (partial > chain || partial2 > chain2) {
1470+
int depth = (chain+n-1) - partial;
1471+
int depth2 = (chain2+n2-1) - partial2;
1472+
1473+
if (partial > chain && partial2 > chain2 &&
1474+
partial->bh->b_blocknr == partial2->bh->b_blocknr) {
1475+
/*
1476+
* We've converged on the same block. Clear the range,
1477+
* then we're done.
1478+
*/
14581479
ext4_free_branches(handle, inode, partial->bh,
1459-
partial->p + 1,
1460-
(__le32 *)partial->bh->b_data+addr_per_block,
1461-
(chain+n-1) - partial);
1480+
partial->p + 1,
1481+
partial2->p,
1482+
(chain+n-1) - partial);
14621483
BUFFER_TRACE(partial->bh, "call brelse");
14631484
brelse(partial->bh);
1464-
partial--;
1485+
BUFFER_TRACE(partial2->bh, "call brelse");
1486+
brelse(partial2->bh);
1487+
return 0;
14651488
}
1489+
14661490
/*
1467-
* Clear the ends of indirect blocks on the shared branch
1468-
* at the end of the range
1491+
* The start and end partial branches may not be at the same
1492+
* level even though the punch happened within one level. So, we
1493+
* give them a chance to arrive at the same level, then walk
1494+
* them in step with each other until we converge on the same
1495+
* block.
14691496
*/
1470-
if (partial2 > chain2) {
1497+
if (partial > chain && depth <= depth2) {
1498+
ext4_free_branches(handle, inode, partial->bh,
1499+
partial->p + 1,
1500+
(__le32 *)partial->bh->b_data+addr_per_block,
1501+
(chain+n-1) - partial);
1502+
BUFFER_TRACE(partial->bh, "call brelse");
1503+
brelse(partial->bh);
1504+
partial--;
1505+
}
1506+
if (partial2 > chain2 && depth2 <= depth) {
14711507
ext4_free_branches(handle, inode, partial2->bh,
14721508
(__le32 *)partial2->bh->b_data,
14731509
partial2->p,
1474-
(chain2+n-1) - partial2);
1510+
(chain2+n2-1) - partial2);
14751511
BUFFER_TRACE(partial2->bh, "call brelse");
14761512
brelse(partial2->bh);
14771513
partial2--;
14781514
}
14791515
}
1516+
return 0;
14801517

14811518
do_indirects:
14821519
/* Kill the remaining (whole) subtrees */

0 commit comments

Comments
 (0)