From ccf6d5301a078b4fb7ce52ea1ed80bda1d6c7397 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sat, 29 Apr 2023 23:40:17 -0400 Subject: [PATCH] Add shells hook --- src/hpk.rs | 7 +- src/installer/hooks.rs | 297 ++++++++++++++++++++++++++++++----------- src/installer/mod.rs | 24 ++-- src/package/mod.rs | 5 +- src/package/specs.rs | 3 + 5 files changed, 238 insertions(+), 98 deletions(-) diff --git a/src/hpk.rs b/src/hpk.rs index 59db98d..2ea4c57 100644 --- a/src/hpk.rs +++ b/src/hpk.rs @@ -9,10 +9,7 @@ mod cli; use { clap::ArgMatches, cli::cli, - hpk::{ - CreationError, Creator, Dependency, InstallMessage, Installer, Message, Specs, - Version, - }, + hpk::{CreationError, Creator, Dependency, InstallMessage, Installer, Message, Specs, Version}, indicatif::{ProgressBar, ProgressStyle}, ron::ser::{to_writer_pretty, PrettyConfig}, std::{ @@ -158,7 +155,7 @@ fn install_local + fmt::Display>( } fn init(matches: &ArgMatches) -> Result> { - let specsfile = PathBuf::from("package.specs"); + let specsfile = PathBuf::from("specs.ron"); let cfg = PrettyConfig::new().struct_names(true); let buf = File::create(&specsfile)?; let writer = BufWriter::new(buf); diff --git a/src/installer/hooks.rs b/src/installer/hooks.rs index 99876b9..5c09576 100644 --- a/src/installer/hooks.rs +++ b/src/installer/hooks.rs @@ -3,6 +3,7 @@ use std::{ path::PathBuf, process::{Command, Output}, sync::mpsc::Sender, + thread, fs::OpenOptions, io::{Seek, Read, Write, SeekFrom}, }; #[derive(Debug, Clone)] @@ -14,6 +15,7 @@ pub struct Hooks { pinstall: Vec, users: Vec, groups: Vec, + shells: Vec, } impl Default for Hooks { @@ -26,6 +28,7 @@ impl Default for Hooks { pinstall: vec![], users: vec![], groups: vec![], + shells: vec![], } } } @@ -61,87 +64,227 @@ impl Hooks { self.groups.push(group); } - pub fn makewhatis( - &self, - sender: Sender, - ) -> Result, InstallError> { - if self.man { - let output = Command::new("makewhatis") - .output() - .map_err(Into::::into)?; - sender.send(InstallMessage::Man)?; - Ok(Some(output)) - } else { - Ok(None) - } + pub fn push_shell(&mut self, shell: PathBuf) { + self.shells.push(shell); } - pub fn compile_schemas( - &self, - sender: Sender, - ) -> Result, InstallError> { + pub fn run(self, sender: Sender) -> Result<(), InstallError> { + let Some(root) = self.root.to_str().map(|x| x.to_string()) else { + return Err(InstallError::BadPath); + }; + let mut threads = vec![]; + if !self.users.is_empty() { + let sender = sender.clone(); + let root = root.clone(); + let users = self.users; + threads.push(thread::spawn(move || { + create_users(users, &root, sender)?; + Ok::<(), InstallError>(()) + })); + } + if !self.groups.is_empty() { + let sender = sender.clone(); + let root = root.clone(); + let groups = self.groups; + threads.push(thread::spawn(move || { + create_groups(groups, &root, sender)?; + Ok::<(), InstallError>(()) + })); + } + if !self.shells.is_empty() { + let sender = sender.clone(); + let root = root.clone(); + let shells = self.shells; + threads.push(thread::spawn(move || { + append_shells(shells, &root, sender)?; + Ok::<(), InstallError>(()) + })); + } + if self.man && root.as_str() == "/" { + let sender = sender.clone(); + threads.push(thread::spawn(move || { + install_man(sender)?; + Ok::<(), InstallError>(()) + })); + } if self.glib_schema { - let mut dir = self.root.clone(); - dir.push("usr"); - dir.push("share"); - dir.push("glib-2.0"); - dir.push("schemas"); - let output = Command::new("glib-compile-schemas") - .arg(dir.to_str().unwrap()) - .output() - .map_err(Into::::into)?; - sender.send(InstallMessage::GlibSchemas)?; - Ok(Some(output)) - } else { - Ok(None) + let sender = sender.clone(); + let root = root.clone(); + threads.push(thread::spawn(move || { + compile_schemas(&root, sender)?; + Ok::<(), InstallError>(()) + })); + } + if !self.info.is_empty() { + let sender = sender.clone(); + let root = PathBuf::from(&root); + let pages = self.info; + threads.push(thread::spawn(move || { + install_info(pages, root, sender)?; + Ok::<(), InstallError>(()) + })); + } + if !self.pinstall.is_empty() { + let scripts = self.pinstall; + threads.push(thread::spawn(move || { + run_pinstall(scripts, root.into(), sender)?; + Ok::<(), InstallError>(()) + })); + } + for th in threads { + if let Err(e) = th.join() { + eprintln!("{e:?}"); + } } - } - - pub fn install_info(&self, sender: Sender) -> Result<(), InstallError> { - let root = self.root.clone(); - self.info.iter().try_for_each(|page| { - let p = root.clone(); - let page = p.join(page); - let Some(dir) = page.parent() else { - return Err(InstallError::BadPath); - }; - let mut dir = dir.to_path_buf(); - dir.push("dir"); - let Some(page) = page.to_str() else { - return Err(InstallError::BadPath); - }; - let Some(dir) = dir.to_str() else { - return Err(InstallError::BadPath); - }; - Command::new("install-info") - .args([page, dir]) - .output() - .map_err(Into::::into)?; - sender.send(InstallMessage::Info(page.to_string()))?; - Ok(()) - })?; - Ok(()) - } - - pub fn run_pinstall(&self, sender: Sender) -> Result<(), InstallError> { - let root = self.root.clone(); - self.pinstall.iter().try_for_each(|pinstall| { - let p = root.clone(); - let pinstall = p.join(pinstall); - let Some(root) = root.to_str() else { - return Err(InstallError::BadPath); - }; - let Some(pinstall) = pinstall.to_str() else { - return Err(InstallError::BadPath); - }; - let output = Command::new("/bin/sh") - .arg(pinstall) - .env("HPOK_ROOT", root) - .output() - .map_err(Into::::into)?; - sender.send(InstallMessage::PostInstall(output))?; - Ok(()) - })?; Ok(()) } } + +pub fn append_shells( + shells: Vec, + root: &str, + sender: Sender, +) -> Result<(), InstallError> { + shells.iter().try_for_each(|shell| { + let s = PathBuf::from("/"); + let shell = s.join(shell); + let Some(shell) = shell.to_str().map(|x| x.to_string()) else { + return Err(InstallError::BadPath); + }; + let mut f = PathBuf::from(root); + f.push("etc"); + f.push("shells"); + let mut fd = OpenOptions::new() + .read(true) + .write(true) + .open(&f)?; + let meta = fd.metadata()?; + let len = meta.len(); + let pos = fd.seek(SeekFrom::Start(len - 1))?; + if !pos == len - 1 { + eprintln!("Bad seek operation in /etc/shells"); + } + let mut buf = [0; 1]; + fd.read_exact(&mut buf)?; + if !buf[0] == b'\n' { + write!(fd, "\n")?; + } + write!(fd, "{shell}")?; + sender.send(InstallMessage::ShellAdded(shell))?; + Ok::<(), InstallError>(()) + })?; + Ok(()) +} + +pub fn create_users( + users: Vec, + root: &str, + sender: Sender, +) -> Result<(), InstallError> { + users.iter().try_for_each(|user| { + let mut cmd = Command::new("useradd"); + cmd.args(["-rs", "/sbin/nologin", "-R", root]); + if let Some(uid) = user.uid { + cmd.args(["-u", &format!("{uid}")]); + } + cmd.arg(&user.name); + let _output = cmd.output().map_err(Into::::into)?; + sender.send(InstallMessage::UserCreated(user.clone()))?; + Ok::<(), InstallError>(()) + })?; + Ok(()) +} + +pub fn create_groups( + groups: Vec, + root: &str, + sender: Sender, +) -> Result<(), InstallError> { + groups.iter().try_for_each(|group| { + let mut cmd = Command::new("groupadd"); + cmd.args(["-r", "-R", root]); + if let Some(gid) = group.gid { + cmd.args(["-u", &format!("{gid}")]); + } + cmd.arg(&group.name); + let _output = cmd.output().map_err(Into::::into)?; + sender.send(InstallMessage::GroupCreated(group.clone()))?; + Ok::<(), InstallError>(()) + })?; + Ok(()) +} + +pub fn install_man(sender: Sender) -> Result { + let output = Command::new("makewhatis") + .output() + .map_err(Into::::into)?; + sender.send(InstallMessage::Man)?; + Ok(output) +} + +pub fn compile_schemas(root: &str, sender: Sender) -> Result { + let mut dir = PathBuf::from(root); + dir.push("usr"); + dir.push("share"); + dir.push("glib-2.0"); + dir.push("schemas"); + let output = Command::new("glib-compile-schemas") + .arg(dir.to_str().unwrap()) + .output() + .map_err(Into::::into)?; + sender.send(InstallMessage::GlibSchemas)?; + Ok(output) +} + +pub fn install_info( + pages: Vec, + root: PathBuf, + sender: Sender, +) -> Result<(), InstallError> { + pages.iter().try_for_each(|page| { + let page = root.join(page); + let Some(dir) = page.parent() else { + return Err(InstallError::BadPath); + }; + let mut dir = dir.to_path_buf(); + dir.push("dir"); + let Some(page) = page.to_str() else { + return Err(InstallError::BadPath); + }; + let Some(dir) = dir.to_str() else { + return Err(InstallError::BadPath); + }; + Command::new("install-info") + .args([page, dir]) + .output() + .map_err(Into::::into)?; + sender.send(InstallMessage::Info(page.to_string()))?; + Ok(()) + })?; + Ok(()) +} + +pub fn run_pinstall( + scripts: Vec, + root: PathBuf, + sender: Sender, +) -> Result<(), InstallError> { + scripts.iter().try_for_each(|pinstall| { + let p = root.clone(); + let pinstall = p.join(pinstall); + let Some(root) = root.to_str() else { + return Err(InstallError::BadPath); + }; + let Some(pinstall) = pinstall.to_str() else { + return Err(InstallError::BadPath); + }; + let output = Command::new("/bin/sh") + .arg(pinstall) + .env("HPOK_ROOT", root) + .output() + .map_err(Into::::into)?; + sender.send(InstallMessage::PostInstall(output))?; + Ok(()) + })?; + Ok(()) +} diff --git a/src/installer/mod.rs b/src/installer/mod.rs index e7bdb49..49dbcec 100644 --- a/src/installer/mod.rs +++ b/src/installer/mod.rs @@ -50,6 +50,8 @@ pub enum InstallMessage { UserCreated(User), /// A `Group` has been successfully created GroupCreated(Group), + /// A shell has been added to '/etc/shells' + ShellAdded(String), /// The output of the post install script PostInstall(Output), /// Update the mandoc db @@ -100,14 +102,13 @@ impl Installer { pr_node.write(&mut buf)?; let package: Package = ron::de::from_bytes(&buf)?; if let Some(ref users) = package.users { - users - .iter() - .for_each(|u| hooks.push_user(u.clone())); + users.iter().for_each(|u| hooks.push_user(u.clone())); } if let Some(ref groups) = package.groups { - groups - .iter() - .for_each(|g| hooks.push_group(g.clone())); + groups.iter().for_each(|g| hooks.push_group(g.clone())); + } + if let Some(ref shells) = package.shells { + shells.iter().for_each(|s| hooks.push_shell(s.clone())); } let mut db_pkgdir = crate::get_dbdir(Some(self.root.clone())); db_pkgdir.push(&package.name); @@ -139,10 +140,7 @@ impl Installer { let mut h = hooks.lock().unwrap(); h.push_man(); } else if s.contains("/share/info") { - hooks - .lock() - .unwrap() - .push_info(fpath.to_str().unwrap()); + hooks.lock().unwrap().push_info(fpath.to_str().unwrap()); } else if s.contains("/share/glib-2.0/schemas") { let mut h = hooks.lock().unwrap(); h.push_glib_schema(); @@ -237,11 +235,7 @@ fn pop_appstream(archive: &mut Archive, db_pkgdir: &Path) -> Result<(), Error> { Ok(()) } -fn pop_pinstall( - archive: &mut Archive, - hooks: &mut Hooks, - pkgname: &str, -) -> Result<(), Error> { +fn pop_pinstall(archive: &mut Archive, hooks: &mut Hooks, pkgname: &str) -> Result<(), Error> { let pinstall = archive.pop("postinstall.sh"); if let Some(node) = pinstall { let mut path: PathBuf = ["/", "tmp", "hpk", pkgname].iter().collect(); diff --git a/src/package/mod.rs b/src/package/mod.rs index bcef93f..5d4a5a9 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -12,7 +12,7 @@ use { fs, fs::File, io::{BufWriter, Write}, - path::Path, + path::{Path, PathBuf}, }, }; @@ -56,6 +56,8 @@ pub struct Package { pub users: Option>, /// an optional list of groups to be created upon installation pub groups: Option>, + /// applicable only if this package contains a shell + pub shells: Option>, /// an optional post installation shell script to be run pub post_install: Option, } @@ -71,6 +73,7 @@ impl From for Package { dependencies: value.dependencies, users: value.users, groups: value.groups, + shells: value.shells, post_install: value.post_install, ..Default::default() } diff --git a/src/package/specs.rs b/src/package/specs.rs index abe182b..3aed292 100644 --- a/src/package/specs.rs +++ b/src/package/specs.rs @@ -2,6 +2,7 @@ use { super::{Group, User}, crate::{Dependency, Version}, serde::{Deserialize, Serialize}, + std::path::PathBuf, }; #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] @@ -22,6 +23,8 @@ pub struct Specs { pub users: Option>, /// an optional list of groups to be created upon installation pub groups: Option>, + /// applicable only if this package contains a shell + pub shells: Option>, /// an optional post installation shell script to be run pub post_install: Option, }