linux-user: raw ppoll() and pselect6() lose timeout writeback on EINTR
## Host environment
- Operating system:
EndeavourOS x86_64
- OS/kernel version:
Linux ArchLinux-desktop 6.19.6-arch1-1 #1 SMP PREEMPT_DYNAMIC Wed, 04 Mar 202618:25:08 +0000 x86_64 GNU/Linux
- Architecture:
ALL
- QEMU flavor:
qemu-user(I tested qemu-x86_64 and riscv64)
- QEMU version:
10.2.1
- QEMU command line:
- qemu-x86_64 /tmp/test-04 ppoll
- qemu-x86_64 /tmp/test-04 pselect6
## Emulated/Virtualized environment
QEMU-USER
- Architecture:
ALL
## Description of problem
I can reproduce this locally with qemu-x86_64 10.2.1, and the source locations below match the 10.2.50 code layout.
The linux-user implementations of raw ppoll() and pselect6() only copy the timeout back to guest memory on non-error returns.
Relevant source locations:
- linux-user/syscall.c:1522
- linux-user/syscall.c:1609
At those locations, timeout writeback is guarded by !is_error(ret).
This is incorrect for raw syscall semantics. On Linux, raw ppoll() and pselect6() may update the timeout object even when the syscall returns -1 with errno = EINTR.
Observed output on native Linux:
ppoll_ret=-1 errno=4 remaining=0.999985506 got_sigalrm=1
pselect6_ret=-1 errno=4 remaining=0.999966902 got_sigalrm=1
Observed output under qemu-x86_64:
ppoll_ret=-1 errno=4 remaining=2.000000000 got_sigalrm=1
pselect6_ret=-1 errno=4 remaining=2.000000000 got_sigalrm=1
Expected behavior:
the remaining timeout should still be written back on EINTR, matching Linux raw syscall behavior.
Actual behavior:
QEMU preserves the original timeout and drops the remaining-time information.
This is a direct guest-visible raw-syscall semantic mismatch.
## Steps to reproduce
1. The minimal reproducer file `tests/04_ppoll_pselect6_timeout.c`.
[04_ppoll_pselect6_timeout.c](/uploads/042f2e5cab86fed0dcd428dac5ccb7b1/04_ppoll_pselect6_timeout.c)
2. Build it with `gcc -O2 -Wall -Wextra -static -o /tmp/test-04 tests/04_ppoll_pselect6_timeout.c`.
3. Run on native Linux with `/tmp/test-04 ppoll` and `/tmp/test-04 pselect6`.
4. Run under QEMU with `qemu-x86_64 /tmp/test-04 ppoll` and `qemu-x86_64 /tmp/test-04 pselect6`.
5. Compare the timeout value after the syscall returns.
The test arms alarm(1), passes a 2-second timeout, calls the raw syscall directly, and prints the timeout value after return.
## Additional information
I am a student from Xidian University.
I quickly discovered some bugs through Codex. I know that QEMU does not accept content directly generated by AI, so I have verified them and confirm that this bug is **valid**.
```c
#define _GNU_SOURCE
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t got_sigalrm;
static void on_sigalrm(int signo)
{
(void)signo;
got_sigalrm = 1;
}
static int run_ppoll(void)
{
struct timespec timeout = { .tv_sec = 2, .tv_nsec = 0 };
long ret;
int saved_errno;
got_sigalrm = 0;
alarm(1);
ret = syscall(SYS_ppoll, NULL, 0UL, &timeout, NULL, 0UL);
saved_errno = errno;
alarm(0);
printf("ppoll_ret=%ld errno=%d remaining=%lld.%09ld got_sigalrm=%d\n",
ret, saved_errno, (long long)timeout.tv_sec,
timeout.tv_nsec, got_sigalrm ? 1 : 0);
return 0;
}
static int run_pselect6(void)
{
struct timespec timeout = { .tv_sec = 2, .tv_nsec = 0 };
long ret;
int saved_errno;
got_sigalrm = 0;
alarm(1);
ret = syscall(SYS_pselect6, 0, NULL, NULL, NULL, &timeout, NULL);
saved_errno = errno;
alarm(0);
printf("pselect6_ret=%ld errno=%d remaining=%lld.%09ld got_sigalrm=%d\n",
ret, saved_errno, (long long)timeout.tv_sec,
timeout.tv_nsec, got_sigalrm ? 1 : 0);
return 0;
}
int main(int argc, char **argv)
{
struct sigaction sa;
if (argc != 2) {
fprintf(stderr, "usage: %s ppoll|pselect6\n", argv[0]);
return 2;
}
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_sigalrm;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) < 0) {
perror("sigaction");
return 1;
}
if (strcmp(argv[1], "ppoll") == 0) {
return run_ppoll();
}
if (strcmp(argv[1], "pselect6") == 0) {
return run_pselect6();
}
fprintf(stderr, "usage: %s ppoll|pselect6\n", argv[0]);
return 2;
}
```
issue