devela 0.27.0

A development layer of coherence.
Documentation
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
// devela::sys::os::linux::namespace
//
//! Defines the [`Linux`] namespace.
//

#[cfg(all(feature = "unsafe_syscall", not(miri)))]
use crate::{
    AtomicOrdering, AtomicPtr, Duration, LINUX_ERRNO as ERRNO, LINUX_EXIT, LINUX_FILENO as FILENO,
    LINUX_IOCTL as IOCTL, LINUX_SIGACTION as SIGACTION, LinuxClock, LinuxError,
    LinuxResult as Result, LinuxSigaction, LinuxSiginfo, LinuxSigset, LinuxTermios, LinuxTimespec,
    MaybeUninit, Ptr, ScopeGuard, Str, TermSize, c_int, c_uint, c_void, is, transmute,
};

#[cfg(all(feature = "unsafe_syscall", feature = "alloc", not(miri)))]
use crate::Vec;

#[doc = crate::_tags!(linux namespace)]
/// Linux-related operations.
#[doc = crate::_doc_location!("sys/os/linux")]
///
/// # Features
/// All the methods depend on the features `linux` and `unsafe_syscall`.
///
/// # Methods
/// - [read](#read-related-methods)
/// - [write](#write-related-methods)
/// - [term](#terminal-related-methods)
/// - [thread](#thread-related-methods)
/// - [signal](#signaling-related-methods)
/// - [random](#randomness-related-methods)
/// - syscalls:
///   - [file-descriptors](#syscalls-file-descriptors)
///   - [filesystem](#syscalls-filesystem)
///   - [device and special I/O](#syscalls-device-and-special-io)
///   - [IPC](#syscalls-ipc)
///   - [process control](#syscalls-process-control)
///   - [timing and signal handling](#syscalls-timing-and-signal-handling)
#[derive(Debug)]
pub struct Linux;

/// # Read-related methods.
#[rustfmt::skip]
#[cfg(all(feature = "unsafe_syscall", not(miri)))]
impl Linux {
    /// Checks if there is input available to read from stdin.
    pub fn has_input() -> bool { Self::available_bytes().unwrap_or(0) > 0 }

    /// Returns the number of bytes available to be read from *stdin*.
    pub fn available_bytes() -> Result<usize> {
        let mut n = 0;
        let result = unsafe {
            Linux::sys_ioctl(FILENO::STDIN, IOCTL::FIONREAD, &mut n as *mut i32 as *mut u8)
        };
        if result < 0 { Err(LinuxError::Sys(result)) } else { Ok(n as usize) }
    }

    /// Gets a single byte from *stdin*.
    pub fn get_byte() -> Result<u8> {
        let mut c = 0;
        loop {
            let n = unsafe { Linux::sys_read(FILENO::STDIN, &mut c as *mut u8, 1) };
            if n < 0 { return Err(LinuxError::Sys(n)); }
            if n == 1 { break; }
        }
        Ok(c)
    }

    /// Attempts to get a single byte from stdin without blocking.
    ///
    /// Checks if there is input available using `has_input`,
    /// and if so, reads a byte using `get_byte`.
    ///
    /// # Returns
    /// - `Some(u8)` if a byte is successfully read.
    /// - `None` if no data is available.
    pub fn try_get_byte() -> Option<u8> {
        if Linux::has_input() { Linux::get_byte().ok() } else { None }
    }

    /// Pauses execution until receiving from *stdin* any `char` in the `list`.
    pub fn pause_until_char(list: &[char]) {
        loop { if list.contains(&Linux::get_dirty_char()) { break; } }
    }

    /// Gets a single `char` from *stdin*.
    ///
    /// # Returns
    /// - `Ok(char)` if a valid UTF-8 character is read.
    /// - `Err(LinuxError::InvalidUtf8)` if the bytes are not valid UTF-8.
    /// - `Err(LinuxError::NoInput)` if the UTF-8 sequence is valid but empty.
    pub fn get_char() -> Result<char> {
        let bytes = Linux::get_utf8_bytes()?;
        let s = unsafe { Str::from_utf8_unchecked(&bytes) };
        s.chars().next().ok_or(LinuxError::NoInput)
    }

