The Linux kernel writes back the remaining timeout for select-family syscalls in poll_select_finish(). If that writeback fails, it keeps the original return value.
However, QEMU only writes back the timeout on success. If the writeback fails, QEMU returns -TARGET_EFAULT. This can lose the remaining timeout and change the return value. Update do_select(), do_pselect6(), and do_ppoll() to always write back the timeout to match the Linux kernel's behavior. If the timeout writeback fails, keep the original return value. Tested with the issue reproducer. Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3343 Signed-off-by: Sun Haoyu <[email protected]> --- Changes since v2: - move the Resolves: tag above Signed-off-by - ignore timeout writeback failures directly(to match kernel) and add comments - fix indentation in do_pselect6() linux-user/syscall.c | 45 +++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 064bc604c9..0a0c2fa1eb 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -1394,14 +1394,15 @@ static abi_long do_select(int n, return -TARGET_EFAULT; if (efd_addr && copy_to_user_fdset(efd_addr, &efds, n)) return -TARGET_EFAULT; - - if (target_tv_addr) { - tv.tv_sec = ts.tv_sec; - tv.tv_usec = ts.tv_nsec / 1000; - if (copy_to_user_timeval(target_tv_addr, &tv)) { - return -TARGET_EFAULT; - } - } + } + if (target_tv_addr) { + tv.tv_sec = ts.tv_sec; + tv.tv_usec = ts.tv_nsec / 1000; + /* + * Like the kernel, we deliberately ignore possible + * failures writing back to the timeout struct. + */ + copy_to_user_timeval(target_tv_addr, &tv); } return ret; @@ -1529,14 +1530,16 @@ static abi_long do_pselect6(abi_long arg1, abi_long arg2, abi_long arg3, if (efd_addr && copy_to_user_fdset(efd_addr, &efds, n)) { return -TARGET_EFAULT; } + } + if (ts_addr) { + /* + * Like the kernel, we deliberately ignore possible + * failures writing back to the timeout struct. + */ if (time64) { - if (ts_addr && host_to_target_timespec64(ts_addr, &ts)) { - return -TARGET_EFAULT; - } + host_to_target_timespec64(ts_addr, &ts); } else { - if (ts_addr && host_to_target_timespec(ts_addr, &ts)) { - return -TARGET_EFAULT; - } + host_to_target_timespec(ts_addr, &ts); } } return ret; @@ -1606,15 +1609,15 @@ static abi_long do_ppoll(abi_long arg1, abi_long arg2, abi_long arg3, if (set) { finish_sigsuspend_mask(ret); } - if (!is_error(ret) && arg3) { + if (arg3) { + /* + * Like the kernel, we deliberately ignore possible + * failures writing back to the timeout struct. + */ if (time64) { - if (host_to_target_timespec64(arg3, timeout_ts)) { - return -TARGET_EFAULT; - } + host_to_target_timespec64(arg3, timeout_ts); } else { - if (host_to_target_timespec(arg3, timeout_ts)) { - return -TARGET_EFAULT; - } + host_to_target_timespec(arg3, timeout_ts); } } } else { -- 2.53.0
