[PATCH v3 1/2] tools/nolibc: add ftruncate()

Jordan Richards posted 2 patches 4 weeks, 1 day ago
[PATCH v3 1/2] tools/nolibc: add ftruncate()
Posted by Jordan Richards 4 weeks, 1 day ago
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.

Signed-off-by: Jordan Richards <jordanrichards@google.com>
---
 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
+#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
+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
+#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)
+{
+
+	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);
+	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;
+	}
+
+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
Re: [PATCH v3 1/2] tools/nolibc: add ftruncate()
Posted by Thomas Weißschuh 4 weeks, 1 day ago
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 <jordanrichards@google.com>
> ---
>  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
>