    /// Gets either a single `char` from *stdin*, or the replacement character.
    ///
    /// If the bytes received doesn't form a valid unicode scalar then the
    /// [replacement character (�)][char::REPLACEMENT_CHARACTER] will be returned.
    pub fn get_dirty_char() -> char {
        match Linux::get_utf8_bytes() {
            Ok(bytes) => {
                let s = unsafe { Str::from_utf8_unchecked(&bytes) };
                s.chars().next().unwrap()
            }
            Err(_) => char::REPLACEMENT_CHARACTER,
        }
    }

    /// Gets a UTF-8 encoded byte sequence from *stdin* representing a `char`.
    pub fn get_utf8_bytes() -> Result<[u8; 4]> {
        let mut bytes = [0u8; 4];
        let len;
        // read the first byte to determine the length of the character
        bytes[0] = Linux::get_byte()?;
        if bytes[0] & 0x80 == 0 { return Ok([bytes[0], 0, 0, 0]); } // ASCII char
        // IMPROVE: use text module functionality
        else if bytes[0] & 0xE0 == 0xC0 { len = 2; }
        else if bytes[0] & 0xF0 == 0xE0 { len = 3; }
        else if bytes[0] & 0xF8 == 0xF0 { len = 4; }
        else { return Err(LinuxError::InvalidUtf8) }
        // read the remaining bytes of the character
        for i in 1..len {
            bytes[i as usize] = Linux::get_byte()?;
            if bytes[i as usize] & 0xC0 != 0x80 { return Err(LinuxError::InvalidUtf8); }
        }
        Ok(bytes)
    }

    /// Reads all available bytes from stdin.
    ///
    /// # Returns
    /// - `Ok(Vec<u8>)` containing the bytes read.
    /// - `Err(isize)` if the read fails, returning the error code.
    #[cfg(feature = "alloc")]
    #[cfg_attr(nightly_doc, doc(cfg(feature = "alloc")))]
    pub fn read_available_bytes() -> Result<Vec<u8>> {
        let count = Linux::available_bytes()?;
        if count == 0 { return Ok(Vec::new()); }
        let mut buffer = crate::vec_![0u8; count];
        let n = unsafe { Linux::sys_read(FILENO::STDIN, buffer.as_mut_ptr(), count) };
        if n < 0 { Err(LinuxError::Sys(n)) } else { buffer.truncate(n as usize); Ok(buffer) }
    }

    /// Prompts the user for a string from *stdin*, backed by a `buffer`.
    ///
    /// # Examples
    /// ```no_run
    /// # use devela::Linux;
    /// let mut name_buffer = [0_u8; 32];
    /// let name: &str = Linux::prompt::<32>("Enter your name: ", &mut name_buffer).unwrap();
    /// ```
    pub fn prompt<'i, const CAP: usize>(text: &str, buffer: &'i mut [u8; CAP]) -> Result<&'i str> {
        Linux::print(text)?; Linux::get_line(buffer)
    }

    /// Gets a string from *stdin* backed by a `buffer`, until a newline.
    ///
    /// # Examples
    /// ```no_run
    /// # use devela::Linux;
    /// let mut buf = [0_u8; 32];
    /// let name: &str = Linux::get_line::<32>(&mut buf).unwrap();
    /// ```
    pub fn get_line<const CAP: usize>(buffer: &mut [u8; CAP]) -> Result<&str> {
        Linux::get_str(buffer, '\n')
    }

    /// Gets a string from *stdin* backed by a `buffer`, until the `stop` char is received.
    ///
    /// # Examples
    /// ```no_run
    /// # use devela::Linux;
    /// let mut buf = [0_u8; 32];
    /// let name: &str = Linux::get_str::<32>(&mut buf, '\n').unwrap();
    /// ```
    pub fn get_str<const CAP: usize>(buffer: &mut [u8; CAP], stop: char) -> Result<&str> {
        let mut index = 0;
        loop {
            if let Ok(c) = Linux::get_char() {
                let mut c_buf = [0; 4];
                let c_str = c.encode_utf8(&mut c_buf);
                if c == stop {
                    break;
                } else if index + c_str.len() <= CAP {
                    // Linux::print(c_str)?;
                    for &b in c_str.as_bytes() {
                        buffer[index] = b;
                        index += 1;
                    }
                }
            }
        }
        Ok(unsafe { Str::from_utf8_unchecked(&buffer[..index]) })
    }
}

