From 30b3853ab3b54e22edf3372fd6aaf35d52d667e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 18 Aug 2026 18:33:02 +0200 Subject: [PATCH 01/28] selftests/nolibc: stop treating skipped tests as warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nolibc testsuite will always have some tests which get skipped. Due to this the full testsuite is always marked as 'warning', even though all testcases succeeded as expected. Stop treating skipped tests as warning. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260818-nolibc-test-result-warning-v1-1-d15cdda84ccd@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index f70c8dfca018..c790f937fe0a 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -225,7 +225,7 @@ include $(srctree)/tools/scripts/Makefile.include REPORT ?= awk '/\[OK\][\r]*$$/{p++} /\[FAIL\][\r]*$$/{if (!f) printf("\n"); f++; print;} /\[SKIPPED\][\r]*$$/{s++} \ /^Total number of errors:/{done++} \ END{ printf("\n%3d test(s): %3d passed, %3d skipped, %3d failed => status: ", p+s+f, p, s, f); \ - if (f || !p || !done) printf("failure\n"); else if (s) printf("warning\n"); else printf("success\n");; \ + if (f || !p || !done) printf("failure\n"); else printf("success\n");; \ printf("\nSee all results in %s\n", ARGV[1]); }' # Execute the toplevel kernel Makefile From 836cecd18332a3152f09e79b02e613b3982c2e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 08:31:51 +0200 Subject: [PATCH 02/28] tools/nolibc: split the architecture list into multiple lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list is getting long, split it up for easier additions. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Reviewed-by: Brian Cain Link: https://patch.msgid.link/20260819-nolibc-hexagon-v1-1-6bc3be591f09@weissschuh.net --- tools/include/nolibc/Makefile | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index 6d213d372bf8..b5897b3964f8 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -17,7 +17,22 @@ endif # it defaults to this nolibc directory. OUTPUT ?= $(CURDIR)/ -architectures := alpha arm arm64 loongarch m68k mips openrisc parisc powerpc riscv s390 sh sparc x86 +architectures := \ + alpha \ + arm \ + arm64 \ + loongarch \ + m68k \ + mips \ + openrisc \ + parisc \ + powerpc \ + riscv \ + s390 \ + sh \ + sparc \ + x86 \ + arch_files := arch.h $(addsuffix .h, $(addprefix arch-, $(architectures))) all_files := \ alloca.h \ From 4b4f56583850ba13f576f1e34f1b9e217c003773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 08:31:52 +0200 Subject: [PATCH 03/28] selftests/nolibc: prepare for clang-only architectures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contrary to other architectures currently supported by nolibc, hexagon can only be built with clang. Prepare run-tests.sh to allow for this. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Reviewed-by: Brian Cain Link: https://patch.msgid.link/20260819-nolibc-hexagon-v1-2-6bc3be591f09@weissschuh.net --- tools/testing/selftests/nolibc/run-tests.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index dc0b1649c641..5732b956ead9 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -132,6 +132,12 @@ crosstool_abi() { esac } +need_gcc() { + case "$1" in + *) echo "1";; + esac +} + download_crosstool() { arch="$(crosstool_arch "$1")" abi="$(crosstool_abi "$1")" @@ -164,14 +170,18 @@ test_arch() { arch=$1 ct_arch=$(crosstool_arch "$arch") ct_abi=$(crosstool_abi "$1") + gcc="$(need_gcc "$1")" + cross_compile= - if [ ! -d "${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}/bin/." ]; then + if [ -n "$gcc" ] && [ ! -d "${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}/bin/." ]; then echo "No toolchain found in ${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}." echo "Did you install the toolchains or set the correct arch ? Rerun with -h for help." return 1 fi - cross_compile=$(realpath "${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}/bin/${ct_arch}-${ct_abi}-") + if [ -n "$gcc" ]; then + cross_compile=$(realpath "${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}/bin/${ct_arch}-${ct_abi}-") + fi build_dir="${build_location}/${arch}" if [ "$werror" -ne 0 ]; then CFLAGS_EXTRA="$CFLAGS_EXTRA -Werror -Wl,--fatal-warnings" From 3842a1f486a05c12b2a8712546858e6872431292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 08:31:53 +0200 Subject: [PATCH 04/28] tools/nolibc: add support for hexagon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A mostly straightforward new architecture with a few quirks: * Only qemu-user is supported for testing. * Clang is required for compilation. * -fsanitize=undefined is broken. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Reviewed-by: Brian Cain Link: https://patch.msgid.link/20260819-nolibc-hexagon-v1-3-6bc3be591f09@weissschuh.net --- tools/include/nolibc/Makefile | 1 + tools/include/nolibc/arch-hexagon.h | 164 ++++++++++++++++++ tools/include/nolibc/arch.h | 2 + .../testing/selftests/nolibc/Makefile.nolibc | 8 + tools/testing/selftests/nolibc/run-tests.sh | 6 + 5 files changed, 181 insertions(+) create mode 100644 tools/include/nolibc/arch-hexagon.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index b5897b3964f8..c4a0879e4140 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -21,6 +21,7 @@ architectures := \ alpha \ arm \ arm64 \ + hexagon \ loongarch \ m68k \ mips \ diff --git a/tools/include/nolibc/arch-hexagon.h b/tools/include/nolibc/arch-hexagon.h new file mode 100644 index 000000000000..0767054882f3 --- /dev/null +++ b/tools/include/nolibc/arch-hexagon.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * hexagon specific definitions for NOLIBC + * Copyright (C) 2026 Thomas Weißschuh + */ + +#ifndef _NOLIBC_ARCH_HEXAGON_H +#define _NOLIBC_ARCH_HEXAGON_H + +#include + +#include "compiler.h" +#include "crt.h" + +/* + * Syscalls for hexagon: + * - syscall number is passed in r6 + * - arguments are in r0, r1, r2, r3, r4, r5 + * - the system call is performed by calling trap0(#1) + * - syscall return value is in r0 + */ + +#define _NOLIBC_SYSCALL_CLOBBERLIST "memory" + +#define __nolibc_syscall0(num) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0"); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "=r"(_arg1) \ + : "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall1(num, arg1) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall2(num, arg1, arg2) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall3(num, arg1, arg2, arg3) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall4(num, arg1, arg2, arg3, arg4) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + register long _arg4 __asm__ ("r3") = (long)(arg4); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + register long _arg4 __asm__ ("r3") = (long)(arg4); \ + register long _arg5 __asm__ ("r4") = (long)(arg5); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + register long _arg4 __asm__ ("r3") = (long)(arg4); \ + register long _arg5 __asm__ ("r4") = (long)(arg5); \ + register long _arg6 __asm__ ("r5") = (long)(arg6); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), "r"(_arg6), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#ifndef NOLIBC_NO_RUNTIME +/* startup code */ +void __attribute__((weak, noreturn)) +__nolibc_entrypoint __nolibc_no_stack_protector +_start(void) +{ + __asm__ volatile ( + "r0 = sp\n" /* save stack pointer to r0, as arg1 of _start_c */ + "call _start_c\n" /* transfer to c runtime */ + ); + __nolibc_entrypoint_epilogue(); +} +#endif /* NOLIBC_NO_RUNTIME */ + +static __attribute__((unused)) +int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) +{ + return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); +} +#define _sys_ftruncate64 _sys_ftruncate64 + +#endif /* _NOLIBC_ARCH_HEXAGON_H */ diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index 06cc2dbe7a33..24e0ec4fb2db 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -34,6 +34,8 @@ #include "arch-parisc.h" #elif defined(__alpha__) #include "arch-alpha.h" +#elif defined(__hexagon__) +#include "arch-hexagon.h" #else #error Unsupported Architecture #endif diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index c790f937fe0a..d8b71cbfc173 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -206,6 +206,8 @@ CFLAGS_mips64be = -EB -mabi=64 -march=mips64r2 CFLAGS_loongarch = $(if $(LLVM),-fuse-ld=lld) CFLAGS_sparc32 = $(call cc-option,-m32) -mcpu=v8 CFLAGS_sh4 = -ml -m4 +# github.com/llvm/llvm-project/issues/216765 +CFLAGS_hexagon = -fno-sanitize=undefined ifeq ($(origin XARCH),command line) CFLAGS_XARCH = $(CFLAGS_$(XARCH)) endif @@ -222,6 +224,12 @@ LDFLAGS := # Modify CFLAGS based on LLVM= include $(srctree)/tools/scripts/Makefile.include +ifeq ($(ARCH),hexagon) +# tools/scripts/Makefile.include requires CROSS_COMPILE which does not exist for hexagon +CFLAGS += --target=hexagon-linux-musl +CLANG_CROSS_FLAGS += --target=hexagon-linux-musl +endif + REPORT ?= awk '/\[OK\][\r]*$$/{p++} /\[FAIL\][\r]*$$/{if (!f) printf("\n"); f++; print;} /\[SKIPPED\][\r]*$$/{s++} \ /^Total number of errors:/{done++} \ END{ printf("\n%3d test(s): %3d passed, %3d skipped, %3d failed => status: ", p+s+f, p, s, f); \ diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index 5732b956ead9..3da806e2b302 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -31,6 +31,7 @@ all_archs=( sh4 parisc32 alpha + hexagon ) archs="${all_archs[@]}" @@ -134,6 +135,7 @@ crosstool_abi() { need_gcc() { case "$1" in + hexagon);; *) echo "1";; esac } @@ -212,6 +214,10 @@ test_arch() { echo "Unsupported configuration" return fi + if [ "$arch" = "hexagon" ] && [ -z "$llvm" -o "$test_mode" = "system" ]; then + echo "Unsupported configuration" + return + fi mkdir -p "$build_dir" swallow_output "${MAKE[@]}" defconfig From 4c00540add56942d621a941481cf9d0fb7dcec0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 25 Aug 2026 22:41:33 +0200 Subject: [PATCH 05/28] selftests/nolibc: switch around the skip conditions in run-tests.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the potentially long list of affected architectures to the end of the conditional to make the logic easier to understand. Suggested-by: David Laight Link: https://lore.kernel.org/lkml/20260824091958.7810094f@pumpkin/ Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/run-tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index 3da806e2b302..af2a6f510d1d 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -206,15 +206,15 @@ test_arch() { exit 1 esac printf '%-15s' "$arch:" - if [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" -o "$arch" = "alpha" ] && [ "$llvm" = "1" ]; then + if [ "$llvm" = "1" ] && [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" -o "$arch" = "alpha" ]; then echo "Unsupported configuration" return fi - if [ "$arch" = "x32" ] && [ "$test_mode" = "user" ]; then + if [ "$test_mode" = "user" ] && [ "$arch" = "x32" ]; then echo "Unsupported configuration" return fi - if [ "$arch" = "hexagon" ] && [ -z "$llvm" -o "$test_mode" = "system" ]; then + if [ -z "$llvm" -o "$test_mode" = "system" ] && [ "$arch" = "hexagon" ]; then echo "Unsupported configuration" return fi From c0da8b53603ad84f622b2c5f6c1a69f7d4b4b8bc Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 28 Aug 2026 19:32:04 +0900 Subject: [PATCH 06/28] tools/nolibc: Add sendfile() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is useful because it allows copying data up to 2GB from one fd to another without a buffer in userspace and with a single syscall. For 32bit kernels we use the sendfile64 syscall to allow for 64bit offsets to work. Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260828103205.384589-2-daniel@thingy.jp Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/Makefile | 1 + tools/include/nolibc/nolibc.h | 1 + tools/include/nolibc/sys/sendfile.h | 41 +++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 tools/include/nolibc/sys/sendfile.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index c4a0879e4140..0880be0f6ef5 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -75,6 +75,7 @@ all_files := \ sys/reboot.h \ sys/resource.h \ sys/select.h \ + sys/sendfile.h \ sys/stat.h \ sys/syscall.h \ sys/sysmacros.h \ diff --git a/tools/include/nolibc/nolibc.h b/tools/include/nolibc/nolibc.h index faa94f247281..352a207bbedc 100644 --- a/tools/include/nolibc/nolibc.h +++ b/tools/include/nolibc/nolibc.h @@ -106,6 +106,7 @@ #include "sys/reboot.h" #include "sys/resource.h" #include "sys/select.h" +#include "sys/sendfile.h" #include "sys/stat.h" #include "sys/syscall.h" #include "sys/sysmacros.h" diff --git a/tools/include/nolibc/sys/sendfile.h b/tools/include/nolibc/sys/sendfile.h new file mode 100644 index 000000000000..ac7d85847718 --- /dev/null +++ b/tools/include/nolibc/sys/sendfile.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * sendfile for NOLIBC + * Copyright (C) 2026 Daniel Palmer + */ + +/* make sure to include all global symbols */ +#include "../nolibc.h" + +#ifndef _NOLIBC_SYS_SENDFILE_H +#define _NOLIBC_SYS_SENDFILE_H + +#include "../sys.h" +#include + +/* + * ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); + */ + +static __attribute__((unused)) +ssize_t _sys_sendfile(int out_fd, int in_fd, off_t *offset, size_t count) +{ + __nolibc_static_assert(sizeof(*offset) == sizeof(__kernel_loff_t)); + +#ifdef __NR_sendfile64 + /* 32-bit applications use sendfile64 so offset is treated as a __kernel_loff_t */ + return __nolibc_syscall4(__NR_sendfile64, out_fd, in_fd, offset, count); +#else + __nolibc_static_assert(sizeof(*offset) == sizeof(__kernel_off_t)); + + return __nolibc_syscall4(__NR_sendfile, out_fd, in_fd, offset, count); +#endif +} + +static __attribute__((unused)) +ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count) +{ + return __sysret(_sys_sendfile(out_fd, in_fd, offset, count)); +} + +#endif /* _NOLIBC_SYS_SENDFILE_H */ From 9c47af906bc655c8f45aaf1f156656239c2c5073 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 28 Aug 2026 19:32:05 +0900 Subject: [PATCH 07/28] selftests/nolibc: Add basic test for sendfile() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Basic smoke test for sendfile(). Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260828103205.384589-3-daniel@thingy.jp [Thomas: add '#include sys/syscall.h', simplify test cleanup a bit] Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/nolibc-test.c | 68 ++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index ed860b0a15a1..0a8fe5100b7f 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -1563,6 +1564,72 @@ int test_large_file(void) return ret; } +int test_sendfile(void) +{ + char data_in[] = "This is some data"; + const size_t data_sz = sizeof(data_in); + char data_out[data_sz + 10]; + int in_fd, out_fd; + int ret; + + /* Create two tmp files */ + in_fd = open("/tmp", O_TMPFILE | O_RDWR, 0644); + out_fd = open("/tmp", O_TMPFILE | O_RDWR, 0644); + + if (in_fd == -1 || out_fd == -1) { + ret = __LINE__; + goto out; + } + + /* Populate the "in" file */ + ret = write(in_fd, data_in, data_sz); + if (ret != data_sz) { + ret = __LINE__; + goto out; + } + + /* Rewind the "in" file */ + if (lseek(in_fd, 0, SEEK_SET)) { + ret = __LINE__; + goto out; + } + + /* Use sendfile() to copy "in" to "out" */ + ret = sendfile(out_fd, in_fd, NULL, data_sz + 5); + if (ret != data_sz) { + ret = __LINE__; + goto out; + } + + /* Rewind the "out" file */ + if (lseek(out_fd, 0, SEEK_SET)) { + ret = __LINE__; + goto out; + } + + /* Read back the transferred data */ + ret = read(out_fd, data_out, sizeof(data_out)); + if (ret != data_sz) { + ret = __LINE__; + goto out; + } + + /* Check we have the same data in both files */ + if (memcmp(data_out, data_in, data_sz)) { + ret = __LINE__; + goto out; + } + + /* Test passed */ + ret = 0; + +out: + close(out_fd); + close(in_fd); + + return ret; +} + /* Run syscall tests between IDs and . * Return 0 on success, non-zero on failure. */ @@ -1684,6 +1751,7 @@ int run_syscall(int min, int max) CASE_TEST(select_null); EXPECT_SYSZR(1, ({ struct timeval tv = { 0 }; select(0, NULL, NULL, NULL, &tv); })); break; CASE_TEST(select_stdout); EXPECT_SYSNE(1, ({ fd_set fds; FD_ZERO(&fds); FD_SET(1, &fds); select(2, NULL, &fds, NULL, NULL); }), -1); break; CASE_TEST(select_fault); EXPECT_SYSER(1, select(1, (void *)1, NULL, NULL, 0), -1, EFAULT); break; + CASE_TEST(sendfile); EXPECT_SYSZR(1, test_sendfile()); break; CASE_TEST(stat_blah); EXPECT_SYSER(1, stat("/proc/self/blah", &stat_buf), -1, ENOENT); break; CASE_TEST(stat_fault); EXPECT_SYSER(1, stat(NULL, &stat_buf), -1, EFAULT); break; CASE_TEST(stat_rdev); EXPECT_SYSZR(1, ({ int ret = stat("/dev/null", &stat_buf); ret ?: stat_buf.st_rdev != makedev(1, 3); })); break; From f19ae28f1f050b7d6551412a04281955cc4103e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 31 Aug 2026 18:05:01 +0200 Subject: [PATCH 08/28] tools/nolibc: validate directory with O_DIRECTORY in opendir() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch to O_DIRECTORY to validate that the opened file is a directory. ALso remove the call to fdopendir() in opendir() to avoid a double check of 'fd'. This will also make it easier to add a similar check for fdopendir(). Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260831-nolibc-fdopendir-enotdir-v1-3-8cf0e79c4e6f@weissschuh.net --- tools/include/nolibc/dirent.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/include/nolibc/dirent.h b/tools/include/nolibc/dirent.h index 4e02ef25e72d..c7372c935e0d 100644 --- a/tools/include/nolibc/dirent.h +++ b/tools/include/nolibc/dirent.h @@ -42,10 +42,11 @@ DIR *opendir(const char *name) { int fd; - fd = open(name, O_RDONLY); + fd = open(name, O_RDONLY | O_DIRECTORY); if (fd == -1) return NULL; - return fdopendir(fd); + + return (DIR *)(intptr_t)~fd; } static __attribute__((unused)) From f90242f39f32c6d708c33af504bdc80a683f0da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 31 Aug 2026 18:04:59 +0200 Subject: [PATCH 09/28] tools/nolibc: verify that a directory is opened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a non-directory is opened, ENODIR should be returned from opendir()/fdopendir() right away and not only during readdir_r(). Validate the type of opened file during open. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260831-nolibc-fdopendir-enotdir-v1-1-8cf0e79c4e6f@weissschuh.net --- tools/include/nolibc/dirent.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/include/nolibc/dirent.h b/tools/include/nolibc/dirent.h index c7372c935e0d..2dbf4052b85a 100644 --- a/tools/include/nolibc/dirent.h +++ b/tools/include/nolibc/dirent.h @@ -30,10 +30,23 @@ typedef struct { static __attribute__((unused)) DIR *fdopendir(int fd) { + struct stat buf; + int ret; + if (fd < 0) { SET_ERRNO(EBADF); return NULL; } + + ret = fstat(fd, &buf); + if (ret < 0) + return NULL; + + if (!S_ISDIR(buf.st_mode)) { + SET_ERRNO(ENOTDIR); + return NULL; + } + return (DIR *)(intptr_t)~fd; } From 319b153f400a510e15029ce92c9c9b8981aa8bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 31 Aug 2026 18:05:00 +0200 Subject: [PATCH 10/28] selftests/nolibc: validate ENOTDIR return values from opendir()/fdopendir() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure that ENOTDIR is detected already during directory opening. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260831-nolibc-fdopendir-enotdir-v1-2-8cf0e79c4e6f@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 0a8fe5100b7f..f62183a46205 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1711,6 +1711,7 @@ int run_syscall(int min, int max) CASE_TEST(execve_root); EXPECT_SYSER(1, execve("/", (char*[]){ [0] = (char []){"/"}, [1] = NULL }, NULL), -1, EACCES); break; CASE_TEST(fchdir_stdin); EXPECT_SYSER(1, fchdir(STDIN_FILENO), -1, ENOTDIR); break; CASE_TEST(fchdir_badfd); EXPECT_SYSER(1, fchdir(-1), -1, EBADF); break; + CASE_TEST(fdopendir_notdir); EXPECT_SYSER(1, (uintptr_t)fdopendir(STDIN_FILENO), (uintptr_t)NULL, ENOTDIR); break; CASE_TEST(file_stream); EXPECT_SYSZR(1, test_file_stream()); break; CASE_TEST(file_stream_wsr); EXPECT_SYSZR(1, test_file_stream_wsr()); break; CASE_TEST(fork); EXPECT_SYSZR(1, test_fork(FORK_STANDARD)); break; @@ -1739,6 +1740,7 @@ int run_syscall(int min, int max) CASE_TEST(open_blah); EXPECT_SYSER(1, tmp = open("/proc/self/blah", O_RDONLY), -1, ENOENT); if (tmp != -1) close(tmp); break; CASE_TEST(openat_dir); EXPECT_SYSZR(1, test_openat()); break; CASE_TEST(open_mode); EXPECT_SYSZR(1, test_open_mode()); break; + CASE_TEST(opendir_notdir); EXPECT_SYSER(1, (uintptr_t)opendir("/dev/null"), (uintptr_t)NULL, ENOTDIR); break; CASE_TEST(pipe); EXPECT_SYSZR(1, test_pipe()); break; CASE_TEST(poll_null); EXPECT_SYSZR(1, poll(NULL, 0, 0)); break; CASE_TEST(poll_stdout); EXPECT_SYSNE(1, ({ struct pollfd fds = { 1, POLLOUT, 0}; poll(&fds, 1, 0); }), -1); break; From f2212892b6a0c2adab438783e9fa8093c90786d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 14 Sep 2026 00:26:31 +0200 Subject: [PATCH 11/28] tools/nolibc/parisc: consistently use %dp to refer to register %r27 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parisc initialization code inconsistently uses both %dp and %r27 to refer to the data pointer register. Consistently use the %dp memnonic. Suggested-by: Helge Deller Link: https://lore.kernel.org/lkml/495ef25e-0bac-4f98-b0da-ae0d96dcea59@gmx.de/ Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260914-nolibc-parisc-dp-register-v1-1-6388f4aa8fbe@weissschuh.net --- tools/include/nolibc/arch-parisc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/arch-parisc.h b/tools/include/nolibc/arch-parisc.h index 417043ecbc53..ca219cbbabb0 100644 --- a/tools/include/nolibc/arch-parisc.h +++ b/tools/include/nolibc/arch-parisc.h @@ -169,7 +169,7 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote __asm__ volatile ( ".import $global$\n" /* Set up the dp register */ "ldil L%$global$, %dp\n" - "ldo R%$global$(%r27), %dp\n" + "ldo R%$global$(%dp), %dp\n" "b _start_c\n" /* Call _start_c, the load below is executed first */ From 368fae47c6cae28a312c5dc86e5bdb8f9369fd26 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Wed, 23 Sep 2026 15:56:21 -0500 Subject: [PATCH 12/28] tools/nolibc: fix WIFSIGNALED() for status 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WIFSIGNALED() needs the subtraction to wrap around for status 0, so that only 1 to 0xff, a terminating signal with or without the core dump flag, pass the check. But status is an int, and 0 - 1 is -1, which is less than 0xff. A child that exits with 0 is therefore reported as both exited and killed, and a caller that tests WIFSIGNALED() first sees "killed by signal 0". Make the subtraction unsigned, as musl does in the same macro. Fixes: 8c934d4822c7 ("tools/nolibc: add helpers for wait() signal exits") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260923205622.1123270-2-danishkhateeb03@gmail.com [Thomas: Drop stable@ Cc] Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/types.h b/tools/include/nolibc/types.h index 8f3cb18df7f1..4373bd8a5b5f 100644 --- a/tools/include/nolibc/types.h +++ b/tools/include/nolibc/types.h @@ -119,7 +119,7 @@ struct timeval { #define WEXITSTATUS(status) (((status) & 0xff00) >> 8) #define WIFEXITED(status) (((status) & 0x7f) == 0) #define WTERMSIG(status) ((status) & 0x7f) -#define WIFSIGNALED(status) ((status) - 1 < 0xff) +#define WIFSIGNALED(status) ((status) - 1U < 0xff) /* standard exit() codes */ #define EXIT_SUCCESS 0 From 61fb00d6efeeeb9fd65c81db3bd8b2ac140f573c Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Wed, 23 Sep 2026 15:56:22 -0500 Subject: [PATCH 13/28] selftests/nolibc: add tests for the wait status macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until the previous commit, WIFSIGNALED() was true for status 0, which is what waitpid() reports for a child that exits with 0. nolibc-test did not notice, because its only use of WIFSIGNALED(), in the abort test, also checks WTERMSIG(). Add tests for WEXITSTATUS(), WIFEXITED(), WIFSIGNALED() and WTERMSIG(), on the statuses the kernel reports for an exit with 0 or 0x12, a kill by SIGKILL, a SIGSEGV with the core dump flag, and a stop by SIGSTOP. Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260923205622.1123270-3-danishkhateeb03@gmail.com [Thomas: Use EXPECT_EQ() for all assertions] Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/nolibc-test.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index f62183a46205..37c11a7fce23 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -2038,6 +2038,14 @@ int run_stdlib(int min, int max) CASE_TEST(htole32); EXPECT_EQ(1, htole32(is_le ? 0x01234567 : 0x67452301), 0x01234567); break; CASE_TEST(htobe64); EXPECT_EQ(1, htobe64(is_le ? 0x0123456789000000 : 0x8967452301), 0x8967452301); break; CASE_TEST(htole64); EXPECT_EQ(1, htole64(is_le ? 0x0123456789 : 0x8967452301000000), 0x0123456789); break; + CASE_TEST(wexitstatus); EXPECT_EQ(1, WEXITSTATUS(0x1200), 0x12); break; + CASE_TEST(wifexited_exit0); EXPECT_EQ(1, WIFEXITED(0), 1); break; + CASE_TEST(wifexited_signal); EXPECT_EQ(1, WIFEXITED(SIGKILL), 0); break; + CASE_TEST(wifsignaled_exit0); EXPECT_EQ(1, WIFSIGNALED(0), 0); break; + CASE_TEST(wifsignaled_exit); EXPECT_EQ(1, WIFSIGNALED(0x1200), 0); break; + CASE_TEST(wifsignaled_signal); EXPECT_EQ(1, WIFSIGNALED(SIGKILL), 1); break; + CASE_TEST(wifsignaled_stopped); EXPECT_EQ(1, WIFSIGNALED((SIGSTOP << 8) | 0x7f), 0); break; + CASE_TEST(wtermsig_core); EXPECT_EQ(1, WTERMSIG(0x80 | SIGSEGV), SIGSEGV); break; case __LINE__: return ret; /* must be last */ From 6f114ae7b6006ca7cf72e3e3348d263bbfec4525 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Thu, 24 Sep 2026 07:09:57 -0500 Subject: [PATCH 14/28] tools/nolibc: fix verrx() and errx() message formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verrx() passes its va_list to warnx() instead of vwarnx(). warnx() is variadic, so it takes the va_list as its first and only argument, and the conversions in the format string print the va_list itself and then whatever is left in the following argument slots. On x86_64, for instance, errx(1, "int args: %d %d %d", 1, 2, 3); prints something like "int args: -1699009032 1 2", where the first number comes from the address of the va_list and changes from run to run, and errx(1, "%d %s", 1234, "foo"); crashes with SIGSEGV, as "%s" takes a leftover argument slot for the string. errx() goes through verrx(), so every message with arguments printed by either of them is wrong. Call vwarnx() from verrx(), as verr() calls vwarn(). Fixes: 9da0f529c089 ("tools/nolibc: add err.h") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260924120958.23698-2-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/err.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/err.h b/tools/include/nolibc/err.h index e22ae87a7289..61978d537ed8 100644 --- a/tools/include/nolibc/err.h +++ b/tools/include/nolibc/err.h @@ -60,7 +60,7 @@ void verr(int eval, const char *fmt, va_list args) static __attribute__((noreturn, unused)) void verrx(int eval, const char *fmt, va_list args) { - warnx(fmt, args); + vwarnx(fmt, args); exit(eval); } From 00c81514ad49110574e4b9bdbc448551004161f2 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Fri, 25 Sep 2026 16:15:58 -0500 Subject: [PATCH 15/28] tools/nolibc: fix readdir_r() with 64-bit directory offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readdir_r() stores the result of _sys_lseek() in an int. Directory offsets are opaque cookies which can use all 64 bits: ext4, for instance, gives 64-bit processes 63-bit hashes, and 0x7fffffffffffffff as the offset after the last entry. Truncated to an int, such an offset is negative about half the time, and readdir_r() takes it for an error. Since commit 4ada5679f18d ("tools/nolibc/dirent: avoid errno in readdir_r"), readdir_r() fails at the first entry whose offset has bit 31 set, which on ext4 is usually one of the first few, and returns the truncated offset as the error number. Before that, only -1 counted as an error, which the last entry always hits: readdir_r() then returned errno, usually 0, without filling in the entry, so the caller got the previous entry a second time and never saw the last one. 32-bit processes get 31-bit hashes from ext4 and are not affected. Keep the offset in an off_t. Fixes: 665fa8dea90d ("tools/nolibc: add support for directory access") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260925211600.119116-2-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/dirent.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/include/nolibc/dirent.h b/tools/include/nolibc/dirent.h index 2dbf4052b85a..19857369de2a 100644 --- a/tools/include/nolibc/dirent.h +++ b/tools/include/nolibc/dirent.h @@ -81,6 +81,7 @@ int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) struct linux_dirent64 *ldir = (void *)buf; intptr_t i = (intptr_t)dirp; int fd, ret; + off_t off; if (i >= 0) return EBADF; @@ -100,9 +101,9 @@ int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) * readdir() can only return one entry at a time. * Make sure the non-returned ones are not skipped. */ - ret = _sys_lseek(fd, ldir->d_off, SEEK_SET); - if (ret < 0) - return -ret; + off = _sys_lseek(fd, ldir->d_off, SEEK_SET); + if (off < 0) + return -off; entry->d_ino = ldir->d_ino; /* the destination should always be big enough */ From 929ea5b3481fdbbb419f3986703280fb2b819504 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Fri, 25 Sep 2026 16:15:59 -0500 Subject: [PATCH 16/28] tools/nolibc: fix FD_SET(), FD_CLR() and FD_ISSET() on 64-bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit feaf75658783 ("nolibc: fix fd_set type"), an fd_set is an array of unsigned long, but the FD_* macros still build their masks as an int or an unsigned int: 1 << n in FD_SET(), 1U << n in FD_CLR() and FD_ISSET(), where n is the fd modulo 64 on 64-bit architectures. So: - FD_SET() of an fd with n == 31 sets the bits for n = 31-63, as 1 << 31 is negative and gets sign-extended. - For n >= 32 the shifts are undefined. x86-64, for instance, masks the shift count, so FD_SET(40) and FD_ISSET(40) use the bit of fd 8. - FD_CLR() zero-extends ~(1U << n), so it also clears the bits for n = 32-63, whichever fd it is given. select() on fd 40, for instance, fails with EBADF, or watches fd 8 instead if that one is open. Build the masks from 1UL. Fixes: feaf75658783 ("nolibc: fix fd_set type") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260925211600.119116-3-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/sys/select.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/include/nolibc/sys/select.h b/tools/include/nolibc/sys/select.h index 6d65d9ef3d6a..f299c79939f5 100644 --- a/tools/include/nolibc/sys/select.h +++ b/tools/include/nolibc/sys/select.h @@ -26,7 +26,7 @@ typedef struct { int __fd = (fd); \ if (__fd >= 0) \ __set->fds[__fd / FD_SETIDXMASK] &= \ - ~(1U << (__fd & FD_SETBITMASK)); \ + ~(1UL << (__fd & FD_SETBITMASK)); \ } while (0) #define FD_SET(fd, set) do { \ @@ -34,7 +34,7 @@ typedef struct { int __fd = (fd); \ if (__fd >= 0) \ __set->fds[__fd / FD_SETIDXMASK] |= \ - 1 << (__fd & FD_SETBITMASK); \ + 1UL << (__fd & FD_SETBITMASK); \ } while (0) #define FD_ISSET(fd, set) ({ \ @@ -43,7 +43,7 @@ typedef struct { int __r = 0; \ if (__fd >= 0) \ __r = !!(__set->fds[__fd / FD_SETIDXMASK] & \ -1U << (__fd & FD_SETBITMASK)); \ +1UL << (__fd & FD_SETBITMASK)); \ __r; \ }) From 6a8fa40b03f7f111eba4ece966ec4762728378ad Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Fri, 25 Sep 2026 16:16:00 -0500 Subject: [PATCH 17/28] selftests/nolibc: test the FD_* macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only test that uses the FD_* macros passes fd 1, so nothing noticed that they used the wrong bits for fds 31-63 of each word on 64-bit architectures. Check that FD_SET() and FD_CLR() change exactly one fd, for every fd below FD_SETSIZE. Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260925211600.119116-4-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/nolibc-test.c | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 37c11a7fce23..1995d67dcca6 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1841,6 +1841,26 @@ int test_time_types(void) return 0; } +int test_fd_set(void) +{ + fd_set set, clr; + int fd, i; + + /* FD_SET() and FD_CLR() must change exactly one fd */ + for (fd = 0; fd < FD_SETSIZE; fd++) { + FD_ZERO(&set); + FD_SET(fd, &set); + memset(&clr, 0xff, sizeof(clr)); + FD_CLR(fd, &clr); + for (i = 0; i < FD_SETSIZE; i++) + if (!!FD_ISSET(i, &set) != (i == fd) || + !!FD_ISSET(i, &clr) != (i != fd)) + return 1; + } + + return 0; +} + int test_malloc(void) { size_t sz_array1, sz_array2, sz_array3; @@ -2022,6 +2042,7 @@ int run_stdlib(int min, int max) CASE_TEST(memchr_foobar6_o); EXPECT_STREQ(1, memchr("foobar", 'o', 6), "oobar"); break; CASE_TEST(memchr_foobar3_b); EXPECT_STRZR(1, memchr("foobar", 'b', 3)); break; CASE_TEST(time_types); EXPECT_ZR(is_nolibc, test_time_types()); break; + CASE_TEST(fd_set); EXPECT_ZR(1, test_fd_set()); break; CASE_TEST(makedev); EXPECT_EQ(1, makedev(0x12, 0x34), 0x1234); break; CASE_TEST(major); EXPECT_EQ(1, major(0x1234), 0x12); break; CASE_TEST(minor); EXPECT_EQ(1, minor(0x1234), 0x34); break; From 0e1c44b472e1ec21efdad1df21b10e5c568b65b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 7 Apr 2026 13:44:12 +0200 Subject: [PATCH 18/28] tools/nolibc: add 64-bit parisc support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the existing parisc support to also handle 64-bit mode. Some peculiarities: * The nonstandard ftruncate64 system call requires a workaround. * The linker defaults to main() as entry point, which needs to be switched to _start() explicitly. * The linker defaults to dynamic linking, which needs to be disabled explicitly. * With the default -Os, gcc does not use 64-bit multiplication instructions, requiring libgcc. Use -O2 instead. * There is no qemu-user available. * CONFIG_COMPAT needs to be disabled to avoid build errors due to a missing 32-bit vDSO compiler. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260914-nolibc-parisc64-v3-1-f14b320117c1@weissschuh.net --- tools/include/nolibc/arch-parisc.h | 26 ++++++++++++++++--- tools/include/nolibc/stdlib.h | 2 +- .../testing/selftests/nolibc/Makefile.nolibc | 5 ++++ tools/testing/selftests/nolibc/run-tests.sh | 10 ++++--- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/tools/include/nolibc/arch-parisc.h b/tools/include/nolibc/arch-parisc.h index ca219cbbabb0..bf57ba16055c 100644 --- a/tools/include/nolibc/arch-parisc.h +++ b/tools/include/nolibc/arch-parisc.h @@ -7,9 +7,7 @@ #ifndef _NOLIBC_ARCH_PARISC_H #define _NOLIBC_ARCH_PARISC_H -#if defined(__LP64__) -#error 64-bit not supported -#endif +#include #include "compiler.h" #include "crt.h" @@ -167,9 +165,15 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( +#ifdef __LP64__ + ".import __gp\n" /* Set up the dp register */ + "ldil L%__gp, %dp\n" + "ldo R%__gp(%dp), %dp\n" +#else ".import $global$\n" /* Set up the dp register */ "ldil L%$global$, %dp\n" "ldo R%$global$(%dp), %dp\n" +#endif "b _start_c\n" /* Call _start_c, the load below is executed first */ @@ -182,4 +186,20 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ +#ifdef __LP64__ +/* + * This override is normally only meant for 32-bit architectures. + * But parisc 64-bit has __NR_ftruncate64, so this path is taken. + * Add an unused second argument to satisfy the calling convention. + */ +static __attribute__((unused)) +int _sys_ftruncate64(int fd, uint64_t length, uint64_t unused) +{ + (void)unused; + + return __nolibc_syscall2(__NR_ftruncate64, fd, length); +} +#define _sys_ftruncate64 _sys_ftruncate64 +#endif + #endif /* _NOLIBC_ARCH_PARISC_H */ diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index 1816c2368b68..6c3c620a04cf 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -230,7 +230,7 @@ int _nolibc_u64toa_base(uint64_t in, char *buffer, unsigned int base, uint64_t r /* Generate least significant digit first */ do { -#if defined(__SIZEOF_INT128__) && !defined(__mips__) && !defined(__sparc__) +#if defined(__SIZEOF_INT128__) && !defined(__mips__) && !defined(__sparc__) && !defined(__hppa__) q = ((unsigned __int128)in * recip) >> 64; #else uint64_t p = (uint32_t)in * (recip >> 32); diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index d8b71cbfc173..3ca473ec35ef 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -65,6 +65,7 @@ ARCH_sparc32 = sparc ARCH_sparc64 = sparc ARCH_sh4 = sh ARCH_parisc32 = parisc +ARCH_parisc64 = parisc64 ARCH := $(or $(ARCH_$(XARCH)),$(XARCH)) # kernel image names by architecture @@ -113,6 +114,7 @@ EXTRACONFIG_sparc32 = -e CONFIG_TMPFS EXTRACONFIG_m68k = -e CONFIG_BLK_DEV_INITRD EXTRACONFIG_sh4 = -e CONFIG_BLK_DEV_INITRD -e CONFIG_CMDLINE_FROM_BOOTLOADER EXTRACONFIG_alpha = -e CONFIG_BLK_DEV_INITRD +EXTRACONFIG_parisc64 = -d CONFIG_COMPAT EXTRACONFIG = $(EXTRACONFIG_$(XARCH)) # optional tests to run (default = all) @@ -134,6 +136,7 @@ QEMU_ARCH_loongarch = loongarch64 QEMU_ARCH_sparc32 = sparc QEMU_ARCH_openrisc = or1k QEMU_ARCH_parisc32 = hppa +QEMU_ARCH_parisc64 = hppa QEMU_ARCH = $(or $(QEMU_ARCH_$(XARCH)),$(XARCH)) QEMU_ARCH_USER_ppc64le = ppc64le @@ -175,6 +178,7 @@ QEMU_ARGS_m68k = -M virt -append "console=ttyGF0,115200 panic=-1 $(TEST:%= QEMU_ARGS_sh4 = -M r2d -serial file:/dev/stdout -append "console=ttySC1,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_openrisc = -M virt -m 512M -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_parisc32 = -M B160L -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_parisc64 = -M C3700 -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_alpha = -M clipper -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS = -m 1G $(QEMU_ARGS_$(XARCH)) $(QEMU_ARGS_BIOS) $(QEMU_ARGS_EXTRA) @@ -193,6 +197,7 @@ CFLAGS_x32 = -mx32 CFLAGS_arm = -marm CFLAGS_armthumb = -mthumb -march=armv6t2 CFLAGS_parisc32 = -mfast-indirect-calls +CFLAGS_parisc64 = -Wl,--no-dynamic-linker -Wl,--entry=_start -O2 CFLAGS_ppc = -m32 -mbig-endian -mno-vsx $(call cc-option,-mmultiple) CFLAGS_ppc64 = -m64 -mbig-endian -mno-vsx $(call cc-option,-mmultiple) CFLAGS_ppc64le = -m64 -mlittle-endian -mno-vsx $(call cc-option,-mabi=elfv2) diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index af2a6f510d1d..2844fed26df1 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -29,7 +29,7 @@ all_archs=( sparc32 sparc64 m68k sh4 - parisc32 + parisc32 parisc64 alpha hexagon ) @@ -122,6 +122,7 @@ crosstool_arch() { sparc*) echo sparc64;; x32*) echo x86_64;; parisc32) echo hppa;; + parisc64) echo hppa64;; *) echo "$1";; esac } @@ -206,11 +207,14 @@ test_arch() { exit 1 esac printf '%-15s' "$arch:" - if [ "$llvm" = "1" ] && [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" -o "$arch" = "alpha" ]; then + if [ "$llvm" = "1" ] && \ + [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" \ + -o "$arch" == "parisc32" -o "$arch" == "parisc64" -o "$arch" = "alpha" \ + ]; then echo "Unsupported configuration" return fi - if [ "$test_mode" = "user" ] && [ "$arch" = "x32" ]; then + if [ "$test_mode" = "user" ] && [ "$arch" = "x32" -o "$arch" = "parisc64" ]; then echo "Unsupported configuration" return fi From 94d8ed941d63132fee54204b273ba1fd7dcfc760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:12 +0200 Subject: [PATCH 19/28] selftests/nolibc: make the broken MIPS N32 argument detection reusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Another test is about to be added which needs to make use of this. Move the definition into a reusable variable. As a side-effect, the test is now properly marked as skipped. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-1-e9af6e47b37a@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 1995d67dcca6..c344675ad0c9 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1525,12 +1525,6 @@ int test_large_file(void) ssize_t written; off_t off; -#if defined(__mips__) && defined(_ABIN32) - /* https://lore.kernel.org/qemu-devel/fed03914-a95a-4522-a432-f129264cb2ac@t-8ch.de/ */ - if (getpid() != 1) - return 0; -#endif - if (large_seek < UINT32_MAX) { errno = EOVERFLOW; return -1; @@ -1651,6 +1645,7 @@ int run_syscall(int min, int max) void *p1, *p2; int has_gettid = 1; int has_brk; + int has_working_64bit_syscall_arg = 1; /* indicates whether or not /proc is mounted */ proc = stat("/proc", &stat_buf) == 0; @@ -1666,6 +1661,11 @@ int run_syscall(int min, int max) /* on musl setting brk()/sbrk() always fails */ has_brk = brk(0) == 0; +#if defined(__mips__) && defined(_ABIN32) + /* https://lore.kernel.org/qemu-devel/fed03914-a95a-4522-a432-f129264cb2ac@t-8ch.de/ */ + has_working_64bit_syscall_arg = getpid() == 1; +#endif + for (test = min; test >= 0 && test <= max; test++) { int llen = 0; /* line length */ @@ -1781,7 +1781,7 @@ int run_syscall(int min, int max) CASE_TEST(_syscall_noargs); EXPECT_SYSEQ(is_nolibc, _syscall(__NR_getpid), getpid()); break; CASE_TEST(_syscall_args); EXPECT_SYSEQ(is_nolibc, _syscall(__NR_statx, 0, NULL, 0, 0, NULL), -EFAULT); break; CASE_TEST(namespace); EXPECT_SYSZR(euid0 && proc, test_namespace()); break; - CASE_TEST(largefile); EXPECT_SYSZR(1, test_large_file()); break; + CASE_TEST(largefile); EXPECT_SYSZR(has_working_64bit_syscall_arg, test_large_file()); break; case __LINE__: return ret; /* must be last */ /* note: do not set any defaults so as to permit holes above */ From 74136192e3f6f48670a0bccc7bdc5005b13d94f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:13 +0200 Subject: [PATCH 20/28] tools/nolibc: make 64-bit systemcall argument padding generic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several 32-bit architectures pass a 64-bit systemcall argument as two 32-bit values requiring alignment to an even register pair. This case is currently relevant for the ftruncate64() wrapper and handled by those architectures providing custom wrappers. With the introduction of pread() and pwrite() wrappers, the same pattern would lead to a lot of repeated code. Add a #define, so that the generic code can do the padding where necessary, without any per-architecture duplication. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-2-e9af6e47b37a@weissschuh.net --- tools/include/nolibc/arch-arm.h | 7 +------ tools/include/nolibc/arch-hexagon.h | 7 +------ tools/include/nolibc/arch-mips.h | 7 +------ tools/include/nolibc/arch-powerpc.h | 7 +------ tools/include/nolibc/unistd.h | 4 ++++ 5 files changed, 8 insertions(+), 24 deletions(-) diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h index b88686fb19c4..b5b3456c75cb 100644 --- a/tools/include/nolibc/arch-arm.h +++ b/tools/include/nolibc/arch-arm.h @@ -196,11 +196,6 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ -static __attribute__((unused)) -int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) -{ - return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); -} -#define _sys_ftruncate64 _sys_ftruncate64 +#define __NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR #endif /* _NOLIBC_ARCH_ARM_H */ diff --git a/tools/include/nolibc/arch-hexagon.h b/tools/include/nolibc/arch-hexagon.h index 0767054882f3..68f73529a6dd 100644 --- a/tools/include/nolibc/arch-hexagon.h +++ b/tools/include/nolibc/arch-hexagon.h @@ -154,11 +154,6 @@ _start(void) } #endif /* NOLIBC_NO_RUNTIME */ -static __attribute__((unused)) -int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) -{ - return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); -} -#define _sys_ftruncate64 _sys_ftruncate64 +#define __NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR #endif /* _NOLIBC_ARCH_HEXAGON_H */ diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h index 26ad413cec62..21b9959a08e2 100644 --- a/tools/include/nolibc/arch-mips.h +++ b/tools/include/nolibc/arch-mips.h @@ -286,12 +286,7 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #endif /* NOLIBC_NO_RUNTIME */ #if defined(_ABIO32) -static __attribute__((unused)) -int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) -{ - return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); -} -#define _sys_ftruncate64 _sys_ftruncate64 +#define __NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR #endif #endif /* _NOLIBC_ARCH_MIPS_H */ diff --git a/tools/include/nolibc/arch-powerpc.h b/tools/include/nolibc/arch-powerpc.h index dbe2e5205aaa..a4711638cb66 100644 --- a/tools/include/nolibc/arch-powerpc.h +++ b/tools/include/nolibc/arch-powerpc.h @@ -222,12 +222,7 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #endif /* NOLIBC_NO_RUNTIME */ #if !defined(__powerpc64__) -static __attribute__((unused)) -int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) -{ - return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); -} -#define _sys_ftruncate64 _sys_ftruncate64 +#define __NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR #endif #endif /* _NOLIBC_ARCH_POWERPC_H */ diff --git a/tools/include/nolibc/unistd.h b/tools/include/nolibc/unistd.h index a264a20da13d..939d4e05a55d 100644 --- a/tools/include/nolibc/unistd.h +++ b/tools/include/nolibc/unistd.h @@ -52,7 +52,11 @@ int access(const char *path, int amode) static __attribute__((unused)) int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) { +#if defined(__NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR) + return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); +#else return __nolibc_syscall3(__NR_ftruncate64, fd, length0, length1); +#endif } #define _sys_ftruncate64 _sys_ftruncate64 #endif From ddcf77b0350b2eaca4ba2f9db7c4569045c0c46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:14 +0200 Subject: [PATCH 21/28] tools/nolibc: move __NOLIBC_LLARGPART() to compiler.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pread() and pwrite() support will require the usage of the macro from the arch-*.h headers. sys.h can not be used from those. Move the macro to a more generic header. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-3-e9af6e47b37a@weissschuh.net --- tools/include/nolibc/compiler.h | 9 +++++++++ tools/include/nolibc/sys.h | 10 ---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tools/include/nolibc/compiler.h b/tools/include/nolibc/compiler.h index f2d7a81d0d7c..375c45ce81c8 100644 --- a/tools/include/nolibc/compiler.h +++ b/tools/include/nolibc/compiler.h @@ -90,4 +90,13 @@ # define __nolibc_no_sanitize_undefined #endif +/* + * Helper for 32-bit machines where a 64-bit syscall arg needs to be split into + * two 32-bit parts while making sure the order of the low/high parts are correct + * for the endianness: + * __NOLIBC_LLARGPART(x, 0), __NOLIBC_LLARGPART(x, 1) + */ +#define __NOLIBC_LLARGPART(_arg, _part) \ + (((union { long long ll; long l[2]; }) { .ll = _arg }).l[_part]) + #endif /* _NOLIBC_COMPILER_H */ diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 548f94d96ed2..7f06d27da612 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -71,16 +71,6 @@ static __inline__ int __nolibc_enosys(const char *syscall, ...) #endif -/* - * Helper for 32-bit machines where a 64-bit syscall arg needs to be split into - * two 32-bit parts while making sure the order of the low/high parts are correct - * for the endianness: - * __NOLIBC_LLARGPART(x, 0), __NOLIBC_LLARGPART(x, 1) - */ -#define __NOLIBC_LLARGPART(_arg, _part) \ - (((union { long long ll; long l[2]; }) { .ll = _arg }).l[_part]) - - /* Functions in this file only describe syscalls. They're declared static so * that the compiler usually decides to inline them while still being allowed * to pass a pointer to one of their instances. Each syscall exists in two From abafa291e52ac95c46ba0744b64e4d0d3adae4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:15 +0200 Subject: [PATCH 22/28] tools/nolibc: introduce __NOLIBC_BITS_PER_SYSCALL_ARG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ILP32 architectures, like x32 and n32, support 64-bit system call arguments, although they are nominally 32-bit architectures. Add a new define which provides the size of system call arguments so generic libc code can use the best system call for those. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-4-e9af6e47b37a@weissschuh.net --- tools/include/nolibc/arch-mips.h | 1 + tools/include/nolibc/arch-x86.h | 2 ++ tools/include/nolibc/arch.h | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h index 21b9959a08e2..c0da0cc309ce 100644 --- a/tools/include/nolibc/arch-mips.h +++ b/tools/include/nolibc/arch-mips.h @@ -72,6 +72,7 @@ #define _NOLIBC_SYSCALL_STACK_UNRESERVE #define _NOLIBC_SYSCALL_REG register long long +#define __NOLIBC_BITS_PER_SYSCALL_ARG 64 #endif /* _ABIO32 */ diff --git a/tools/include/nolibc/arch-x86.h b/tools/include/nolibc/arch-x86.h index 577e9b682eb3..0638f9316749 100644 --- a/tools/include/nolibc/arch-x86.h +++ b/tools/include/nolibc/arch-x86.h @@ -386,5 +386,7 @@ __asm__ ( ".popsection\n" ); +#define __NOLIBC_BITS_PER_SYSCALL_ARG 64 + #endif /* !defined(__x86_64__) */ #endif /* _NOLIBC_ARCH_X86_H */ diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index 24e0ec4fb2db..79ed5900f2a7 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -40,4 +40,9 @@ #error Unsupported Architecture #endif + +#ifndef __NOLIBC_BITS_PER_SYSCALL_ARG +#define __NOLIBC_BITS_PER_SYSCALL_ARG __BITS_PER_LONG +#endif + #endif /* _NOLIBC_ARCH_H */ From da27722051fdf0775d8c5b3e33dc21bcf605dbd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:16 +0200 Subject: [PATCH 23/28] tools/nolibc: add support for pread() and pwrite() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are standard function to interact with a file descriptor without touching its internal file offset. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-5-e9af6e47b37a@weissschuh.net --- tools/include/nolibc/arch-sh.h | 18 +++++++ tools/include/nolibc/sys.h | 50 ++++++++++++++++++++ tools/testing/selftests/nolibc/nolibc-test.c | 46 ++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/tools/include/nolibc/arch-sh.h b/tools/include/nolibc/arch-sh.h index a32378fd621f..996cfbe1bee3 100644 --- a/tools/include/nolibc/arch-sh.h +++ b/tools/include/nolibc/arch-sh.h @@ -10,6 +10,8 @@ #include "compiler.h" #include "crt.h" +#include + /* * Syscalls for SuperH: * - registers are 32bit wide @@ -161,4 +163,20 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ +static __attribute__((unused)) +ssize_t _sys_pread(int fd, void *buf, size_t count, off_t offset) +{ + return __nolibc_syscall6(__NR_pread64, fd, buf, count, 0, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +} +#define _sys_pread _sys_pread + +static __attribute__((unused)) +ssize_t _sys_pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + return __nolibc_syscall6(__NR_pwrite64, fd, buf, count, 0, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +} +#define _sys_pwrite _sys_pwrite + #endif /* _NOLIBC_ARCH_SH_H */ diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 7f06d27da612..b63fa6c618c5 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -745,6 +745,56 @@ int pivot_root(const char *new, const char *old) } +/* + * ssize_t pread(int fd, void *buf, size_t count, off_t offset); + * ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset); + */ + +#ifndef _sys_pread +static __attribute__((unused)) +ssize_t _sys_pread(int fd, void *buf, size_t count, off_t offset) +{ +#if __NOLIBC_BITS_PER_SYSCALL_ARG == 64 + return __nolibc_syscall4(__NR_pread64, fd, buf, count, offset); +#elif defined(__NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR) + return __nolibc_syscall6(__NR_pread64, fd, buf, count, 0, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +#else + return __nolibc_syscall5(__NR_pread64, fd, buf, count, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +#endif +} +#endif /* _sys_pread */ + +static __attribute__((unused)) +ssize_t pread(int fd, void *buf, size_t count, off_t offset) +{ + return __sysret(_sys_pread(fd, buf, count, offset)); +} + +#ifndef _sys_pwrite +static __attribute__((unused)) +ssize_t _sys_pwrite(int fd, const void *buf, size_t count, off_t offset) +{ +#if __NOLIBC_BITS_PER_SYSCALL_ARG == 64 + return __nolibc_syscall4(__NR_pwrite64, fd, buf, count, offset); +#elif defined(__NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR) + return __nolibc_syscall6(__NR_pwrite64, fd, buf, count, 0, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +#else + return __nolibc_syscall5(__NR_pwrite64, fd, buf, count, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +#endif +} +#endif /* _sys_pwrite */ + +static __attribute__((unused)) +ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + return __sysret(_sys_pwrite(fd, buf, count, offset)); +} + + /* * ssize_t read(int fd, void *buf, size_t count); */ diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index c344675ad0c9..f3d0d27cdbe1 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1369,6 +1369,51 @@ int test_pipe(void) return !!memcmp(buf, msg, len); } +int test_pwrite_pread(void) +{ + const off_t offset = (0x12ULL << 32) | 0x34; + ssize_t ret; + char buf[2]; + int fd, err; + off_t offs; + + fd = open("/tmp", O_TMPFILE | O_RDWR, 0644); + if (fd == -1) + return __LINE__; + + /* Write into the middle of the file. */ + ret = pwrite(fd, "abc", 3, offset); + if (ret != 3) { + err = __LINE__; + goto out; + } + + /* Make sure the file offset points somewhere else before pread(). */ + offs = lseek(fd, 0, SEEK_END); + if (offs != offset + 3) { + err = __LINE__; + goto out; + } + + ret = pread(fd, buf, 2, offset + 1); + if (ret != 2) { + err = __LINE__; + goto out; + } + + if (buf[0] != 'b' || buf[1] != 'c') { + err = __LINE__; + goto out; + } + + err = 0; + +out: + close(fd); + + return err; +} + int test_rlimit(void) { struct rlimit rlim = { @@ -1746,6 +1791,7 @@ int run_syscall(int min, int max) CASE_TEST(poll_stdout); EXPECT_SYSNE(1, ({ struct pollfd fds = { 1, POLLOUT, 0}; poll(&fds, 1, 0); }), -1); break; CASE_TEST(poll_fault); EXPECT_SYSER(1, poll(NULL, 1, 0), -1, EFAULT); break; CASE_TEST(prctl); EXPECT_SYSER(1, prctl(PR_SET_NAME, (unsigned long)NULL, 0, 0, 0), -1, EFAULT); break; + CASE_TEST(pwrite_pread); EXPECT_SYSZR(has_working_64bit_syscall_arg, test_pwrite_pread()); break; CASE_TEST(read_badf); EXPECT_SYSER(1, read(-1, &tmp, 1), -1, EBADF); break; CASE_TEST(rlimit); EXPECT_SYSZR(1, test_rlimit()); break; CASE_TEST(rmdir_blah); EXPECT_SYSER(1, rmdir("/blah"), -1, ENOENT); break; From b7db3ba9434cbad3d15f62500104525bfa28a304 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Sat, 26 Sep 2026 10:24:13 -0500 Subject: [PATCH 24/28] tools/nolibc: stop rounding malloc() sizes up to 4096 bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit malloc() rounds the size of each mapping up to a multiple of 4096, presumably to align it to a page boundary. But the page size is not 4096 on all architectures, and mmap() and munmap() round the length up to the page size themselves. Drop the rounding. As heap->len is no longer rounded up, growing a block with realloc() now always creates a new mapping. Assisted-by: LLM Signed-off-by: Danish Khateeb Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260926152414.251214-2-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/stdlib.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index 6c3c620a04cf..dc554faff22a 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -130,9 +130,7 @@ void *malloc(size_t len) { struct nolibc_heap *heap; - /* Always allocate memory with size multiple of 4096. */ len = sizeof(*heap) + len; - len = (len + 4095UL) & -4096UL; heap = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (__builtin_expect(heap == MAP_FAILED, 0)) From 2111eb4273e7fda0815de85319653104f323c441 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Sat, 26 Sep 2026 10:24:14 -0500 Subject: [PATCH 25/28] tools/nolibc: check for overflow in malloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit malloc() adds the size of its header to the requested size without checking for overflow. For a size within the header size of SIZE_MAX, the sum wraps around to a few bytes, and malloc() returns a single page instead of NULL. calloc() checks its multiplication for overflow, but then passes the product to malloc(), so calloc(SIZE_MAX, 1) returns a single page too. So does realloc(ptr, SIZE_MAX). Such a size is usually a bug in the caller, for instance a length of -1 used as a size_t, and returning a page instead of NULL can turn it into a heap overflow. Fail with ENOMEM when adding the header overflows, as glibc does for sizes this large. Fixes: 0e0ff638400b ("tools/nolibc/stdlib: Implement `malloc()`, `calloc()`, `realloc()` and `free()`") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260926152414.251214-3-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/stdlib.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index dc554faff22a..cea8290ac0b9 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -130,7 +130,11 @@ void *malloc(size_t len) { struct nolibc_heap *heap; - len = sizeof(*heap) + len; + if (__builtin_expect(__builtin_add_overflow(len, sizeof(*heap), &len), 0)) { + SET_ERRNO(ENOMEM); + return NULL; + } + heap = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (__builtin_expect(heap == MAP_FAILED, 0)) From 5f81ffdf26072fd262093c4127e656276564395f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 24 Sep 2026 09:11:58 +0200 Subject: [PATCH 26/28] tools/nolibc: fix the function argument order of calloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per the man page, the number of elements should be the first argument and the size of each one the second. Switch the arguments around. The behavior will not change, as both arguments are only multiplied with each other. Reported-by: Sean Christopherson Link: https://lore.kernel.org/lkml/aqQmCtPf_5M-BjRW@google.com/ Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260924-nolibc-calloc-v1-1-c49b383475d5@weissschuh.net --- tools/include/nolibc/stdlib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index cea8290ac0b9..1c5a914c0f23 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -145,7 +145,7 @@ void *malloc(size_t len) } static __attribute__((unused)) -void *calloc(size_t size, size_t nmemb) +void *calloc(size_t nmemb, size_t size) { size_t x; From f9532b372e415e8198287ce010efa22f337857b5 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Wed, 14 Oct 2026 16:01:42 -0400 Subject: [PATCH 27/28] selftests/nolibc: use the configured kernel header directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The freestanding build hardcodes the source tree's usr/include even when selftests exports KHDR_INCLUDES for an output-directory build. Use the exported header location while preserving -nostdlib, -nostdinc and static linkage. GCC 14 and Clang 21 builds then find the generated UAPI headers. Fixes: eb1353110831 ("selftests/nolibc: integrate with kselftests") Signed-off-by: Sasha Levin Link: https://patch.msgid.link/20261014200142.1364761-1-sashal@kernel.org Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nolibc/Makefile b/tools/testing/selftests/nolibc/Makefile index 6bace04227a7..6981c220eed7 100644 --- a/tools/testing/selftests/nolibc/Makefile +++ b/tools/testing/selftests/nolibc/Makefile @@ -10,7 +10,7 @@ cc-option = $(call __cc-option, $(CC),,$(1),$(2)) include Makefile.include $(OUTPUT)/nolibc-test: CFLAGS = -nostdlib -nostdinc -static \ - -isystem $(top_srcdir)/tools/include/nolibc -isystem $(top_srcdir)/usr/include \ + -isystem $(top_srcdir)/tools/include/nolibc $(KHDR_INCLUDES) \ $(CFLAGS_NOLIBC_TEST) $(OUTPUT)/nolibc-test: LDLIBS = $(if $(LLVM),,-lgcc) $(OUTPUT)/nolibc-test: $(NOLIBC_TEST_SOURCES) | headers From 700ce4da6f6f172435349b687d2d916e791c6330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 18 Oct 2026 00:02:18 +0200 Subject: [PATCH 28/28] selftests/nolibc: explicitly disable position independent executables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nolibc does not support position independent executables (PIE). However a toolchain might generate these by default depending on various circumstances. Recently clang started to do so for hexagon. See LLVM commit ab43cb8520c0 ("[Hexagon] Pass -pie to linker when PIE is the toolchain default (#189723)"). As clang can also be configured to create PIE for other architectures by default disable it globally. While we are at it also pass the argument for GCC to be on the safe side. If PIE is disabled already, clang will generate -Wunused-command-line-argument warnings. Inhibit those. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20261018-nolibc-unused-arguments-v1-1-6cba71555fe8@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.include | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/nolibc/Makefile.include b/tools/testing/selftests/nolibc/Makefile.include index ea520eac64a6..14e51c821d8a 100644 --- a/tools/testing/selftests/nolibc/Makefile.include +++ b/tools/testing/selftests/nolibc/Makefile.include @@ -8,6 +8,7 @@ _CFLAGS_SANITIZER ?= $(call cc-option,-fsanitize=undefined -fsanitize-trap=all) CFLAGS_NOLIBC_TEST ?= -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -ggdb \ -W -Wall -Wextra -Wundef -Wwrite-strings \ $(call cc-option,-fno-stack-protector) $(call cc-option,-Wmissing-prototypes) \ + $(call cc-option,--start-no-unused-arguments) -no-pie $(call cc-option,--end-no-unused-arguments) \ $(_CFLAGS_STACKPROTECTOR) $(_CFLAGS_SANITIZER) NOLIBC_TEST_SOURCES := nolibc-test.c nolibc-test-linkage.c nolibc-test-ignore-errno.c