use nix::fcntl;
use nix::sys::{signal, signalfd, stat};
use std::os::unix::io::{RawFd, AsRawFd};
use std::mem;
use std::path::Path;
use libc;
use dharma::{EventHandler, EventKind, event_kind};
use qualia::{EventHandling, Illusion, StatePublishing};
use device_access::RestrictedOpener;
const DEFAULT_TTY_PATH: &'static str = "/dev/tty";
const BASE_TTY_PATH: &'static str = "/dev/tty";
mod ioctl {
const VT_SETMODE: u32 = 0x5602;
const VT_GETSTATE: u32 = 0x5603;
const VT_RELDISP: u32 = 0x5605;
const VT_ACTIVATE: u32 = 0x5606;
pub const PROCESS: u32 = 0x1;
pub const ACK_ACQ: u32 = 0x2;
ioctl!(set_vt_mode with VT_SETMODE);
ioctl!(get_vt_state with VT_GETSTATE);
ioctl!(release_vt with VT_RELDISP);
ioctl!(activate_vt with VT_ACTIVATE);
}
pub struct SwitchHandler<P> where P: StatePublishing {
signal_fd: signalfd::SignalFd,
tty_fd: RawFd,
state_publisher: P,
}
impl<P> SwitchHandler<P> where P: StatePublishing {
pub fn new(tty_fd: RawFd, state_publisher: P) -> Self {
let mut mask = signal::SigSet::empty();
mask.add(signal::SIGUSR1);
mask.add(signal::SIGUSR2);
SwitchHandler {
signal_fd: signalfd::SignalFd::new(&mask).expect("Creation of signalfd"),
tty_fd: tty_fd,
state_publisher: state_publisher,
}
}
}
impl<P> SwitchHandler<P> where P: StatePublishing {
fn handle_activation(&mut self) {
log_info1!("Virtual terminal activation");
self.state_publisher.wakeup();
self.state_publisher.notify();
}
fn handle_deactivation(&mut self) {
log_info1!("Virtual terminal deactivation");
match unsafe { ioctl::release_vt(self.tty_fd, ioctl::ACK_ACQ as *mut u8) } {
Ok(_) => self.state_publisher.suspend(),
Err(err) => log_warn1!("Failed to release VT: {:?}", err),
}
}
}
impl<P> EventHandler for SwitchHandler<P> where P: StatePublishing {
fn get_fd(&self) -> RawFd {
self.signal_fd.as_raw_fd()
}
fn process_event(&mut self, _: EventKind) {
match self.signal_fd.read_signal() {
Ok(ossi) => {
match ossi {
Some(ssi) => {
if ssi.ssi_signo == signal::SIGUSR1 as u32 {
self.handle_deactivation();
} else if ssi.ssi_signo == signal::SIGUSR2 as u32 {
self.handle_activation();
} else {
log_warn2!("Received unexpected signal ({})", ssi.ssi_signo);
}
}
None => {
log_warn1!("Received invalid siginfo!");
}
}
}
Err(err) => {
log_warn1!("Error occurred during processing signal! ({:?})", err);
}
}
}
}
#[repr(C)]
struct VtMode {
mode: libc::c_char,
waitv: libc::c_char,
relsig: libc::c_short,
acqsig: libc::c_short,
frsig: libc::c_short,
}
impl VtMode {
pub fn new(hang_on_writes: bool, relsig: signal::Signal, acqsig: signal::Signal) -> Self {
VtMode {
mode: ioctl::PROCESS as libc::c_char,
waitv: hang_on_writes as libc::c_char,
relsig: relsig as libc::c_short,
acqsig: acqsig as libc::c_short,
frsig: 0,
}
}
}
#[repr(C)]
struct VtState {
pub active: libc::c_short,
pub signal: libc::c_short,
pub state: libc::c_short,
}
impl VtState {
pub fn new() -> Self {
VtState {
active: 0,
signal: 0,
state: 0,
}
}
}
#[derive(Clone, Copy)]
pub struct VirtualTerminal {
fd: RawFd,
}
impl VirtualTerminal {
pub fn new(ro: &RestrictedOpener) -> Result<Self, Illusion> {
match ro.open(&Path::new(DEFAULT_TTY_PATH),
fcntl::O_WRONLY | fcntl::O_CLOEXEC,
stat::Mode::empty()) {
Ok(tty_fd) => Ok(VirtualTerminal { fd: tty_fd }),
Err(err) => {
let text = format!("Failed to open VT device {:?}: {:?}", DEFAULT_TTY_PATH, err);
Err(Illusion::General(text))
}
}
}
pub fn get_current(&self) -> Result<u32, Illusion> {
let mut state = VtState::new();
let data = unsafe { mem::transmute::<&mut VtState, &mut u8>(&mut state) as *mut u8 };
match unsafe { ioctl::get_vt_state(self.fd, data) } {
Ok(_) => Ok(state.active as u32),
Err(err) => {
let text = format!("Failed to get state of terminal: {:?}", err);
Err(Illusion::General(text))
}
}
}
pub fn switch_to(&self, num: u8) -> Result<(), Illusion> {
match unsafe { ioctl::activate_vt(self.fd, num as *mut u8) } {
Ok(_) => Ok(()),
Err(err) => {
let text = format!("Failed to activate virtual terminal {}: {:?}", num, err);
Err(Illusion::General(text))
}
}
}
}
fn subscribe<C>(path: &Path,
mut coordinator: C,
ro: &RestrictedOpener)
-> Result<(), Illusion>
where C: 'static + StatePublishing + EventHandling + Send + Clone
{
match ro.open(path, fcntl::O_WRONLY | fcntl::O_CLOEXEC, stat::Mode::empty()) {
Ok(tty_fd) => {
let mut mode = VtMode::new(true, signal::SIGUSR1, signal::SIGUSR2);
let data = unsafe { mem::transmute::<&mut VtMode, &mut u8>(&mut mode) as *mut u8 };
match unsafe { ioctl::set_vt_mode(tty_fd, data) } {
Ok(_) => {
let signal_source = Box::new(SwitchHandler::new(tty_fd, coordinator.clone()));
coordinator.add_event_handler(signal_source, event_kind::READ);
Ok(())
}
Err(err) => {
let text = format!("Failed to subscribe for terminal events: {:?}", err);
Err(Illusion::General(text))
}
}
}
Err(err) => {
let text = format!("Failed to open terminal device {:?}: {:?}", path, err);
Err(Illusion::General(text))
}
}
}
pub fn setup<C>(tty_num: u32,
coordinator: C,
ro: &RestrictedOpener)
-> Result<(), Illusion>
where C: 'static + StatePublishing + EventHandling + Send + Clone
{
let path_str = format!("{}{}", BASE_TTY_PATH, tty_num);
let path = Path::new(&path_str);
log_info2!("Setting up virtual terminal '{}'", path_str);
subscribe(path, coordinator, ro)
}