/// # Write-related methods.
#[rustfmt::skip]
#[cfg(all(feature = "unsafe_syscall", not(miri)))]
impl Linux {
    /* core helpers */

    const STACK_BUFFER_LEN: usize = 512; // PIPE_BUF is typically 4096 but 512 is safe

    /// Writes all bytes to a file descriptor, handling partial writes.
    fn write_all(fd: c_int, mut buf: &[u8]) -> Result<()> {
        while !buf.is_empty() {
            let n = unsafe { Linux::sys_write(fd, buf.as_ptr(), buf.len()) };
            if n < 0 { return Err(LinuxError::Sys(n)); }
            buf = &buf[n as usize..];
        }
        Ok(())
    }
    /// Writes all bytes to stdout. Returns error on syscall failure.
    fn write_all_unchecked(fd: c_int, mut buf: &[u8]) {
        while !buf.is_empty() {
            let n = unsafe { Linux::sys_write(fd, buf.as_ptr(), buf.len()) };
            if n <= 0 { panic!("write failed with return value {n}"); }
            buf = &buf[n as usize..];
        }
    }

    /* public methods */

    /// Writes string + newline to stdout. Optimized for small strings.
    pub fn print(s: &str) -> Result<()> {
        Self::write_all(FILENO::STDOUT, s.as_bytes())
    }
    /// Like `print`, but panics on failure instead of returning errors.
    pub fn print_unchecked(s: &str) { Self::write_all_unchecked(FILENO::STDOUT, s.as_bytes()); }

    /// Writes string + newline to stdout. Optimized for small strings.
    pub fn println(s: &str) -> Result<()> {
        if s.len() <= Self::STACK_BUFFER_LEN { // small string optimization:
            let mut buf = [0u8; Self::STACK_BUFFER_LEN];
            let bytes = s.as_bytes();
            buf[..bytes.len()].copy_from_slice(bytes);
            buf[bytes.len()] = b'\n';
            Self::write_all(FILENO::STDOUT, &buf[..bytes.len() + 1])
        } else { // fallback for large strings:
            Self::write_all(FILENO::STDOUT, s.as_bytes())?;
            Self::write_all(FILENO::STDOUT, b"\n")
        }
    }
    /// Like `println`, but panics on failure instead of returning errors.
    pub fn println_unchecked(s: &str) {
        if s.len() <= Self::STACK_BUFFER_LEN {
            let mut buf = [0u8; Self::STACK_BUFFER_LEN];
            let bytes = s.as_bytes();
            buf[..bytes.len()].copy_from_slice(bytes);
            buf[bytes.len()] = b'\n';
            Self::write_all_unchecked(FILENO::STDOUT, &buf[..bytes.len() + 1]);
        } else {
            Self::write_all_unchecked(FILENO::STDOUT, s.as_bytes());
            Self::write_all_unchecked(FILENO::STDOUT, b"\n");
        }
    }
    /// Ultra-fast stdout write. Panics if not all bytes are written at once.
    /// Only use for small strings (<512 bytes).
    pub fn print_unchecked_fast(s: &str) {
        let bytes = s.as_bytes();
        let n = unsafe { Linux::sys_write(FILENO::STDOUT, bytes.as_ptr(), bytes.len()) };
        if n != bytes.len() as isize {
            panic!("write failed with return value {n}");
        }
    }
    /// Ultra-fast stdout write with newline. Panics if write isn't atomic.
    /// Only use for small strings (<511 bytes).
    pub fn println_unchecked_fast(s: &str) {
        let bytes = s.as_bytes();
        let mut buf = MaybeUninit::<[u8; Self::STACK_BUFFER_LEN]>::uninit();
        // SAFETY: we're about to initialize all accessed bytes
        unsafe {
            let buf_ptr = buf.as_mut_ptr() as *mut u8;
            core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
            *buf_ptr.add(bytes.len()) = b'\n'; // Add newline
            let n = Linux::sys_write(FILENO::STDOUT, buf_ptr, bytes.len() + 1); // single syscall
            if n != (bytes.len() + 1) as isize { panic!("write failed with return value {n}"); }
        }
    }
    /// Writes bytes to stdout. Returns error on syscall failure.
    pub fn print_bytes(b: &[u8]) -> Result<()> { Self::write_all(FILENO::STDOUT, b) }
    /// Like `print_bytes`, but panics on failure instead of returning errors.
    pub fn print_bytes_unchecked(b: &[u8]) { Self::write_all_unchecked(FILENO::STDOUT, b); }

