macluhan 0.1.1

The medium is the message
Documentation
#[cfg(feature = "tokio")]
#[path = "linux/tokio.rs"]
pub mod tokio;

use core::mem::{size_of_val, MaybeUninit};
use core::ptr;
use core::sync::atomic::{AtomicI32, Ordering};

use heveanly::syscall::{self, syscall, Check};
use heveanly::{retry_eintr, AsUninitBytes, Fd};
use libc::sigemptyset;

pub type Signal = libc::c_int;

fn signals_new<T>(sigs: &[Signal], from_sigset: fn(&mut libc::sigset_t) -> T) -> T {
   let mut sigset = MaybeUninit::uninit();
   from_sigset(unsafe {
      libc::sigemptyset(sigset.as_mut_ptr());
      for &sig in sigs {
         libc::sigaddset(sigset.as_mut_ptr(), sig);
      }
      sigset.assume_init_mut()
   })
}

fn fill_all(sigs: *mut libc::sigset_t) {
   unsafe {
      libc::sigfillset(sigs);
      // From the `sigprocmask` man page:
      // > If SIGBUS, SIGFPE, SIGILL, or SIGSEGV are generated while they are
      // > blocked, the result is undefined, unless the signal was generated by
      // > kill(2), sigqueue(3), or raise(3).
      libc::sigdelset(sigs, libc::SIGBUS);
      libc::sigdelset(sigs, libc::SIGFPE);
      libc::sigdelset(sigs, libc::SIGILL);
      libc::sigdelset(sigs, libc::SIGSEGV);
      // The Rust runtime ignores `SIGPIPE` on startup, which is very
      // reasonable and probably means the standard library, tokio, etc. don't
      // bother writing `MSG_NOSIGNAL` everywhere. Let's not mess with that.
      libc::sigdelset(sigs, libc::SIGPIPE);
   }
}

fn signals_all<T>(from_sigset: fn(&mut libc::sigset_t) -> T) -> T {
   let mut sigs = MaybeUninit::uninit();
   from_sigset(unsafe {
      fill_all(sigs.as_mut_ptr());
      sigs.assume_init_mut()
   })
}

fn signals_deadly<T>(from_sigset: fn(&mut libc::sigset_t) -> T) -> T {
   let mut sigs = MaybeUninit::uninit();
   from_sigset(unsafe {
      fill_all(sigs.as_mut_ptr());
      // The default disposition of these signals is `Ign`
      libc::sigdelset(sigs.as_mut_ptr(), libc::SIGCHLD);
      libc::sigdelset(sigs.as_mut_ptr(), libc::SIGURG);
      libc::sigdelset(sigs.as_mut_ptr(), libc::SIGWINCH);
      sigs.assume_init_mut()
   })
}

fn signals_benign<T>(from_sigset: fn(&mut libc::sigset_t) -> T) -> T {
   let mut sigs = MaybeUninit::uninit();
   from_sigset(unsafe {
      libc::sigemptyset(sigs.as_mut_ptr());
      libc::sigaddset(sigs.as_mut_ptr(), libc::SIGCHLD);
      libc::sigaddset(sigs.as_mut_ptr(), libc::SIGURG);
      libc::sigaddset(sigs.as_mut_ptr(), libc::SIGWINCH);
      sigs.assume_init_mut()
   })
}

// The only reason pretty much anything in here can fail is if system resources
// are exhausted, so if Rust can panic on OOM then I can too. :)))
macro_rules! libc_unwrap {
   ($f:path, $($xs:expr),*$(,)?) => {
      {
         let rc = $f($($xs),+);
         if rc < 0 {
            panic!("`{}` failed with error code {}", stringify!($f), *libc::__errno_location());
         }
         rc
      }
   }
}

macro_rules! libc_unwrap_fd {
   ($f:path, $($xs:expr),*$(,)?) => {
      Fd::new_unchecked(libc_unwrap!($f, $($xs),*))
   }
}

// Linux kernel bug 9039 means that we not only can't just do the "obvious"
// thing of using `sigwaitinfo` and calling it a day, but also have to handle
// `SIGINT` separately when using `signalfd`. This is the reason for pretty
// much all of the complexity in this code.
static SIGINT_EFD: AtomicI32 = AtomicI32::new(-1);

extern "C" fn sigint_efd_handler(_: libc::c_int, _: *mut libc::siginfo_t, _: *mut libc::c_void) {
   let _ =
      unsafe { Fd::new_unchecked(SIGINT_EFD.load(Ordering::Relaxed)) }.write(&1u64.to_ne_bytes());
}

