Re: [PATCH] hugetlb/hugemmap: add hugemmap33 to test hugetlbfs quota accounting

Cyril Hrubis <[email protected]> Tue, 4 Aug 2026 12:04:30 +0200
Newsgroups gmane.linux.ltp
Message-ID <[email protected]>
Hi!
> diff --git a/runtest/hugetlb b/runtest/hugetlb
> index 8ee0e6f82..dbb05a9bc 100644
> --- a/runtest/hugetlb
> +++ b/runtest/hugetlb
> @@ -35,6 +35,7 @@ hugemmap29 hugemmap29
>  hugemmap30 hugemmap30
>  hugemmap31 hugemmap31
>  hugemmap32 hugemmap32
> +hugemmap33 hugemmap33
>  hugemmap34 hugemmap34
>  hugemmap35 hugemmap35
>  hugemmap05_1 hugemmap05 -m
> diff --git a/testcases/kernel/mem/.gitignore b/testcases/kernel/mem/.gitignore
> index 0e59035df..0dd0345c8 100644
> --- a/testcases/kernel/mem/.gitignore
> +++ b/testcases/kernel/mem/.gitignore
> @@ -35,6 +35,7 @@
>  /hugetlb/hugemmap/hugemmap30
>  /hugetlb/hugemmap/hugemmap31
>  /hugetlb/hugemmap/hugemmap32
> +/hugetlb/hugemmap/hugemmap33
>  /hugetlb/hugemmap/hugemmap34
>  /hugetlb/hugemmap/hugemmap35
>  /hugetlb/hugeshmat/hugeshmat01
> diff --git a/testcases/kernel/mem/hugetlb/hugemmap/hugemmap33.c b/testcases/kernel/mem/hugetlb/hugemmap/hugemmap33.c
> new file mode 100644
> index 000000000..f5b2a6335
> --- /dev/null
> +++ b/testcases/kernel/mem/hugetlb/hugemmap/hugemmap33.c
> @@ -0,0 +1,275 @@
> +// SPDX-License-Identifier: LGPL-2.1-or-later
> +/*
> + * Copyright (C) 2005-2007 David Gibson & Adam Litke, IBM Corporation.
> + * Copyright (c) Linux Test Project, 2024
> + * Copyright (C) 2025-2026 Naveed & Pavithra, IBM Corporation.
> + * Assisted with AI tools
> + */
> +
> +/*\
> + * Test hugetlbfs quota accounting with filesystem size limits.
> + *
> + * The number of global huge pages available to a mounted hugetlbfs filesystem
> + * can be limited using a quota mechanism by setting the size attribute at
> + * mount time. Older kernels did not properly handle quota accounting in a
> + * number of cases (e.g., for MAP_PRIVATE pages, and with MAP_SHARED reservation).
> + *
> + * This test replays some scenarios on a privately mounted filesystem with
> + * quota to check for regressions in hugetlbfs quota accounting.
> + *
> + * Root is required to mount the quota-limited hugetlbfs instance.
> + */
> +
> +#define _GNU_SOURCE
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <sys/vfs.h>
> +#include <sys/statfs.h>
> +#include <sys/mount.h>
> +
> +#include "hugetlb.h"
> +
> +#define MNTPOINT "hugetlbfs/"
> +
> +static long hpage_size;
> +static int private_resv;
> +static char quota_mnt[PATH_MAX];
> +static int quota_mounted;
> +
> +/* map action flags */
> +#define ACTION_COW		0x0001
> +#define ACTION_TOUCH		0x0002
> +
> +/* Test result expectations */
> +#define EXPECT_SUCCESS	0
> +#define EXPECT_SIGNAL	1
> +#define EXPECT_FAILURE	2
> +
> +static void verify_quota_stat(long tot, long free, long avail)
> +{
> +	struct statfs s;
> +
> +	SAFE_STATFS(quota_mnt, &s);
> +
> +	if ((long)s.f_blocks != tot || (long)s.f_bfree != free || (long)s.f_bavail != avail) {
> +		tst_res(TFAIL,
> +			"Bad quota counters: total=%li (expected %li), "
> +			"free=%li (expected %li), avail=%li (expected %li)",
> +			(long)s.f_blocks, tot, (long)s.f_bfree, free,
> +			(long)s.f_bavail, avail);
> +	}
> +}
> +
> +static void do_map(unsigned long size, int mmap_flags, int action_flags)
> +{
> +	int fd;
> +	char *a = MAP_FAILED, *b, *c = MAP_FAILED;
> +	char path[PATH_MAX + 32];
> +
> +	snprintf(path, sizeof(path), "%s/test_file_%d", quota_mnt, getpid());
> +	fd = SAFE_OPEN(path, O_CREAT | O_RDWR, 0600);
> +	SAFE_UNLINK(path);
> +
> +	a = mmap(NULL, size, PROT_READ | PROT_WRITE, mmap_flags, fd, 0);
> +	if (a == MAP_FAILED) {
> +		if (errno == ENOMEM || errno == ENOSPC) {
> +			tst_res(TINFO | TERRNO, "mmap failed as expected due to quota");
> +			goto cleanup_fd;
> +		}
> +		tst_brk(TBROK | TERRNO, "mmap failed unexpectedly");
> +	}
> +
> +	if (action_flags & ACTION_TOUCH) {
> +		for (b = a; b < a + size; b += hpage_size)
> +			*b = 1;
> +	}
> +
> +	if (action_flags & ACTION_COW) {
> +		c = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
> +		if (c == MAP_FAILED) {
> +			if (errno == ENOMEM || errno == ENOSPC) {
> +				tst_res(TINFO | TERRNO, "COW mapping failed as expected due to quota");
> +				goto cleanup_a;
> +			}
> +			tst_brk(TBROK | TERRNO, "COW mapping failed unexpectedly");
> +		}
> +
> +		if (*c != 1) {
> +			tst_res(TINFO, "Data mismatch when setting up COW");
> +			goto cleanup_c;
> +		}
> +		*c = 0;
> +		SAFE_MUNMAP(c, size);
> +	}
> +
> +	SAFE_MUNMAP(a, size);
> +	SAFE_CLOSE(fd);
> +	return;
> +
> +cleanup_c:
> +	SAFE_MUNMAP(c, size);
> +cleanup_a:
> +	SAFE_MUNMAP(a, size);
> +cleanup_fd:
> +	SAFE_CLOSE(fd);
> +	exit(1);
> +}
> +
> +static void run_quota_test(int expected_result,
> +			   unsigned long size, int mmap_flags,
> +			   int action_flags)
> +{
> +	pid_t pid;
> +	int status;
> +	int actual_result;
> +
> +	pid = SAFE_FORK();
> +	if (pid == 0) {
> +		do_map(size, mmap_flags, action_flags);
> +		exit(0);
> +	}
> +
> +	SAFE_WAITPID(pid, &status, 0);
> +
> +	if (WIFEXITED(status)) {
> +		if (WEXITSTATUS(status) == 0)
> +			actual_result = EXPECT_SUCCESS;
> +		else
> +			actual_result = EXPECT_FAILURE;

Test results must not be propagated via exit values, PASS/FAIL must be
reported exactly at the spot in the code where it happened.

> +	} else {
> +		actual_result = EXPECT_SIGNAL;
> +	}
> +
> +	if (actual_result != expected_result) {
> +		static const char * const result_names[] = {"success", "signal", "failure"};
> +
> +		tst_res(TFAIL,
> +			"Unexpected result: expected %s, got %s",
> +			result_names[expected_result],
> +			result_names[actual_result]);
> +	} else {
> +		tst_res(TPASS, "Quota test passed as expected");
> +	}
> +}
> +
> +static int kernel_has_private_reservations(void)
> +{
> +	int fd;
> +	long t, f, r, s;
> +	long nt, nf, nr, ns;
> +	void *p;
> +	char path[PATH_MAX];
> +
> +	t = SAFE_READ_MEMINFO(MEMINFO_HPAGE_TOTAL);
> +	f = SAFE_READ_MEMINFO(MEMINFO_HPAGE_FREE);
> +	r = SAFE_READ_MEMINFO(MEMINFO_HPAGE_RSVD);
> +	s = SAFE_READ_MEMINFO(MEMINFO_HPAGE_SURP);
> +
> +	snprintf(path, sizeof(path), "%s/test_priv_resv", MNTPOINT);
> +	fd = SAFE_OPEN(path, O_CREAT | O_RDWR, 0600);
> +	SAFE_UNLINK(path);
> +
> +	p = SAFE_MMAP(NULL, hpage_size, PROT_READ | PROT_WRITE,
> +		      MAP_PRIVATE, fd, 0);
> +
> +	nt = SAFE_READ_MEMINFO(MEMINFO_HPAGE_TOTAL);
> +	nf = SAFE_READ_MEMINFO(MEMINFO_HPAGE_FREE);
> +	nr = SAFE_READ_MEMINFO(MEMINFO_HPAGE_RSVD);
> +	ns = SAFE_READ_MEMINFO(MEMINFO_HPAGE_SURP);
> +
> +	SAFE_MUNMAP(p, hpage_size);
> +	SAFE_CLOSE(fd);
> +
> +	/* Check if reservation was created for private mapping */
> +	if ((nt == t + 1) && (nf == f + 1) && (ns == s + 1) && (nr == r + 1))
> +		return 1;
> +	else if ((nt == t) && (nf == f) && (ns == s)) {
> +		if (nr == r + 1)
> +			return 1;
> +		else if (nr == r)
> +			return 0;
> +	}
> +
> +	tst_brk(TCONF, "Unexpected counter state - "
> +		"T:%li F:%li R:%li S:%li -> T:%li F:%li R:%li S:%li",
> +		t, f, r, s, nt, nf, nr, ns);
> +	return -1;
> +}