    /// Writes all bytes to stderr. Returns error on syscall failure.
    pub fn eprint(s: &str) -> Result<()> { Self::write_all(FILENO::STDERR, s.as_bytes()) }
    /// Writes string + newline to stderr. Optimized for small strings.
    pub fn eprintln(s: &str) -> Result<()> {
        if s.len() <= Self::STACK_BUFFER_LEN { // small string optimization:
            let mut buf = [0u8; Self::STACK_BUFFER_LEN];
            let bytes = s.as_bytes();
            buf[..bytes.len()].copy_from_slice(bytes);
            buf[bytes.len()] = b'\n';
            Self::write_all(FILENO::STDERR, &buf[..bytes.len() + 1])
        } else { // fallback for large strings:
            Self::write_all(FILENO::STDERR, s.as_bytes())?;
            Self::write_all(FILENO::STDERR, b"\n")
        }
    }
    /// Writes bytes to stderr. Returns error on syscall failure.
    pub fn eprint_bytes(b: &[u8]) -> Result<()> {
        Self::write_all(FILENO::STDERR, b)
    }
}

/// # Terminal-related methods.
#[rustfmt::skip]
#[cfg(all(feature = "unsafe_syscall", not(miri)))]
impl Linux {
    /// Returns `true` if we are in a terminal context.
    #[must_use]
    pub fn is_terminal() -> bool { LinuxTermios::is_terminal() }

    /// Returns the terminal dimensions.
    pub fn terminal_size() -> Result<TermSize> { LinuxTermios::get_winsize() }

    /// Enables raw mode.
    ///
    /// Raw mode is a way to configure the terminal so that it does not process or
    /// interpret any of the input but instead passes it directly to the program.
    pub fn enable_raw_mode() -> Result<()> { LinuxTermios::enable_raw_mode() }

    /// Disables raw mode.
    pub fn disable_raw_mode() -> Result<()> { LinuxTermios::disable_raw_mode() }

    /// Enables raw mode and returns a `ScopeGuard` that restores the original state on drop.
    pub fn scoped_raw_mode()
        -> Result<ScopeGuard<LinuxTermios, impl FnOnce(LinuxTermios, &()), ()>> {
        let initial_state = LinuxTermios::get_state()?;
        LinuxTermios::enable_raw_mode()?;

        Ok(ScopeGuard::with(initial_state, (), |state, ()| {
            LinuxTermios::set_state(state).unwrap();
        }))
    }

    // pub fn scoped_raw_mode() -> Result<ScopeGuard<LinuxTermios, impl FnOnce(LinuxTermios), &()>> {
    //     let initial_state = LinuxTermios::get_state()?;
    //     LinuxTermios::enable_raw_mode()?;
    //     Ok(ScopeGuard::new(initial_state, |state| {
    //         LinuxTermios::set_state(state).unwrap();
    //     }))
    //
    //     // Ok(devela::ScopeGuard::new(move || {
    //     //     LinuxTermios::set_state(initial_state).unwrap();
    //     // }))
    // }
}

/// # Thread-related methods.
#[rustfmt::skip]
#[cfg(all(feature = "unsafe_syscall", not(miri)))]
impl Linux {
    /// Gets clock resolution for the specified clock.
    ///
    /// It typically returns 1 ns even though the clock resolution may be coarser.
    pub fn clock_getres(clock_id: LinuxClock) -> Result<LinuxTimespec> {
        let mut res = LinuxTimespec::default();
        let ret = unsafe {
            Self::sys_clock_getres(clock_id, res.as_mut_ptr())
        };
        if ret == 0 {
            Ok(res)
        } else {
            Err(LinuxError::Sys(ret))
        }
    }

    /// Gets the current time from the specified clock
    pub fn clock_gettime(clock_id: LinuxClock) -> Result<LinuxTimespec> {
        let mut tp = LinuxTimespec::default();
        let ret = unsafe { Self::sys_clock_gettime(clock_id, tp.as_mut_ptr()) };
        is![ret == 0, Ok(tp), Err(LinuxError::Sys(ret))]
    }

    /// Suspends execution of calling thread for the given `duration`.
    pub fn sleep(duration: Duration) -> Result<()> {
        let mut req = LinuxTimespec::with_saturating_duration(duration);
        let mut rem = LinuxTimespec::default();
        loop {
            let n = unsafe { Linux::sys_nanosleep(req.as_ptr(), rem.as_mut_ptr()) };
            if n == 0 {
                break Ok(()); // Success
            } else if n == -ERRNO::EINTR {
                req = rem; // Retry with remaining time
                continue;
            } else { // Actual error
                return Err(LinuxError::Sys(n));
            }
        }
    }
    /// Suspends execution of calling thread for the given `milliseconds`.
    pub fn sleep_ms(milliseconds: u64) -> Result<()> {
        Linux::sleep(Duration::from_millis(milliseconds))
    }

    /// Returns the current process number.
    #[must_use]
    #[inline(always)]
    pub fn getpid() -> c_int {
        unsafe { Linux::sys_getpid() }
    }

    /// Terminates the process with a normalized exit `status` (0–255).
    ///
    /// See also [`LINUX_EXIT`].
    #[inline(always)]
    pub fn exit(status: c_int) -> ! {
        unsafe { Linux::sys_exit(status & LINUX_EXIT::MAX); }
    }
}

/* signals */

#[cfg(feature = "unsafe_syscall")]
crate::items! {
    /// Maximum number of signals.
    const LINUX_SIG_MAX: usize = 30;
    /// A static array to match signals with handlers.
    ///
    /// NOTE: This stores the addresses of the functions, not pointers to them.
    static LINUX_SIG_HANDLERS: [AtomicPtr<fn(i32)>; LINUX_SIG_MAX] = {
        [const {AtomicPtr::new(Ptr::null_mut()) }; LINUX_SIG_MAX]
    };
}

/// # Signaling-related methods.
#[rustfmt::skip]
#[cfg(all(feature = "unsafe_syscall", not(miri)))]
impl Linux {
    /// Registers multiple signals using a handler function.
    ///
    /// # Notes
    /// - Unknown flags in the `flags` slice are ignored, and a warning is printed.
    ///
    /// # Arguments
    /// - `handler`: A function that will be called when one of the specified signals is received.
    ///   The function takes a single argument for the signal number.
    /// - `signals`: A slice of [`LINUX_SIGNAL`][super::LINUX_SIGNAL] values specifying the signals to handle.
    /// - `flags`: An optional slice of [`LINUX_SIGACTION`][super::LINUX_SIGACTION] flags.
    ///   If `None`, only the `SA_RESTORER` flag is used.
    ///
    /// # Notes
    /// - The `SA_RESTORER` flag is always included.
    /// - The `SA_SIGINFO` flag is ignored. Use [`sig_handler_info`][Self::sig_handler_info] instead.
    /// - Unknown flags in the `flags` slice are ignored, and a warning is printed.
    ///
    /// # Examples
    /// ```no_run
    /// # #[cfg(feature = "std")] {
    /// # use devela::{Linux, LINUX_SIGNAL, Thread, ThreadExt};
    /// fn handler(sig: i32) {
    ///    println!("\nsignal `{sig}` received! continuing. . .");
    /// }
    /// fn main() {
    ///     println!("press Ctrl+C, or resize the terminal to catch the signals");
    ///     Linux::sig_handler(handler, &[LINUX_SIGNAL::SIGINT, LINUX_SIGNAL::SIGWINCH], None);
    ///     Thread::sleep_ms(4_000);
    ///     println!("bye");
    /// }
    /// # }
    /// ```
    pub fn sig_handler(handler: fn(i32), signals: &[i32], flags: Option<&[usize]>) {
        extern "C" fn c_handler(sig: i32) {
            is![sig < 1 || sig > LINUX_SIG_MAX as i32, return]; // Ignore invalid signals
            let handler = LINUX_SIG_HANDLERS[sig as usize].load(AtomicOrdering::SeqCst);
            if !handler.is_null() {
                #[expect(clippy::crosspointer_transmute, reason = "pointer to fn pointer")]
                // SAFETY: we control both the storage and retrieval and ensure null checks.
                let handler: fn(i32) = unsafe { transmute(handler) };
                handler(sig);
            }
        }
        // Use the restorer function defined in assembly.
        unsafe extern "C" { safe fn __devela_linux_restore_rt(); }

        // Start with the SA_RESTORER flag, as it is always required.
        let mut combined_flags = SIGACTION::SA_RESTORER;
        // Add additional flags if provided.
        if let Some(provided_flags) = flags {
            for &flag in provided_flags {
                match flag { // Validate the flag to ensure it is one of the known values.
                    SIGACTION::SA_NOCLDSTOP
                    | SIGACTION::SA_NOCLDWAIT
                    | SIGACTION::SA_NODEFER
                    | SIGACTION::SA_ONSTACK
                    | SIGACTION::SA_RESETHAND
                    | SIGACTION::SA_RESTART => {
                        combined_flags |= flag;
                    }
                    _ => {
                        #[cfg(feature = "std")]
                        eprintln!("Warning: Ignoring unknown flag: {flag:#x}");
                    }
                }
            }
        }
        let mask = LinuxSigset::empty();
        for &sig in signals {
            if (1..=LINUX_SIG_MAX).contains(&(sig as usize)) { // make sure the signal is a valid number
                // Store the handler in the appropriate slot
                LINUX_SIG_HANDLERS[sig as usize].store(handler as *mut _, AtomicOrdering::SeqCst);
                // Register the handler with the OS
                let c_handler = c_handler as extern "C" fn(i32);
                let sigaction = LinuxSigaction::new(c_handler,
                    combined_flags, mask, Some(__devela_linux_restore_rt));
                unsafe {
                    let _ = Linux::sys_rt_sigaction(sig, &sigaction,
                        Ptr::null_mut(), LinuxSigset::size());
                }
            }
        }
    }

    /// Registers multiple signals using a handler function accepting additional signal information.
    ///
    /// # Arguments
    /// - `handler`: A function that will be called when one of the specified signals is received.
    ///   The function takes three arguments:
    ///   - `i32`: The signal number.
    ///   - `LinuxSiginfo`: Additional information about the signal.
    ///   - `*mut c_void`: A pointer to the signal context (ucontext_t).
    /// - `signals`: A slice of [`LINUX_SIGNAL`][super::LINUX_SIGNAL] values specifying the signals to handle.
    /// - `flags`: An optional slice of [`LINUX_SIGACTION`][super::LINUX_SIGACTION] flags.
    ///   If `None`, only the `SA_RESTORER` and `SA_SIGINFO` flags are used.
    ///
    /// # Notes
    /// - The `SA_RESTORER` and `SA_SIGINFO` flags are always included.
    /// - Unknown flags in the `flags` slice are ignored, and a warning is printed.
    ///
    /// # Examples
    /// ```no_run
    /// # #[cfg(feature = "std")]
    /// # use devela::{Thread, ThreadExt};
    /// # use devela::{c_void, Linux, LinuxSiginfo, LINUX_SIGNAL, LINUX_SIGACTION};
    /// fn handler(sig: i32, info: LinuxSiginfo, _context: *mut c_void) {
    ///     println!("Signal {} received from PID {}!", sig, info.pid());
    /// }
    /// fn main() {
    ///     Linux::sig_handler_info(handler, &[LINUX_SIGNAL::SIGINT],
    ///         Some(&[LINUX_SIGACTION::SA_RESETHAND, LINUX_SIGACTION::SA_RESTART]),
    ///     );
    ///     println!("press Ctrl+C, to trigger SIGINT");
    ///     loop {
    ///         println!("Waiting for signal...");
    ///         #[cfg(feature = "std")]
    ///         Thread::sleep_ms(1_000);
    ///     }
    /// }
    /// ```
    pub fn sig_handler_info(
        handler: fn(i32, LinuxSiginfo, *mut c_void),
        signals: &[i32],
        flags: Option<&[usize]>,
    ) {
        // We store the given `handler` function in a static to be able to call it
        // from the new extern function which can't capture its environment.
        static HANDLER: AtomicPtr<fn(i32, LinuxSiginfo, *mut c_void)>
            = AtomicPtr::new(Ptr::null_mut());
        HANDLER.store(handler as *mut _, AtomicOrdering::SeqCst);

        extern "C" fn c_handler_siginfo(sig: i32, info: LinuxSiginfo, context: *mut c_void) {
            is![sig < 1 || sig > LINUX_SIG_MAX as i32, return]; // Ignore invalid signals
            let handler = HANDLER.load(AtomicOrdering::SeqCst);
            if !handler.is_null() {
                #[expect(clippy::crosspointer_transmute, reason = "pointer to fn pointer")]
                // SAFETY: we control both the storage and retrieval and ensure null checks.
                let handler = unsafe {
                    transmute::<*mut fn(i32, LinuxSiginfo, *mut c_void),
                        fn(i32, LinuxSiginfo, *mut c_void)>(handler)
                };
                handler(sig, info, context);
            }
        }
        // Use the restorer function defined in assembly.
        unsafe extern "C" { safe fn __devela_linux_restore_rt(); }

        // Start with the SA_RESTORER and SA_SIGINFO flags, as they are always required.
        let mut combined_flags = SIGACTION::SA_RESTORER | SIGACTION::SA_SIGINFO;
        // Add additional flags if provided.
        if let Some(provided_flags) = flags {
            for &flag in provided_flags {
                match flag {
                    SIGACTION::SA_NOCLDSTOP
                    | SIGACTION::SA_NOCLDWAIT
                    | SIGACTION::SA_NODEFER
                    | SIGACTION::SA_ONSTACK
                    | SIGACTION::SA_RESETHAND
                    | SIGACTION::SA_RESTART => {
                        combined_flags |= flag;
                    }
                    _ => {
                        #[cfg(feature = "std")]
                        eprintln!("Warning: Ignoring unknown flag: {flag:#x}");
                    }
                }
            }
        }
        let mask = LinuxSigset::empty();
        let sigaction = LinuxSigaction::new_siginfo(
            c_handler_siginfo,
            combined_flags,
            mask,
            Some(__devela_linux_restore_rt),
        );
        for s in signals {
            if (1..=31).contains(s) { // make sure the signal is a valid number
                unsafe {
                    let _ = Linux::sys_rt_sigaction(*s, &sigaction,
                        Ptr::null_mut(), LinuxSigset::size());
                }
            }
        }
    }
}

