trunc_indirect took END - OFFSET as the index of the first entry to
free at every level of indirection. That is right for a single
indirect block only; an entry of a double (triple) indirect block
covers addr_per_block (addr_per_block^2) blocks. A truncation to a
size inside those ranges thus started at the wrong entry or freed
nothing, leaving the blocks past the new end allocated. A file that
later grows over them gets their old contents back instead of zeros.
Steps to reproduce (with or without the journal):
sudo settrans -ac /mnt/stress /hurd/ext2fs.static /dev/sd0
sudo dd if=/dev/urandom of=/mnt/stress/f bs=1M count=7
sudo truncate -s 6000000 /mnt/stress/f
sudo settrans -ga /mnt/stress && sudo /sbin/fsck.ext2 -fn /dev/sd0
Inode 16996, i_size is 6000000, should be 7340032. Fix? no
---
ext2fs/truncate.c | 22 +++++++++++++---------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/ext2fs/truncate.c b/ext2fs/truncate.c
index 837fd02c3..082947d32 100644
--- a/ext2fs/truncate.c
+++ b/ext2fs/truncate.c
@@ -111,13 +111,14 @@ trunc_direct (struct node *node, block_t end, struct
free_block_run *fbr)
/* Free any blocks in NODE greater than or equal to END that are rooted in
the indirect block *P; OFFSET should be the block position that *P
- corresponds to. For each block pointer in *P that should be freed,
- FREE_BLOCK is called with a pointer to the entry for that block, and the
- index of the entry within *P. If every block in *P is freed, then *P is
- set to 0, otherwise it is left alone. */
+ corresponds to, and SPAN the number of file blocks each entry of *P
+ covers (1 for a single indirect block). For each block pointer in *P
+ that should be freed, FREE_BLOCK is called with a pointer to the entry
+ for that block, and the index of the entry within *P. If every block in
+ *P is freed, then *P is set to 0, otherwise it is left alone. */
static void
trunc_indirect (struct node *node, block_t end,
- block_t *p, block_t offset,
+ block_t *p, block_t offset, block_t span,
void (*free_block)(block_t *p, unsigned index),
struct free_block_run *fbr)
{
@@ -128,7 +129,9 @@ trunc_indirect (struct node *node, block_t end,
unsigned index;
int modified = 0, all_freed = 1;
block_t *ind_bh = (block_t *) disk_cache_block_ref (*p);
- unsigned first = end < offset ? 0 : end - offset;
+ /* The entry that holds END: it is truncated in part, and the ones
+ after it are freed. */
+ unsigned first = end < offset ? 0 : (end - offset) / span;
for (index = first; index < addr_per_block; index++)
if (ind_bh[index])
@@ -177,7 +180,7 @@ trunc_single_indirect (struct node *node, block_t end,
{
free_block_run_free_ptr (fbr, p);
}
- trunc_indirect (node, end, p, offset, free_block, fbr);
+ trunc_indirect (node, end, p, offset, 1, free_block, fbr);
}
static void
@@ -190,7 +193,7 @@ trunc_double_indirect (struct node *node, block_t end,
block_t entry_offs = offset + (index * addr_per_block);
trunc_single_indirect (node, end, p, entry_offs, fbr);
}
- trunc_indirect (node, end, p, offset, free_block, fbr);
+ trunc_indirect (node, end, p, offset, addr_per_block, free_block, fbr);
}
static void
@@ -203,7 +206,8 @@ trunc_triple_indirect (struct node *node, block_t end,
block_t entry_offs = offset + (index * addr_per_block * addr_per_block);
trunc_double_indirect (node, end, p, entry_offs, fbr);
}
- trunc_indirect (node, end, p, offset, free_block, fbr);
+ trunc_indirect (node, end, p, offset, addr_per_block * addr_per_block,
+ free_block, fbr);
}
/* ---------------------------------------------------------------- */
--
2.56.0