//! Support for automatically installing terminfo files. use std::io::{Result, Write}; use std::os::fd::AsRawFd; use std::path::Path; use nix::unistd::{Gid, Uid}; #[derive(Clone, Copy)] struct TerminfoPath<'a> { path: &'a str, term: &'a str, first: char, } impl<'a> TerminfoPath<'a> { pub fn new(path: &'a str, term: &'a str) -> Option { let first = *term.as_bytes().iter().next()?; let first = char::from_u32(first.into())?; Some(Self { path, term, first }) } pub fn try_each(self, mut f: F) -> Result> where F: FnMut(&str) -> Result>, { let mut buf = String::new(); if cfg!(target_os = "macos") { self.write_hex(&mut buf); } else { self.write_ltr(&mut buf); } f(&buf) /*self.write_ltr(&mut buf); if let Some(t) = f(&buf)? { return Ok(Some(t)); } self.write_hex(&mut buf); f(&buf)*/ } fn write_ltr(self, buf: &mut String) { buf.clear(); buf.reserve_exact(self.path.len() + 1 + 1 + 1 + self.term.len()); buf.push_str(self.path); buf.push_str("/"); buf.push(self.first); buf.push_str("/"); buf.push_str(self.term); } fn write_hex(self, buf: &mut String) { use std::fmt::Write; buf.clear(); buf.reserve_exact(self.path.len() + 1 + 1 + 1 + self.term.len()); buf.push_str(self.path); buf.push_str("/"); _ = write!(buf, "{:x}", self.first as u8); buf.push_str("/"); buf.push_str(self.term); } } pub async fn install_terminfo( path: &str, term: &str, terminfo: &[u8], owner: (Uid, Gid), ) -> Result<()> { if let Some(helper) = TerminfoPath::new(path, term) { tokio::task::block_in_place(move || { helper .try_each(move |path| { if !Path::new(&path).exists() { info!("Installing terminfo file for {:?} at {:?}", term, path); let dir = &path[..path.len() - term.len() - 1]; crate::io_util::ignore_already_exists(crate::io_util::create_dir_owned( dir, owner.0, owner.1, ))?; let mut f = std::fs::File::create(&path)?; nix::unistd::fchown(f.as_raw_fd(), Some(owner.0), Some(owner.1))?; f.write_all(terminfo)?; Ok(None) } else { Ok(Some(())) } }) .map(|_| ()) }) } else { Ok(()) } } pub async fn read_terminfo(path: &str, term: &str) -> Result> { if let Some(helper) = TerminfoPath::new(path, term) { let o = tokio::task::block_in_place(move || { helper.try_each(move |path| { if Path::new(&path).exists() { std::fs::read(&path).map(Some) } else { Ok(None) } }) }) .transpose(); if let Some(r) = o { return r; } } Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("terminfo file not found in {:?} for {:?}", path, term), )) }