/* random */

/// Generates a `random_*` function for each given integer primitive
#[cfg(all(feature = "unsafe_syscall", not(miri)))]
macro_rules! impl_random_fns {
    // $prim: the unsigned integer primitive
    // $len:  the length of the primitive in bytes
    ($($prim:ident : $len:literal),+) => { $crate::paste! { $(
        #[doc = "Generates a random `" $prim "` value that may not be criptographically secure."]
        pub fn [<random_ $prim>]() -> Result<$prim> {
            #[cold] fn getrandom_failed_cold(n: isize) -> Result<$prim> { Err(LinuxError::Sys(n)) }

            let (mut r, mut attempts) = ([0; $len], 0);
            loop {
                let n = unsafe { Linux::sys_getrandom(r.as_mut_ptr(), $len, Linux::RAND_FLAGS) };
                if n == $len { break; } // ← hot path
                else if n == -ERRNO::EAGAIN { // ←↓ cold paths
                    is![!Linux::getrandom_try_again_cold(&mut attempts), break];
                } else { return getrandom_failed_cold(n); } // n < 0
            }
            Ok($prim::from_ne_bytes(r))
        }
    )+ }};
}

/// # Randomness-related methods.
///
/// They make use of the `GRND_NONBLOCK` and `GRND_INSECURE` flags. So when the randomness
/// source is not ready, instead of blocking it may return less secure data in linux >= 5.6
/// or retry it a certain number of times, or even return 0 in some cases.
#[rustfmt::skip]
#[cfg(all(feature = "unsafe_syscall", not(miri)))]
impl Linux {
    impl_random_fns![u8:1, u16:2, u32:4, u64:8, u128:16];

    /// Fills the given `buffer` with random bytes that may not be cryptographically secure.
    ///
    /// # Panics
    /// Panics in debug if `buffer.len() > `[`isize::MAX`]
    pub fn random_bytes(buffer: &mut [u8]) -> Result<()> {
        #[cold] fn getrandom_failed_cold(n: isize) -> Result<()> { Err(LinuxError::Sys(n)) }

        debug_assert![buffer.len() <= isize::MAX as usize];
        let (mut attempts, mut offset) = (0, 0);
        while offset < buffer.len() {
            let n = unsafe { Linux::sys_getrandom(buffer[offset..].as_mut_ptr(),
                buffer.len() - offset, Linux::RAND_FLAGS) };
            if n == -ERRNO::EAGAIN { is![!Linux::getrandom_try_again_cold(&mut attempts), break]; }
            else if n < 0 { return getrandom_failed_cold(n); } // ←↑ cold paths
            else { offset += n as usize; } // ← hot path
        }
        Ok(())
    }

    // from `sys/random.h`
    const GRND_NONBLOCK: c_uint = 0x0001;
    // const GRND_RANDOM: isize = 0x0002;
    const GRND_INSECURE: c_uint = 0x0004;
    const RAND_FLAGS: c_uint = Linux::GRND_NONBLOCK | Linux::GRND_INSECURE;
    const RAND_MAX_ATTEMPTS: usize = 15;
    /// the cold path for trying again
    #[cold] #[must_use]
    fn getrandom_try_again_cold(attempts: &mut usize) -> bool {
        // if *attempts >= Linux::RAND_MAX_ATTEMPTS { Linux::getrandom_failed_cold(); }
        *attempts += 1;
        *attempts <= Linux::RAND_MAX_ATTEMPTS
    }
}

/* syscalls (implemented under ./syscalls/) */