Was this changed recently in kernel? Adding a git hash that changed the
kernel behavior into the test description would help.

Also if this was a bug, we have a rule in LTP not to work around kernel
bugs, if kernel is broken it's fine that the test fails.

> +static void run_test(void)
> +{
> +	int bad_priv_resv = private_resv ? EXPECT_FAILURE : EXPECT_SIGNAL;
> +
> +	tst_res(TINFO, "Testing unused quota cleanup for untouched mappings");
> +	run_quota_test(EXPECT_SUCCESS, hpage_size, MAP_PRIVATE, 0);
> +	verify_quota_stat(1, 1, 1);
> +	run_quota_test(EXPECT_SUCCESS, hpage_size, MAP_SHARED, 0);
> +	verify_quota_stat(1, 1, 1);
> +
> +	tst_res(TINFO, "Testing page instantiation within quota limits");
> +	run_quota_test(EXPECT_SUCCESS, hpage_size, MAP_PRIVATE, ACTION_TOUCH);
> +	run_quota_test(EXPECT_SUCCESS, hpage_size, MAP_SHARED, ACTION_TOUCH);
> +
> +	tst_res(TINFO, "Testing page instantiation over quota");
> +	run_quota_test(EXPECT_FAILURE, 2 * hpage_size, MAP_SHARED, ACTION_TOUCH);
> +
> +	tst_res(TINFO, "Testing private mapping quota check");
> +	run_quota_test(bad_priv_resv, 2 * hpage_size, MAP_PRIVATE, ACTION_TOUCH);
> +
> +	tst_res(TINFO, "Testing COW over quota");
> +	run_quota_test(bad_priv_resv, hpage_size, MAP_SHARED,
> +		       ACTION_TOUCH | ACTION_COW);
> +	run_quota_test(bad_priv_resv, hpage_size, MAP_PRIVATE,
> +		       ACTION_TOUCH | ACTION_COW);
> +
> +	tst_res(TINFO, "Testing operations within quota after failures");
> +	run_quota_test(EXPECT_SUCCESS, hpage_size, MAP_SHARED, ACTION_TOUCH);
> +	run_quota_test(EXPECT_SUCCESS, hpage_size, MAP_PRIVATE, ACTION_TOUCH);
> +}
> +
> +static void setup(void)
> +{
> +	char mount_opts[BUFSIZ];
> +
> +	hpage_size = tst_get_hugepage_size();
> +
> +	/* Create a quota-limited hugetlbfs mount */
> +	snprintf(quota_mnt, sizeof(quota_mnt), "%s/quota_test", MNTPOINT);
> +	SAFE_MKDIR(quota_mnt, 0755);
> +
> +	snprintf(mount_opts, sizeof(mount_opts), "size=%luK",
> +		 hpage_size / 1024);
> +
> +	if (mount("none", quota_mnt, "hugetlbfs", 0, mount_opts) == -1) {
> +		if (errno == ENODEV)
> +			tst_brk(TCONF, "hugetlbfs not supported");
> +		tst_brk(TBROK | TERRNO, "mount() failed");
> +	}
> +	quota_mounted = 1;

Since we cannot determine the size= parameter at a compile time (and
hence cannot pass it in tst_test struct) wouldn't it make more sense to
MS_REMOUNT the hugetlbfs already mounted in the test library with the
size parameter? Would that work?

It does seem strange to mount hugetlbfs twice and keep one mount unused.

> +	tst_res(TINFO, "Mounted hugetlbfs with quota at %s (size=%luK)",
> +		quota_mnt, hpage_size / 1024);
> +
> +	private_resv = kernel_has_private_reservations();
> +	tst_res(TINFO, "Kernel %s private reservations",
> +		private_resv ? "has" : "does not have");
> +}
> +
> +static void cleanup(void)
> +{
> +	if (quota_mounted) {
> +		SAFE_UMOUNT(quota_mnt);
> +		SAFE_RMDIR(quota_mnt);
> +	}
> +}
> +
> +static struct tst_test test = {
> +	.needs_root = 1,
> +	.mntpoint = MNTPOINT,
> +	.needs_hugetlbfs = 1,
> +	.forks_child = 1,
> +	.setup = setup,
> +	.cleanup = cleanup,
> +	.test_all = run_test,
> +	.hugepages = {2, TST_NEEDS},
> +};
> -- 
> 2.55.0
> 
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp

-- 
Cyril Hrubis
[email protected]

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp