[PATCH v5 2/5] tools/lib/mm: add shared file helpers

From: Sarthak Sharma

Date: Thu Jul 16 2026 - 08:33:16 EST


Move read_file(), write_file(), read_num(), and write_num() out of
tools/testing/selftests/mm/vm_util.c into a new shared helper under
tools/lib/mm/.

These helpers are used by mm selftests today and will also be needed by
shared hugepage helpers in subsequent patches. Move them to a generic
location so they can be reused outside selftests as well.

Keep the helpers exposed to mm selftests through vm_util.h by including
the new shared header there, and link the new helper into the
selftests/mm build.

Update the explicit x86 protection_keys 32-bit and 64-bit build rules
to preserve prerequisite paths, now that file_utils.c is built from
tools/lib/mm.

Add tools/lib/mm/ to the MEMORY MANAGEMENT - MISC entry in MAINTAINERS.

Signed-off-by: Sarthak Sharma <sarthak.sharma@xxxxxxx>
---
MAINTAINERS | 1 +
tools/lib/mm/file_utils.c | 119 +++++++++++++++++++++++++++
tools/lib/mm/file_utils.h | 12 +++
tools/testing/selftests/mm/Makefile | 11 +--
tools/testing/selftests/mm/vm_util.c | 109 ------------------------
tools/testing/selftests/mm/vm_util.h | 6 +-
6 files changed, 139 insertions(+), 119 deletions(-)
create mode 100644 tools/lib/mm/file_utils.c
create mode 100644 tools/lib/mm/file_utils.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 806bd2d80d15..26b06d80ab45 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17128,6 +17128,7 @@ F: mm/memory-tiers.c
F: mm/page_idle.c
F: mm/pgalloc-track.h
F: mm/process_vm_access.c
+F: tools/lib/mm/
F: tools/testing/selftests/mm/

MEMORY MANAGEMENT - NUMA MEMBLOCKS AND NUMA EMULATION
diff --git a/tools/lib/mm/file_utils.c b/tools/lib/mm/file_utils.c
new file mode 100644
index 000000000000..792f4b4db625
--- /dev/null
+++ b/tools/lib/mm/file_utils.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "file_utils.h"
+
+int read_file(const char *path, char *buf, size_t buflen)
+{
+ int fd;
+ ssize_t numread;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ int err = errno;
+
+ printf("# %s: %s (%d)\n", path, strerror(err), err);
+ return -err;
+ }
+
+ numread = read(fd, buf, buflen - 1);
+ if (numread < 1) {
+ int err = numread ? errno : ENODATA;
+
+ close(fd);
+ printf("# %s: %s (%d)\n", path, strerror(err), err);
+ return -err;
+ }
+
+ buf[numread] = '\0';
+ close(fd);
+
+ return (int)numread;
+}
+
+int write_file(const char *path, const char *buf, size_t buflen)
+{
+ int fd, saved_errno;
+ ssize_t numwritten;
+
+ if (buflen < 2) {
+ printf("# %s: %s (%d)\n", path, strerror(EINVAL), EINVAL);
+ return -EINVAL;
+ }
+
+ fd = open(path, O_WRONLY);
+ if (fd == -1) {
+ int err = errno;
+
+ printf("# %s: %s (%d)\n", path, strerror(err), err);
+ return -err;
+ }
+
+ numwritten = write(fd, buf, buflen - 1);
+ saved_errno = errno;
+ close(fd);
+ if (numwritten < 0) {
+ printf("# %s: %s (%d)\n", path, strerror(saved_errno),
+ saved_errno);
+ return -saved_errno;
+ }
+ if (numwritten != (ssize_t)(buflen - 1)) {
+ printf("# %s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
+ path, (int)(buflen - 1), buf, buflen - 1, numwritten);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int read_num(const char *path, unsigned long *num)
+{
+ unsigned long val;
+ int err, ret;
+ char buf[21];
+ char *end;
+
+ if (!num)
+ return -EINVAL;
+
+ ret = read_file(path, buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ errno = 0;
+ val = strtoul(buf, &end, 10);
+ if (errno) {
+ err = errno;
+ printf("# %s: %s (%d)\n", path, strerror(err), err);
+ return -err;
+ }
+
+ if (end == buf || buf[0] == '-') {
+ printf("# %s: invalid numeric value\n", path);
+ return -EINVAL;
+ }
+
+ if (*end == '\n')
+ end++;
+
+ if (*end != '\0') {
+ printf("# %s: invalid numeric value\n", path);
+ return -EINVAL;
+ }
+
+ *num = val;
+ return 0;
+}
+
+int write_num(const char *path, unsigned long num)
+{
+ char buf[21];
+
+ snprintf(buf, sizeof(buf), "%lu", num);
+ return write_file(path, buf, strlen(buf) + 1);
+}
diff --git a/tools/lib/mm/file_utils.h b/tools/lib/mm/file_utils.h
new file mode 100644
index 000000000000..38576790a342
--- /dev/null
+++ b/tools/lib/mm/file_utils.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __MM_FILE_UTILS_H__
+#define __MM_FILE_UTILS_H__
+
+#include <stddef.h>
+
+int read_file(const char *path, char *buf, size_t buflen);
+int write_file(const char *path, const char *buf, size_t buflen);
+int read_num(const char *path, unsigned long *num);
+int write_num(const char *path, unsigned long num);
+
+#endif
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index e6df968f0971..70d6a9ccf9c9 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -37,7 +37,8 @@ endif
# LDLIBS.
MAKEFLAGS += --no-builtin-rules

-CFLAGS = -Wall -O2 -I $(top_srcdir) $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
+CFLAGS = -Wall -O2 -I $(top_srcdir) -I $(top_srcdir)/tools/lib
+CFLAGS += $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
CFLAGS += -Wunreachable-code
LDLIBS = -lrt -lpthread -lm

@@ -187,8 +188,8 @@ TEST_FILES += write_hugetlb_memory.sh

include ../lib.mk

-$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c
-$(TEST_GEN_FILES): vm_util.c hugepage_settings.c
+$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c $(top_srcdir)/tools/lib/mm/file_utils.c
+$(TEST_GEN_FILES): vm_util.c hugepage_settings.c $(top_srcdir)/tools/lib/mm/file_utils.c

$(OUTPUT)/uffd-stress: uffd-common.c
$(OUTPUT)/uffd-unit-tests: uffd-common.c
@@ -217,7 +218,7 @@ $(BINARIES_32): CFLAGS += -m32 -mxsave
$(BINARIES_32): LDLIBS += -lrt -ldl -lm
$(BINARIES_32): $(OUTPUT)/%_32: %.c
$(call msg,CC,,$@)
- $(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(notdir $^) $(LDLIBS) -o $@
+ $(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $^ $(LDLIBS) -o $@
$(foreach t,$(VMTARGETS),$(eval $(call gen-target-rule-32,$(t))))
endif

@@ -226,7 +227,7 @@ $(BINARIES_64): CFLAGS += -m64 -mxsave
$(BINARIES_64): LDLIBS += -lrt -ldl
$(BINARIES_64): $(OUTPUT)/%_64: %.c
$(call msg,CC,,$@)
- $(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(notdir $^) $(LDLIBS) -o $@
+ $(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $^ $(LDLIBS) -o $@
$(foreach t,$(VMTARGETS),$(eval $(call gen-target-rule-64,$(t))))
endif

diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index bfe0efbe517e..f276eee57680 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -698,115 +698,6 @@ int unpoison_memory(unsigned long pfn)
return ret > 0 ? 0 : -errno;
}

-int read_file(const char *path, char *buf, size_t buflen)
-{
- int fd;
- ssize_t numread;
-
- fd = open(path, O_RDONLY);
- if (fd == -1) {
- int err = errno;
-
- printf("# %s: %s (%d)\n", path, strerror(err), err);
- return -err;
- }
-
- numread = read(fd, buf, buflen - 1);
- if (numread < 1) {
- int err = numread ? errno : ENODATA;
- close(fd);
- printf("# %s: %s (%d)\n", path, strerror(err), err);
- return -err;
- }
-
- buf[numread] = '\0';
- close(fd);
-
- return (int)numread;
-}
-
-int write_file(const char *path, const char *buf, size_t buflen)
-{
- int fd, saved_errno;
- ssize_t numwritten;
-
- if (buflen < 2) {
- printf("# %s: %s (%d)\n", path, strerror(EINVAL), EINVAL);
- return -EINVAL;
- }
-
- fd = open(path, O_WRONLY);
- if (fd == -1) {
- int err = errno;
-
- printf("# %s: %s (%d)\n", path, strerror(err), err);
- return -err;
- }
-
- numwritten = write(fd, buf, buflen - 1);
- saved_errno = errno;
- close(fd);
- if (numwritten < 0) {
- printf("# %s: %s (%d)\n", path, strerror(saved_errno), saved_errno);
- return -saved_errno;
- }
-
- if (numwritten != (ssize_t)(buflen - 1)) {
- printf("# %s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
- path, (int)(buflen - 1), buf, buflen - 1, numwritten);
- return -EIO;
- }
-
- return 0;
-}
-
-int read_num(const char *path, unsigned long *num)
-{
- unsigned long val;
- int err, ret;
- char buf[21];
- char *end;
-
- if (!num)
- return -EINVAL;
-
- ret = read_file(path, buf, sizeof(buf));
- if (ret < 0)
- return ret;
-
- errno = 0;
- val = strtoul(buf, &end, 10);
- if (errno) {
- err = errno;
- printf("# %s: %s (%d)\n", path, strerror(err), err);
- return -err;
- }
-
- if (end == buf || buf[0] == '-') {
- printf("# %s: invalid numeric value\n", path);
- return -EINVAL;
- }
-
- if (*end == '\n')
- end++;
-
- if (*end != '\0') {
- printf("# %s: invalid numeric value\n", path);
- return -EINVAL;
- }
-
- *num = val;
- return 0;
-}
-
-int write_num(const char *path, unsigned long num)
-{
- char buf[21];
-
- sprintf(buf, "%lu", num);
- return write_file(path, buf, strlen(buf) + 1);
-}
-
static unsigned long shmall, shmmax;

void __shm_limits_restore(void)
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 28c3d7c1faed..d45135283732 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -8,6 +8,7 @@
#include <unistd.h> /* _SC_PAGESIZE */
#include "kselftest.h"
#include <linux/fs.h>
+#include <mm/file_utils.h>

#define BIT_ULL(nr) (1ULL << (nr))
#define PM_SOFT_DIRTY BIT_ULL(55)
@@ -164,11 +165,6 @@ int unpoison_memory(unsigned long pfn);
#define PAGEMAP_PRESENT(ent) (((ent) & (1ull << 63)) != 0)
#define PAGEMAP_PFN(ent) ((ent) & ((1ull << 55) - 1))

-int read_file(const char *path, char *buf, size_t buflen);
-int write_file(const char *path, const char *buf, size_t buflen);
-int read_num(const char *path, unsigned long *num);
-int write_num(const char *path, unsigned long num);
-
void shm_limits_prepare(unsigned long length);
void __shm_limits_restore(void);

--
2.39.5