fn sigint_efd() -> Fd {
   let efd = unsafe {
      libc_unwrap_fd!(
         libc::eventfd,
         0,
         libc::EFD_CLOEXEC | libc::EFD_NONBLOCK | libc::EFD_SEMAPHORE,
      )
   };
   match SIGINT_EFD.compare_exchange(-1, efd.get(), Ordering::Relaxed, Ordering::Relaxed) {
      Ok(_) => {
         let mut act = MaybeUninit::<libc::sigaction>::uninit();
         unsafe {
            // Seems like `libc` doesn't expose the `sa_handler` field. Pretty
            // sure it's unioned with `sa_sigaction` on every platform that I'm
            // ever going to care about, but we might as well avoid a nasty
            // surprise down the road and just use `SA_SIGINFO`.
            (*act.as_mut_ptr()).sa_sigaction = sigint_efd_handler as usize;
            sigemptyset(&mut (*act.as_mut_ptr()).sa_mask);
            (*act.as_mut_ptr()).sa_flags = libc::SA_SIGINFO;
            libc_unwrap!(libc::sigaction, libc::SIGINT, act.assume_init_ref(), ptr::null_mut());
         }
         efd
      },
      Err(fd) => {
         let _ = efd.close();
         unsafe { Fd::new_unchecked(fd) }
      },
   }
}

pub struct Signals {
   sigint_efd: i32, // Morally an `Option<NonNeg<RawFd>>` or whatever
   sigfd: Fd,
}

impl Signals {
   fn from_sigset(sigs: &mut libc::sigset_t) -> Self {
      unsafe {
         let (sigint_efd, flags) = match libc::sigismember(sigs, libc::SIGINT) {
            1 => {
               libc::sigdelset(sigs, libc::SIGINT);
               (sigint_efd().get(), libc::SFD_CLOEXEC | libc::SFD_NONBLOCK)
            },
            _ => (-1, libc::SFD_CLOEXEC),
         };
         libc::pthread_sigmask(libc::SIG_BLOCK, sigs, ptr::null_mut());
         Self { sigint_efd, sigfd: libc_unwrap_fd!(libc::signalfd, -1, sigs, flags) }
      }
   }

   pub fn new(sigs: &[Signal]) -> Self {
      signals_new(sigs, Self::from_sigset)
   }

   pub fn all() -> Self {
      signals_all(Self::from_sigset)
   }

   pub fn deadly() -> Self {
      signals_deadly(Self::from_sigset)
   }

   pub fn benign() -> Self {
      signals_benign(Self::from_sigset)
   }
}

impl Drop for Signals {
   fn drop(&mut self) {
      let _ = self.sigfd.close();
   }
}

fn next(sigfd: Fd) -> Option<Signal> {
   let mut info = MaybeUninit::<libc::signalfd_siginfo>::uninit();
   match retry_eintr(|| sigfd.read(info.as_uninit_bytes_mut())).ok()? == size_of_val(&info) {
      true => Some(unsafe { info.assume_init_ref() }.ssi_signo as Signal),
      false => None,
   }
}

fn next_with_sigint(sigint_efd: Fd, sigfd: Fd) -> Option<Signal> {
   let mut pfds = MaybeUninit::<[libc::pollfd; 2]>::uninit();
   let pfds = unsafe {
      (*pfds.as_mut_ptr())[0].fd = sigint_efd.get();
      (*pfds.as_mut_ptr())[0].events = libc::POLLIN;
      (*pfds.as_mut_ptr())[1].fd = sigfd.get();
      (*pfds.as_mut_ptr())[1].events = libc::POLLIN;
      #[cfg(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64"))]
      retry_eintr(|| syscall!(syscall::SYS_poll, pfds.as_mut_ptr(), 2, -1).check()).ok()?;
      #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
      retry_eintr(|| syscall!(syscall::SYS_ppoll, pfds.as_mut_ptr(), 2, 0, 0, 0).check()).ok()?;
      pfds.assume_init_ref()
   };
   if pfds[0].revents != 0 {
      let _ = sigint_efd.read(MaybeUninit::<[u8; 8]>::uninit().as_uninit_bytes_mut());
      return Some(libc::SIGINT);
   }
   debug_assert_ne!(pfds[1].revents, 0);
   next(sigfd)
}

impl Iterator for Signals {
   type Item = Signal;

   fn next(&mut self) -> Option<Self::Item> {
      if self.sigint_efd < 0 {
         next(self.sigfd)
      } else {
         next_with_sigint(unsafe { Fd::new_unchecked(self.sigint_efd) }, self.sigfd)
      }
   }
}