use crate::base::{Error, Result};
use std::mem;
use std::os::unix::io::RawFd;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::Mutex;
pub(crate) static WINCH_CLAIMED: AtomicBool = AtomicBool::new(false);
pub(crate) static WINCH_PIPE_WR: AtomicI32 = AtomicI32::new(-1);
pub(crate) static WINCH_PIPE_RD: AtomicI32 = AtomicI32::new(-1);
pub(crate) struct EmergencySlot {
pub(crate) fd: RawFd,
pub(crate) termios: libc::termios,
pub(crate) leave_bytes: Vec<u8>,
}
pub(crate) static EMERGENCY: Mutex<Option<EmergencySlot>> = Mutex::new(None);
pub(crate) fn append_emergency_leave(extra: &[u8]) {
if let Ok(mut g) = EMERGENCY.lock() {
if let Some(s) = g.as_mut() {
s.leave_bytes.extend_from_slice(extra);
}
}
}
pub(crate) fn prepend_emergency_leave(extra: &[u8]) {
if let Ok(mut g) = EMERGENCY.lock() {
if let Some(s) = g.as_mut() {
s.leave_bytes.splice(0..0, extra.iter().copied());
}
}
}
pub fn emergency_restore() {
let slot = match EMERGENCY.lock() {
Ok(mut g) => g.take(),
Err(_) => return, };
if let Some(s) = slot {
let _ = write_all_fd(s.fd, &s.leave_bytes);
unsafe { libc::tcsetattr(s.fd, libc::TCSANOW, &s.termios) };
}
}
extern "C" fn on_sigwinch(_: libc::c_int) {
unsafe {
let errno_p = errno_location();
let saved = *errno_p;
let fd = WINCH_PIPE_WR.load(Ordering::Relaxed);
if fd >= 0 {
let byte = 1u8;
libc::write(fd, &byte as *const u8 as *const libc::c_void, 1);
}
*errno_p = saved;
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
unsafe fn errno_location() -> *mut libc::c_int {
libc::__error()
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
unsafe fn errno_location() -> *mut libc::c_int {
libc::__errno_location()
}
pub(crate) fn last_errno() -> i32 {
unsafe { *errno_location() }
}
pub(crate) fn io_err(ctx: &str) -> Error {
let e = std::io::Error::from_raw_os_error(last_errno());
Error::Io(std::io::Error::new(e.kind(), format!("{ctx}: {e}")))
}
#[cfg(target_env = "musl")]
pub(crate) type IoctlReq = libc::c_int;
#[cfg(not(target_env = "musl"))]
pub(crate) type IoctlReq = libc::c_ulong;
pub(crate) fn write_all_fd(fd: RawFd, mut bytes: &[u8]) -> Result<()> {
while !bytes.is_empty() {
let n = unsafe { libc::write(fd, bytes.as_ptr() as *const libc::c_void, bytes.len()) };
if n < 0 {
let e = last_errno();
if e == libc::EINTR {
continue;
}
return Err(io_err("write"));
}
bytes = &bytes[n as usize..];
}
Ok(())
}
pub(crate) fn set_cloexec_nonblock(fd: RawFd) -> Result<()> {
unsafe {
if libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) < 0 {
return Err(io_err("fcntl(FD_CLOEXEC)"));
}
let fl = libc::fcntl(fd, libc::F_GETFL);
if fl < 0 || libc::fcntl(fd, libc::F_SETFL, fl | libc::O_NONBLOCK) < 0 {
return Err(io_err("fcntl(O_NONBLOCK)"));
}
}
Ok(())
}
pub(crate) struct WinchClaim {
pub(crate) pipe_rd: RawFd,
pub(crate) old_action: libc::sigaction,
}
pub(crate) fn claim_winch() -> Option<WinchClaim> {
if WINCH_CLAIMED
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return None; }
let mut rd = WINCH_PIPE_RD.load(Ordering::Acquire);
if rd < 0 {
let mut fds = [0 as libc::c_int; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
WINCH_CLAIMED.store(false, Ordering::Release);
return None;
}
if set_cloexec_nonblock(fds[0]).is_err() || set_cloexec_nonblock(fds[1]).is_err() {
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
WINCH_CLAIMED.store(false, Ordering::Release);
return None;
}
WINCH_PIPE_RD.store(fds[0], Ordering::Release);
WINCH_PIPE_WR.store(fds[1], Ordering::Release);
rd = fds[0];
}
let mut sa: libc::sigaction = unsafe { mem::zeroed() };
sa.sa_sigaction = on_sigwinch as extern "C" fn(libc::c_int) as usize;
sa.sa_flags = libc::SA_RESTART;
unsafe { libc::sigemptyset(&mut sa.sa_mask) };
let mut old: libc::sigaction = unsafe { mem::zeroed() };
if unsafe { libc::sigaction(libc::SIGWINCH, &sa, &mut old) } != 0 {
WINCH_CLAIMED.store(false, Ordering::Release);
return None;
}
Some(WinchClaim {
pipe_rd: rd,
old_action: old,
})
}
pub(crate) fn release_winch(claim: WinchClaim) {
unsafe { libc::sigaction(libc::SIGWINCH, &claim.old_action, std::ptr::null_mut()) };
WINCH_CLAIMED.store(false, Ordering::Release);
}