Linux v5.19, modified hugetlbfs fallocate hole punching will be to
zero partial pages. This aligns with with fallocate documentation.
Add new test (fallocate-align-partial) to test this change in
functionality.
tests/Makefile | 6 +-
tests/fallocate_align_partial.c | 225 +++++++++++++++++++++++++++++++
tests/fallocate_align_partial.sh | 16 +++
tests/run_tests.py | 1 +
4 files changed, 246 insertions(+), 2 deletions(-)
create mode 100644 tests/fallocate_align_partial.c
create mode 100755 tests/fallocate_align_partial.sh
diff --git a/tests/Makefile b/tests/Makefile
index 78f7989..e6220ee 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -14,7 +14,8 @@ LIB_TESTS = gethugepagesize test_root find_path unlinked_fd misalign \
mremap-expand-slice-collision \
mremap-fixed-normal-near-huge mremap-fixed-huge-near-normal \
corrupt-by-cow-opt noresv-preserve-resv-page noresv-regarded-as-resv \
- fallocate_basic fallocate_align_whole fallocate_stress
+ fallocate_basic fallocate_align_whole fallocate_align_partial \
+ fallocate_stress
LIB_TESTS_64 =
LIB_TESTS_64_STATIC = straddle_4GB huge_at_4GB_normal_below \
huge_below_4GB_normal_above
@@ -28,7 +29,8 @@ STRESS_TESTS = mmap-gettest mmap-cow shm-gettest shm-getraw shm-fork
WRAPPERS = quota counters madvise_reserve fadvise_reserve \
readahead_reserve mremap-expand-slice-collision \
mremap-fixed-normal-near-huge mremap-fixed-huge-near-normal \
- fallocate_basic fallocate_align_whole fallocate_stress
+ fallocate_basic fallocate_align_whole fallocate_align_partial \
+ fallocate_stress
HELPERS = get_hugetlbfs_path compare_kvers
HELPER_LIBS = libheapshrink.so
BADTOOLCHAIN = bad-toolchain.sh
diff --git a/tests/fallocate_align_partial.c b/tests/fallocate_align_partial.c
new file mode 100644
index 0000000..c6a9941
--- /dev/null
+++ b/tests/fallocate_align_partial.c
@@ -0,0 +1,225 @@
+/*
+ * libhugetlbfs - Easy use of Linux hugepages
+ * Copyright (c) 2015, 2022, Oracle and/or its affiliates.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#define _GNU_SOURCE
+
+#include <linux/falloc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <linux/falloc.h>
+
+#include <hugetlbfs.h>
+
+#include "hugetests.h"
+
+#define P "fallocate-align"
+#define DESC \
+ "* Test alignment of fallocate arguments. fallocate will take *\n"\
+ "* non-huge page aligned offsets and addresses. However, *\n"\
+ "* operations are only performed on huge pages. This is different *\n"\
+ "* that than fallocate behavior in "normal" filesystems. *"
+
+#define FILL_CHAR 'a'
+
+static void write_entire_file(int fd, off_t size)
+{
+ static void *addr;
+ unsigned long i;
+ int err;
+
+ addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ FAIL("mmap(): %s", strerror(errno));
+
+ for (i = 0; i < size; i++)
+ *((char *)(addr + i)) = FILL_CHAR;
+
+ err = munmap(addr, size);
+ if (err)
+ FAIL("munmap(): %s", strerror(errno));
+}
+
+static void verify_hole(int fd, off_t start, off_t length, off_t f_size)
+{
+ static void *addr;
+ unsigned long i;
+ int err;
+ off_t end = start + length;
+
+ addr = mmap(NULL, f_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ FAIL("mmap(): %s", strerror(errno));
+
+ for (i = 0; i < f_size; i++) {
+ if (i < start || i >= end) {
+ if (*((char *)(addr + i)) != FILL_CHAR) {
+ printf("\nUnexpected char outside hole\n");
+ printf(" offset %lx %c != %c\n",
+ i, *((char *)(addr + i)), FILL_CHAR);
+ FAIL("Unexpected char outside hole\n");
+ }
+ } else {
+ if (*((char *)(addr + i)) != 0) {
+ printf("\nUnexpected char in hole\n");
+ printf(" offset %lx %c != %c\n",
+ i, *((char *)(addr + i)), 0);
+ FAIL("\nNon-zero char in hole\n");
+ }
+ }
+ }
+
+ err = munmap(addr, f_size);
+ if (err)
+ FAIL("munmap(): %s", strerror(errno));
+}
+
+int main(int argc, char *argv[])
+{
+ long hpage_size;
+ int fd;
+ int err;
+ unsigned long free_before, free_after;
+
+ test_init(argc, argv);
+
+ hpage_size = check_hugepagesize();
+
+ fd = hugetlbfs_unlinked_fd();
+ if (fd < 0)
+ FAIL("hugetlbfs_unlinked_fd()");
+
+ free_before = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
+
+ /*
+ * First preallocate file with with just 1 byte. Allocation sizes
+ * are rounded up, so we should get an entire huge page.
+ */
+ err = fallocate(fd, 0, 0, 1);
+ if (err) {
+ if (errno == EOPNOTSUPP)
+ IRRELEVANT();
+ if (err)
+ FAIL("fallocate(): %s", strerror(errno));
+ }
+
+ free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
+ if (free_before - free_after != 1)
+ FAIL("fallocate 1 byte did not preallocate entire huge page\n");
+
+ write_entire_file(fd, hpage_size);
+
+ /*
+ * Now punch a hole with just 1 byte. On hole punch, sizes are
+ * rounded down. So, this operation should not create a hole.
+ */
+ err = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ 0, 1);
+ if (err)
+ FAIL("fallocate(FALLOC_FL_PUNCH_HOLE): %s", strerror(errno));
+
+ free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
+ if (free_after == free_before)
+ FAIL("fallocate hole punch 1 byte free'ed a huge page\n");
+
+ verify_hole(fd, 0, 1, hpage_size);
+
+ /* Make sure file has 2 huge pages */
+ err = fallocate(fd, 0, 0, 2 * hpage_size);
+ if (err) {
+ if (errno == EOPNOTSUPP)
+ IRRELEVANT();
+ if (err)
+ FAIL("fallocate(): %s", strerror(errno));
+ }
+ free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
+ if (free_before - free_after != 2)
+ FAIL("fallocate 2 pages did not preallocate pages\n");
+
+ write_entire_file(fd, 2 * hpage_size);
+
+ /*
+ * Now punch a hole with of 2 * hpage_size - 1 byte. This size
+ * should be rounded down to a single huge page and the hole created.
+ */
+ err = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ 0, (2 * hpage_size) - 1);
+ if (err)
+ FAIL("fallocate(FALLOC_FL_PUNCH_HOLE): %s", strerror(errno));
+
+ free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
+ if (free_before - free_after != 1)
+ FAIL("fallocate hole punch 2 * hpage_size - 1 byte did not free huge page\n");
+
+ verify_hole(fd, 0, (2 * hpage_size) - 1, 2 * hpage_size);
+
+ /*
+ * Perform a preallocate operation with offset 1 and size of
+ * hpage_size. The offset should be rounded down and the
+ * size rounded up to preallocate the missing huge page.
+ */
+ err = fallocate(fd, 0, 1, hpage_size);
+ if (err)
+ FAIL("fallocate(): %s", strerror(errno));
+
+ free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
+ if (free_before - free_after != 2)
+ FAIL("fallocate 1 byte offset, huge page size did not preallocate two huge pages\n");
+
+ write_entire_file(fd, 2 * hpage_size);
+
+ /*
+ * The hole punch code will only delete 'whole' huge pags that are
+ * in the specified range. The offset is rounded up, and (offset
+ * + size) is rounded down to determine the huge pages to be deleted.
+ * In this case, after rounding the range is (hpage_size, hpage_size).
+ * So, no pages should be deleted.
+ */
+ err = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ 1, hpage_size);
+ if (err)
+ FAIL("fallocate(FALLOC_FL_PUNCH_HOLE): %s", strerror(errno));
+
+ free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
+ if (free_before - free_after != 2)
+ FAIL("fallocate hole punch 1 byte offset, huge page size incorrectly deleted a huge page\n");
+
+ verify_hole(fd, 1, hpage_size, 2 * hpage_size);
+
+ write_entire_file(fd, 2 * hpage_size);
+
+ /*
+ * To delete both huge pages, the range passed to hole punch must
+ * overlap the allocated pages
+ */
+ err = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ 0, 2 * hpage_size);
+ if (err)
+ FAIL("fallocate(FALLOC_FL_PUNCH_HOLE): %s", strerror(errno));
+
+ free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
+ if (free_after != free_before)
+ FAIL("fallocate hole punch did not delete two huge pages\n");
+
+ verify_hole(fd, 0, 2 * hpage_size, 2 * hpage_size);
+
+ PASS();
+}
diff --git a/tests/fallocate_align_partial.sh b/tests/fallocate_align_partial.sh
new file mode 100755
index 0000000..ec421c6
--- /dev/null
+++ b/tests/fallocate_align_partial.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+. wrapper-utils.sh
+
+#
+# hugetlbfs fallocate support was not available until 4.3
+# Partial page support added in 5.19
+#
+compare_kvers `uname -r` "5.19.0"
+if [ $? -eq 1 ]; then
+ echo "FAIL no fallocate partial page support in kernels before 5.19.0"
+ exit $RC_FAIL
+else
+ EXP_RC=$RC_PASS
+ exec_and_check $EXP_RC fallocate_align_partial "$@"
+fi
diff --git a/tests/run_tests.py b/tests/run_tests.py
index d9626dd..cab89c0 100755
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -559,6 +559,7 @@ def functional_tests():
do_test("misalign")
do_test("fallocate_basic.sh")
do_test("fallocate_align_whole.sh")
+ do_test("fallocate_align_partial.sh")