Hi Jordan,
On 2026-03-03 01:00:38+0000, Jordan Richards wrote:
> 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.
Thanks, this all looks good in general.
In nolibc-next we renamed all the my_syscall*() functions to
__nolibc_syscall*(), merging this patch through the liveupdate tree
will lead to semantic conflicts.
I see different ways to handle this:
* An an immutable branch to merge into the liveupdate tree
* Split up the series
* Use raw syscall(__NR_ftruncate) in the liveupdate
selftests, as it is sufficient for that.
* Merge the nolibc bits through the nolibc tree.
* Next cycle, switch to proper nolibc ftruncate().
* Let Linus handle the conflict when merging the tree.
There are some nitpicks inline, but we can also fix those up when
applying the patch.
> Signed-off-by: Jordan Richards <[email protected]>
> ---
> tools/include/nolibc/arch-arm.h | 11 +++++
> tools/include/nolibc/arch-mips.h | 11 +++++
> tools/include/nolibc/arch-powerpc.h | 11 +++++
> tools/include/nolibc/unistd.h | 35 ++++++++++++++
> tools/testing/selftests/nolibc/nolibc-test.c | 51 ++++++++++++++++++++
> 5 files changed, 119 insertions(+)
>
> diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h
> index 251c42579028..ed65fb674e61 100644
> --- a/tools/include/nolibc/arch-arm.h
> +++ b/tools/include/nolibc/arch-arm.h
> @@ -6,9 +6,11 @@
>
> #ifndef _NOLIBC_ARCH_ARM_H
> #define _NOLIBC_ARCH_ARM_H
Nit: keep an empty line after the include guards.
> +#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 +198,13 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint
> __no_stack_protector _s
> }
> #endif /* NOLIBC_NO_RUNTIME */
>
> +#ifdef __NR_ftruncate64
Should be unnecessary.
> +static __attribute__((unused))
> +int sys_ftruncate64(int fd, uint32_t length0, uint32_t length1)
> +{
> + return my_syscall4(__NR_ftruncate64, fd, 0, length0, length1);
> +}
> +#define sys_ftruncate64 sys_ftruncate64
> +#endif
> +
> #endif /* _NOLIBC_ARCH_ARM_H */
> diff --git a/tools/include/nolibc/arch-mips.h
> b/tools/include/nolibc/arch-mips.h
> index a72506ceec6b..26d044004ec6 100644
> --- a/tools/include/nolibc/arch-mips.h
> +++ b/tools/include/nolibc/arch-mips.h
> @@ -6,9 +6,11 @@
>
> #ifndef _NOLIBC_ARCH_MIPS_H
> #define _NOLIBC_ARCH_MIPS_H
Also an empty line.
> +#include <linux/unistd.h>
> #include "compiler.h"
> #include "crt.h"
> +#include "std.h"
>
> #if !defined(_ABIO32) && !defined(_ABIN32) && !defined(_ABI64)
> #error Unsupported MIPS ABI
> @@ -269,4 +271,13 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint
> __no_stack_protector __
> }
> #endif /* NOLIBC_NO_RUNTIME */
>
> +#ifdef __NR_ftruncate64
> +static __attribute__((unused))
> +int sys_ftruncate64(int fd, uint32_t length0, uint32_t length1)
> +{
> + return my_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 e0c7e0b81f7c..71829cb027e8 100644
> --- a/tools/include/nolibc/arch-powerpc.h
> +++ b/tools/include/nolibc/arch-powerpc.h
> @@ -6,9 +6,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 +220,13 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint
> __no_stack_protector _s
> }
> #endif /* NOLIBC_NO_RUNTIME */
>
> +#ifdef __NR_ftruncate64
> +static __attribute__((unused))
> +int sys_ftruncate64(int fd, uint32_t length0, uint32_t length1)
> +{
> + return my_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 bb5e80f3f05d..85ac253f9189 100644
> --- a/tools/include/nolibc/unistd.h
> +++ b/tools/include/nolibc/unistd.h
> @@ -48,6 +48,41 @@ 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)
> +{
> +
Nit: Spurious empty line.
> + return my_syscall3(__NR_ftruncate64, fd, length0, length1);
> +}
> +#define sys_ftruncate64 sys_ftruncate64
> +#endif
> +
> +static __attribute__((unused))
> +int sys_ftruncate(int fd, off_t length)
> +{
> +#ifdef sys_ftruncate64
> + union {
> + off_t length;
> + struct {
> + uint32_t length0;
> + uint32_t length1;
> + };
> + } arg;
> +
> + arg.length = length;
> +
> + return sys_ftruncate64(fd, arg.length0, arg.length1);
> +#else
> + return my_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 1b9d3b2e2491..6a84dfbb4b73 100644
> --- a/tools/testing/selftests/nolibc/nolibc-test.c
> +++ b/tools/testing/selftests/nolibc/nolibc-test.c
> @@ -969,6 +969,56 @@ int test_fork(enum fork_type type)
> }
> }
>
> +int test_ftruncate(void)
> +{
> + int ret;
> + int fd;
> + struct stat stat_buf;
> + const char *filename = "/tmp/ftruncate_test";
> +
> + ret = ftruncate(-1, 0);
> + if (ret != -1 || errno != EBADF) {
> + errno = EINVAL;
> + return __LINE__;
> + }
> +
> + fd = open(filename, O_RDWR | O_CREAT);
Missing the mode argument.
O_TMPFILE would make things easier.
> + if (fd == -1)
> + return __LINE__;
> +
> + 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 = stat_buf.st_size;
> + goto end;
> + }
Could you also add a variant which tests that 64-bit values work correctly?
> +
> +end:
> + close(fd);
> + unlink(filename);
> +
> + return ret;
> +}
> +
> int test_stat_timestamps(void)
> {
> struct stat st;
> @@ -1406,6 +1456,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(proc,
> test_dirent()); break;
> --
> 2.53.0.473.g4a7958ca14-goog
>