Skip to content

timeout: deliver real-time signals to monitored commands - #15484

Open
limadog9 wants to merge 1 commit into
uutils:mainfrom
limadog9:fix/timeout-realtime-delivery-12975
Open

limadog9 wants to merge 1 commit into
uutils:mainfrom
limadog9:fix/timeout-realtime-delivery-12975

Conversation

@limadog9

Copy link
Copy Markdown
Contributor

timeout accepted RTMIN/RTMAX names but ChildExt rejected their signal numbers, causing unnecessary KILL escalation. Send raw signal numbers to processes and process groups, and unblock real-time signals safely when preserving a child's termination. Add delivery and exit-signal regressions.

Validation: 35 timeout integration tests pass, including real-time signal delivery and preservation of the child's exit signal.

Closes #12975

Comment on lines +62 to +75
let old_handler = unsafe { libc::signal(signal, libc::SIG_IGN) };
if old_handler == libc::SIG_ERR {
return Err(io::Error::last_os_error());
}
// SAFETY: the signal was validated above and group zero is our group.
let result = if unsafe { libc::kill(0, signal) } == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
};
// Restore the old handler
let _ = unsafe { nix_signal::signal(signal, old_handler) };
result.map_err(|e| io::Error::from_raw_os_error(e as i32))
// SAFETY: old_handler was returned by a successful signal call.
let _ = unsafe { libc::signal(signal, old_handler) };
result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need unsafe here ? why not use nix ?

Comment on lines +128 to +134
if unsafe { libc::sigaddset(&raw mut set, signal.as_raw()) } == -1 {
return Err(io::Error::last_os_error());
}
// SAFETY: the set was initialized by sigemptyset and changed by sigaddset.
unsafe { SigSet::from_sigset_t_unchecked(set) }
.thread_unblock()
.map_err(Into::into)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Comment on lines +45 to +57
let signal = libc::c_int::try_from(signal)
.map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;

// Signal 0 is special - it just checks if process exists, doesn't send anything.
// No need to manipulate signal handlers for it.
if signal == 0 {
return nix_signal::kill(Pid::from_raw(0), None)
.map_err(|e| io::Error::from_raw_os_error(e as i32));
// SAFETY: process group zero denotes the caller's group; no signal
// is delivered by this existence check.
return if unsafe { libc::kill(0, 0) } == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use nix here ?

@limadog9
limadog9 force-pushed the fix/timeout-realtime-delivery-12975 branch from c48d867 to 644f35a Compare October 18, 2026 17:36
Comment on lines +68 to +91
let old_handler = unsafe { signal::signal(signal, SigHandler::SigIgn) }?;
let result = signal::kill(Pid::from_raw(0), signal).map_err(Into::into);
// SAFETY: restore the handler returned by the successful call above.
let _ = unsafe { signal::signal(signal, old_handler) };
return result;
}

// Ignore the signal temporarily so we don't receive it ourselves.
let old_handler = unsafe { nix_signal::signal(signal, SigHandler::SigIgn) }
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
let result = nix_signal::kill(Pid::from_raw(0), Some(signal));
// nix cannot represent real-time signals in its Signal enum.
// SAFETY: SIG_IGN is a valid handler; signal validates the raw number.
let old_handler = unsafe { libc::signal(signal, libc::SIG_IGN) };
if old_handler == libc::SIG_ERR {
return Err(io::Error::last_os_error());
}
// SAFETY: the signal was validated above and group zero is our group.
let result = if unsafe { libc::kill(0, signal) } == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
};
// Restore the old handler
let _ = unsafe { nix_signal::signal(signal, old_handler) };
result.map_err(|e| io::Error::from_raw_os_error(e as i32))
// SAFETY: old_handler was returned by a successful signal call.
let _ = unsafe { libc::signal(signal, old_handler) };
result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's quite a lot of duplication there and I believe you can use the safe signal code here instead of unsafe (just need a cast for kill; which is fine)

return Err(io::Error::last_os_error());
}
// SAFETY: the set was initialized by sigemptyset and changed by sigaddset.
unsafe { SigSet::from_sigset_t_unchecked(set) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cspell warnings

timeout accepted RTMIN/RTMAX names but ChildExt rejected their signal
numbers, causing unnecessary KILL escalation. Send raw signal numbers to
processes and process groups, and unblock real-time signals safely when
preserving a child's termination. Add delivery and exit-signal
regressions.

Validation: 35 timeout integration tests pass, including real-time
signal delivery and preservation of the child's exit signal.

Closes uutils#12975
@limadog9
limadog9 force-pushed the fix/timeout-realtime-delivery-12975 branch from 644f35a to e82dbdd Compare October 18, 2026 18:39

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

timeout:fails to send RTMIN/RTMAX signals through ChildExt

2 participants