[PATCH 3/3] tools/nolibc: add ftruncate()
From: Thomas Weißschuh
Date: Thu May 21 2026 - 13:32:25 EST
On architectures with 32-bit longs, call the compat syscall
__NR_ftruncate64. As off_t is 64-bit it must be split into 2 registers.
Unlike llseek() which passes the high and low parts in explicitly named
arguments, the order here is endian independent.
Some architectures (arm, mips, ppc) require this pair of registers to
be aligned to an even register, so add custom _sys_ftruncate64()
wrappers for those.
A test case for ftruncate is added which validates negative length or
invalid fd return the appropriate error, and checks the length is
correct on success.
Co-developed-by: Jordan Richards <jordanrichards@xxxxxxxxxx>
Signed-off-by: Jordan Richards <jordanrichards@xxxxxxxxxx>
Signed-off-by: Thomas Weißschuh <linux@xxxxxxxxxxxxxx>
---
Lifted from https://lore.kernel.org/lkml/20260303010039.2969125-1-jordanrichards@xxxxxxxxxx/
---
tools/include/nolibc/arch-arm.h | 10 ++++++
tools/include/nolibc/arch-mips.h | 12 +++++++
tools/include/nolibc/arch-powerpc.h | 12 +++++++
tools/include/nolibc/unistd.h | 24 +++++++++++++
tools/testing/selftests/nolibc/nolibc-test.c | 52 ++++++++++++++++++++++++++++
5 files changed, 110 insertions(+)
diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h
index 72a2b28170e2..8681922e05ca 100644
--- a/tools/include/nolibc/arch-arm.h
+++ b/tools/include/nolibc/arch-arm.h
@@ -7,8 +7,11 @@
#ifndef _NOLIBC_ARCH_ARM_H
#define _NOLIBC_ARCH_ARM_H
+#include <linux/unistd.h>
+
#include "compiler.h"
#include "crt.h"
+#include "std.h"
/* Syscalls for ARM in ARM or Thumb modes :
* - registers are 32-bit
@@ -196,4 +199,11 @@ 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
+
#endif /* _NOLIBC_ARCH_ARM_H */
diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h
index 1400653c76c1..26ad413cec62 100644
--- a/tools/include/nolibc/arch-mips.h
+++ b/tools/include/nolibc/arch-mips.h
@@ -7,8 +7,11 @@
#ifndef _NOLIBC_ARCH_MIPS_H
#define _NOLIBC_ARCH_MIPS_H
+#include <linux/unistd.h>
+
#include "compiler.h"
#include "crt.h"
+#include "std.h"
#if !defined(_ABIO32) && !defined(_ABIN32) && !defined(_ABI64)
#error Unsupported MIPS ABI
@@ -282,4 +285,13 @@ 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
+#endif
+
#endif /* _NOLIBC_ARCH_MIPS_H */
diff --git a/tools/include/nolibc/arch-powerpc.h b/tools/include/nolibc/arch-powerpc.h
index 111cda70f2cc..a1ab91d55384 100644
--- a/tools/include/nolibc/arch-powerpc.h
+++ b/tools/include/nolibc/arch-powerpc.h
@@ -7,8 +7,11 @@
#ifndef _NOLIBC_ARCH_POWERPC_H
#define _NOLIBC_ARCH_POWERPC_H
+#include <linux/unistd.h>
+
#include "compiler.h"
#include "crt.h"
+#include "std.h"
/* Syscalls for PowerPC :
* - stack is 16-byte aligned
@@ -218,4 +221,13 @@ 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
+#endif
+
#endif /* _NOLIBC_ARCH_POWERPC_H */
diff --git a/tools/include/nolibc/unistd.h b/tools/include/nolibc/unistd.h
index 5882a6862066..79599ceef45d 100644
--- a/tools/include/nolibc/unistd.h
+++ b/tools/include/nolibc/unistd.h
@@ -48,6 +48,30 @@ int access(const char *path, int amode)
return faccessat(AT_FDCWD, path, amode, 0);
}
+#if !defined(_sys_ftruncate64) && defined(__NR_ftruncate64)
+static __attribute__((unused))
+int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1)
+{
+ return __nolibc_syscall3(__NR_ftruncate64, fd, length0, length1);
+}
+#define _sys_ftruncate64 _sys_ftruncate64
+#endif
+
+static __attribute__((unused))
+int _sys_ftruncate(int fd, off_t length)
+{
+#if defined(_sys_ftruncate64)
+ return _sys_ftruncate64(fd, __NOLIBC_LLARGPART(length, 0), __NOLIBC_LLARGPART(length, 1));
+#else
+ return __nolibc_syscall2(__NR_ftruncate, fd, length);
+#endif
+}
+
+static __attribute__((unused))
+int ftruncate(int fd, off_t length)
+{
+ return __sysret(_sys_ftruncate(fd, length));
+}
static __attribute__((unused))
int msleep(unsigned int msecs)
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index c3867cc570c6..7c3b711c125c 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1017,6 +1017,57 @@ int test_fork(enum fork_type type)
}
}
+int test_ftruncate(void)
+{
+ struct stat stat_buf;
+ int ret, fd;
+
+ ret = ftruncate(-1, 0);
+ if (ret != -1 || errno != EBADF) {
+ errno = EINVAL;
+ return __LINE__;
+ }
+
+ fd = memfd_create(__func__, 0);
+ if (fd == -1)
+ return __LINE__;
+
+ /*
+ * This also tests that the high 32-bit half is passed through correctly.
+ * If it gets lost, the kernel will see a positive number and not fail.
+ */
+ ret = ftruncate(fd, -1);
+ if (!(ret == -1 && errno == EINVAL)) {
+ if (ret == 0)
+ errno = EINVAL;
+ ret = __LINE__;
+ goto end;
+ }
+
+ ret = ftruncate(fd, 42);
+ if (ret != 0) {
+ ret = __LINE__;
+ goto end;
+ }
+
+ ret = fstat(fd, &stat_buf);
+ if (ret != 0) {
+ ret = __LINE__;
+ goto end;
+ }
+
+ if (stat_buf.st_size != 42) {
+ errno = EINVAL;
+ ret = __LINE__;
+ goto end;
+ }
+
+end:
+ close(fd);
+
+ return ret;
+}
+
int test_stat_timestamps(void)
{
struct stat st;
@@ -1539,6 +1590,7 @@ int run_syscall(int min, int max)
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;
+ CASE_TEST(ftruncate); EXPECT_SYSZR(1, test_ftruncate()); break;
CASE_TEST(getdents64_root); EXPECT_SYSNE(1, test_getdents64("/"), -1); break;
CASE_TEST(getdents64_null); EXPECT_SYSER(1, test_getdents64("/dev/null"), -1, ENOTDIR); break;
CASE_TEST(directories); EXPECT_SYSZR(is_nolibc && proc, test_dirent()); break;
--
2.54.0