1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
use alloc::{borrow::Cow, sync::Arc};
use core::{
mem,
sync::atomic::{AtomicBool, Ordering},
};
use axpoll::{IoEvents, Pollable};
use axpoll_set::PollSet;
use starry_signal::{SignalInfo, SignalSet};
use zerocopy::{Immutable, IntoBytes};
use crate::{
StarryError, StarryResult,
file::{FileLike, IoDst, IoSrc},
sync::IrqMutex,
task::{
current_user_task,
future::{block_on_user, poll_io},
},
};
/// The size of signalfd_siginfo structure (128 bytes as per Linux
/// specification)
const SIGNALFD_SIGINFO_SIZE: usize = 128;
/// signalfd_siginfo structure layout
/// This matches the Linux signalfd_siginfo structure (128 bytes)
#[repr(C)]
#[derive(Immutable, IntoBytes)]
struct SignalfdSiginfo {
ssi_signo: u32, // Signal number
ssi_errno: i32, // Error number (unused)
ssi_code: i32, // Signal code
ssi_pid: u32, // PID of sender
ssi_uid: u32, // Real UID of sender
ssi_fd: i32, // File descriptor (SIGIO)
ssi_tid: u32, // Kernel timer ID (POSIX timers)
ssi_band: u32, // Band event (SIGIO)
ssi_overrun: u32, // POSIX timer overrun count
ssi_trapno: u32, // Trap number that caused signal
ssi_status: i32, // Exit status or signal (SIGCHLD)
ssi_int: i32, // Integer sent by sigqueue(2)
ssi_ptr: u64, // Pointer sent by sigqueue(2)
ssi_utime: u64, // User CPU time consumed (SIGCHLD)
ssi_stime: u64, // System CPU time consumed (SIGCHLD)
ssi_addr: u64, // Address that generated signal
ssi_addr_lsb: u16, // Least significant bit of address
_pad: [u8; 46], // Padding to make it 128 bytes
}
const _: [(); SIGNALFD_SIGINFO_SIZE] = [(); mem::size_of::<SignalfdSiginfo>()];
impl SignalfdSiginfo {
/// Convert from SignalInfo to signalfd_siginfo
fn from_signal_info(sig_info: &SignalInfo) -> Self {
let errno = sig_info.errno();
SignalfdSiginfo {
ssi_signo: sig_info.signo() as u32,
ssi_errno: errno,
ssi_code: sig_info.code(),
ssi_pid: sig_info.pid(),
ssi_uid: sig_info.uid(),
ssi_fd: -1,
ssi_tid: 0,
ssi_band: 0,
ssi_overrun: sig_info.timer_overrun(),
ssi_trapno: 0,
ssi_status: 0,
ssi_int: 0,
ssi_ptr: 0,
ssi_utime: 0,
ssi_stime: 0,
ssi_addr: 0,
ssi_addr_lsb: 0,
_pad: [0u8; 46],
}
}
}
pub struct Signalfd {
// SignalSet is a single Copy bitset, so a short project-visible spin lock
// is enough for now. Revisit this when a lockdep-aware project RwLock exists.
mask: IrqMutex<SignalSet>,
non_blocking: AtomicBool,
poll_rx: PollSet,
}
impl Signalfd {
pub fn new(mask: SignalSet) -> Arc<Self> {
Arc::new(Self {
mask: IrqMutex::new(mask),
non_blocking: AtomicBool::new(false),
poll_rx: PollSet::new(),
})
}
pub fn update_mask(&self, mask: SignalSet) {
{
*self.mask.lock() = mask;
}
// The signal mask update is visible before waking readers.
unsafe { self.poll_rx.wake(IoEvents::IN) };
}
fn mask(&self) -> SignalSet {
*self.mask.lock()
}
/// Check if there are any pending signals matching the mask
fn has_pending_signals(&self) -> bool {
let mask = self.mask();
let curr = current_user_task();
let signal = curr.as_thread().signal();
let pending = signal.pending();
!(pending & mask).is_empty()
}
/// Dequeue a signal matching the mask
fn dequeue_signal(&self) -> Option<SignalInfo> {
let mask = self.mask();
let curr = current_user_task();
let signal = curr.as_thread().signal();
signal.dequeue_signal(&mask)
}
}
impl FileLike for Signalfd {
fn validate_write_access(&self) -> StarryResult {
// Linux rejects signalfd writes because the descriptor has no write
// operation. This check precedes user-buffer validation, so EINVAL
// also takes priority over EFAULT for an invalid source pointer.
Err(StarryError::InvalidInput)
}
fn read(&self, dst: &mut IoDst) -> StarryResult<usize> {
if dst.remaining_mut() < SIGNALFD_SIGINFO_SIZE {
return Err(StarryError::InvalidInput);
}
let max_records = dst.remaining_mut() / SIGNALFD_SIGINFO_SIZE;
let task = current_user_task();
block_on_user(
&task,
poll_io(self, IoEvents::IN, self.nonblocking(), || {
if let Some(mut sig_info) = self.dequeue_signal() {
let mut written = 0;
loop {
let sfd_info = SignalfdSiginfo::from_signal_info(&sig_info);
dst.write(sfd_info.as_bytes())?;
written += SIGNALFD_SIGINFO_SIZE;
if written / SIGNALFD_SIGINFO_SIZE == max_records {
break;
}
let Some(next_sig_info) = self.dequeue_signal() else {
break;
};
sig_info = next_sig_info;
}
// Wake up other waiters if there are more signals pending
if self.has_pending_signals() {
// Remaining pending signals are visible before re-wake.
unsafe { self.poll_rx.wake(IoEvents::IN) };
}
Ok(written)
} else {
Err(crate::StarryError::WouldBlock)
}
}),
)
.into_result()?
}
fn write(&self, _src: &mut IoSrc) -> StarryResult<usize> {
// Linux signalfd descriptors reject write(2) through the file
// operation with EINVAL, rather than treating the live fd as bad.
Err(StarryError::InvalidInput)
}
fn nonblocking(&self) -> bool {
self.non_blocking.load(Ordering::Acquire)
}
fn set_nonblocking(&self, non_blocking: bool) -> StarryResult {
self.non_blocking.store(non_blocking, Ordering::Release);
Ok(())
}
fn path(&self) -> Cow<'_, str> {
"anon_inode:[signalfd]".into()
}
}
impl Pollable for Signalfd {
fn poll(&self) -> IoEvents {
let mut events = IoEvents::empty();
events.set(IoEvents::IN, self.has_pending_signals());
events
}
unsafe fn register_shared(
&self,
sink: &mut dyn axpoll::SharedRegistrationSink,
events: IoEvents,
) {
if events.contains(IoEvents::IN) {
// The private poll set covers mask updates and additional queued
// signals. New signal delivery wakes the current thread's shared
// signalfd poll set, so an already-blocked epoll waiter must be
// registered with both sources.
unsafe {
sink.register_shared(&self.poll_rx, IoEvents::IN);
sink.register_shared(
current_user_task().as_thread().signalfd_poll_source(),
IoEvents::IN,
);
}
}
}
unsafe fn register_exclusive(
&self,
sink: &mut dyn axpoll::ExclusiveRegistrationSink,
events: IoEvents,
) {
if events.contains(IoEvents::IN) {
unsafe {
sink.register_exclusive(&self.poll_rx, IoEvents::IN);
sink.register_exclusive(
current_user_task().as_thread().signalfd_poll_source(),
IoEvents::IN,
);
}
}
}
}