[PATCH 0/2] mm/filemap: fix 5.12-rc regressions

From: Hugh Dickins
Date: Wed Apr 21 2021 - 20:35:28 EST


Andrew, I'm very sorry, this is so late: I thought we had already
tested 5.12-rc's mm/filemap changes earlier, but running xfstests
on 32-bit huge tmpfs last weekend revealed a hang (fixed in 1/2);
then looking closer at test results, found SEEK_HOLE/SEEK_DATA
discrepancies that I'd previously assumed benign (surprises there
not surprising when huge pages get used) were in fact indicating
regressions in the new seek_hole_data implementation (fixed in 2/2).

Complicated by xfstests' seek_sanity_test needing some adjustments
to work correctly on huge tmpfs; but not yet submitted because I've
more to do there. seek_sanity combo patch attached, to allow anyone
here to verify the fixes on generic 308 285 286 436 445 448 490 539.

Up to you and Matthew whether these are rushed last minute into
5.12, or held over until the merge window, adding "Cc: stable"s.

1/2 mm/filemap: fix find_lock_entries hang on 32-bit THP
2/2 mm/filemap: fix mapping_seek_hole_data on THP & 32-bit

mm/filemap.c | 33 ++++++++++++++++++++-------------
1 file changed, 20 insertions(+), 13 deletions(-)

Thanks,
Hughxfstests: seek_sanity_test adjustments

Huge tmpfs habitually failed generic/285 seek_sanity_test 11.08 and
12.08 because the near-EOF data was written at an offset of 1MiB into
the x86_64 2MiB huge page allocated for it, so SEEK_DATA then found
an offset 1MiB lower than expected. Work around this by extending
that extra 1MiB at EOF to alloc_size in test11() and test12().

Huge tmpfs on i386 without PAE habitually failed generic/490
seek_sanity_test 20.03 and 20.04: because its 4MiB alloc_size, used
for bufsz, happens to scrape through the initial filsz EFBIG check,
but its overflows fail on those two tests. tmpfs does not use ext[23]
triply indirect blocks anyway, so although it's an interesting test,
just take the easy way out: clamping to 2MiB, which skips test 20.
Surely something cleverer could be done, but it's not worth the math.
And while there, renumber second and third 20.03 to 20.04 and 20.05.

Adjust seek_sanity_test to carry on after its first failure.
Adjust seek_sanity_test to show file offsets in hex not decimal.

Temporarily signed off, but to be split into four when posting to
fstests@xxxxxxxxxxxxxxx; and needs a fifth to fix generic/436 too
(which currently passes because of an old stupidity in mm/shmem.c,
but will probably need adjustment here once the kernel is fixed).

Signed-off-by: Hugh Dickins <hughd@xxxxxxxxxx>
---

src/seek_sanity_test.c | 27 +++++++++++++++++++--------
1 file changed, 19 insertions(+), 8 deletions(-)

--- a/src/seek_sanity_test.c
+++ b/src/seek_sanity_test.c
@@ -207,7 +207,7 @@ static int do_lseek(int testnum, int subtest, int fd, off_t filsz, int origin,
ret = !(errno == ENXIO);
} else {

- x = fprintf(stdout, "%02d.%02d %s expected %lld or %lld, got %lld. ",
+ x = fprintf(stdout, "%02d.%02d %s expected 0x%llx or 0x%llx, got 0x%llx. ",
testnum, subtest,
(origin == SEEK_HOLE) ? "SEEK_HOLE" : "SEEK_DATA",
(long long)exp, (long long)exp2, (long long)pos);
@@ -322,6 +322,9 @@ static int test20(int fd, int testnum)
loff_t bufsz, filsz;

bufsz = alloc_size;
+ /* i386 4MiB bufsz passes filsz EFBIG check but too big for 20.3 20.4 */
+ if (bufsz > 2*1024*1024)
+ bufsz = 2*1024*1024;
buf = do_malloc(bufsz);
if (!buf)
goto out;
@@ -349,9 +352,9 @@ static int test20(int fd, int testnum)
/* Offsets inside ext[23] triply indirect block */
ret += do_lseek(testnum, 3, fd, filsz, SEEK_DATA,
(12 + bufsz / 4 + bufsz / 4 * bufsz / 4 + 3 * bufsz / 4 + 5) * bufsz, filsz - bufsz);
- ret += do_lseek(testnum, 3, fd, filsz, SEEK_DATA,
+ ret += do_lseek(testnum, 4, fd, filsz, SEEK_DATA,
(12 + bufsz / 4 + 7 * bufsz / 4 * bufsz / 4 + 5 * bufsz / 4) * bufsz, filsz - bufsz);
- ret += do_lseek(testnum, 3, fd, filsz, SEEK_DATA,
+ ret += do_lseek(testnum, 5, fd, filsz, SEEK_DATA,
(12 + bufsz / 4 + 8 * bufsz / 4 * bufsz / 4 + bufsz / 4 + 11) * bufsz, filsz - bufsz);
out:
if (buf)
@@ -667,8 +670,13 @@ out:
*/
static int test12(int fd, int testnum)
{
+ blksize_t extra = 1 << 20;
+
+ /* On huge tmpfs (others?) test needs write before EOF to be aligned */
+ if (extra < alloc_size)
+ extra = alloc_size;
return huge_file_test(fd, testnum,
- ((long long)alloc_size << 32) + (1 << 20));
+ ((long long)alloc_size << 32) + extra);
}

/*
@@ -677,8 +685,13 @@ static int test12(int fd, int testnum)
*/
static int test11(int fd, int testnum)
{
+ blksize_t extra = 1 << 20;
+
+ /* On huge tmpfs (others?) test needs write before EOF to be aligned */
+ if (extra < alloc_size)
+ extra = alloc_size;
return huge_file_test(fd, testnum,
- ((long long)alloc_size << 31) + (1 << 20));
+ ((long long)alloc_size << 31) + extra);
}

/* Test an 8G file to check for offset overflows at 1 << 32 */
@@ -1289,9 +1302,7 @@ int main(int argc, char **argv)
for (i = 0; i < numtests; ++i) {
if (seek_tests[i].test_num >= teststart &&
seek_tests[i].test_num <= testend) {
- ret = run_test(&seek_tests[i]);
- if (ret)
- break;
+ ret |= run_test(&seek_tests[i]);
}
}