From da9ce5420b37e731d37adf725dc5d57c8870dbf0 Mon Sep 17 00:00:00 2001 From: chee Date: Sat, 17 Aug 2019 22:44:30 +0100 Subject: [PATCH] Restructure filesystem, fix templating bug (fix bug where template-replacement only worked the first time!!) --- src/main.rs | 177 ++---------------------------------- src/manager/mod.rs | 9 ++ src/manager/none.rs | 9 ++ src/{ => manager}/socket.rs | 5 +- src/{ => manager}/watch.rs | 3 +- src/options.rs | 89 ++++++++++++++++++ src/subprocesses.rs | 71 +++++++++++++++ 7 files changed, 192 insertions(+), 171 deletions(-) create mode 100644 src/manager/mod.rs create mode 100644 src/manager/none.rs rename src/{ => manager}/socket.rs (94%) rename src/{ => manager}/watch.rs (96%) create mode 100644 src/options.rs create mode 100644 src/subprocesses.rs diff --git a/src/main.rs b/src/main.rs index 16c754a..19800d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,180 +2,21 @@ extern crate getopts; extern crate notify; extern crate regex; -use regex::Regex; -use std::fs; -use std::os::unix::prelude::*; -use std::process::{Child, Command}; +mod manager; +mod options; +mod subprocesses; -mod socket; -mod watch; - -pub struct Subprocess { - uid: u32, - gid: u32, - dir: String, - process: Option, -} - -impl Subprocess { - fn make_process(&self, program: &str) -> Result { - Command::new("sh") - .arg("-c") - .arg(program) - .current_dir(&self.dir) - .uid(self.uid) - .gid(self.gid) - .spawn() - } - - fn new(sub: String) -> Result { - let sub_metadata = fs::metadata(&sub)?; - let uid = sub_metadata.uid(); - let gid = sub_metadata.gid(); - - Ok(Subprocess { - uid, - gid, - dir: sub, - process: None, - }) - } - - fn start(&mut self, program: &str) -> Result<(), std::io::Error> { - if let Some(process) = &mut self.process { - process.kill()?; - } - self.process = Some(self.make_process(program)?); - Ok(()) - } -} - -fn print_usage(dollar0: &str, opts: getopts::Options) { - let brief = format!("Usage: {} [options] PROGRAM [root_dir]", dollar0); - println!("{}", opts.usage(&brief)); - println!("PROGRAM will be run in parallel in every subdirectory (SUB), as SUB's owner."); - println!("A placeholder \"{{}}\" is available to PROGRAM, it will be replaced with SUB."); -} - -pub enum Manager { - Watch, - Socket, - None, -} - -pub struct Options { - sock_path: String, - program: String, - root_dir: String, - watch_ignore: Option, - management: Manager, -} - -fn get_options() -> Options { - let args: Vec = std::env::args().collect(); - let mut opts = getopts::Options::new(); - - opts.optopt( - "t", - "type", - "set the management type [choices: watch, socket, none] [default: none]", - "TYPE", - ); - - opts.optopt( - "s", - "socket", - "set the socket path. sending the socket a message like \"restart xxx\" will restart the process running in the directory \"xxx\". [default: ./subsocket]", - "NAME", - ); - - opts.optopt( - "i", - "watch-ignore", - "pattern to ignore when watching (matches whole path)", - "PATTERN", - ); - - opts.optflag("h", "help", "get help"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => panic!(f.to_string()), - }; - - let program = matches.free.get(0); - - let management = match matches.opt_str("t") { - Some(string) => match string.as_ref() { - "watch" => Some(Manager::Watch), - "socket" | "sock" => Some(Manager::Socket), - "none" => Some(Manager::None), - _ => None, - }, - None => Some(Manager::None), - }; - - let should_show_help = matches.opt_present("h") || program.is_none() || management.is_none(); - - if should_show_help { - print_usage(&args[0], opts); - std::process::exit(1); - } - - let default_root_dir = ".".to_string(); - let root_dir = matches.free.get(1).unwrap_or(&default_root_dir).to_string(); - - let sock_path = matches - .opt_str("s") - .unwrap_or_else(|| "subsocket".to_string()); - - let watch_ignore = match matches.opt_str("i") { - Some(string) => Some(Regex::new(&string).expect("-i was not a valid regex")), - None => None, - }; - - Options { - program: program.unwrap().to_string(), - watch_ignore, - sock_path, - root_dir, - management: management.unwrap(), - } -} - -pub type Processes = std::collections::HashMap; +use manager::Manager; fn main() -> Result<(), std::io::Error> { - let options = get_options(); - let subdirectories = fs::read_dir(&options.root_dir)?; + let options = options::get_options(); - let mut processes: Processes = std::collections::HashMap::new(); - - for sub in subdirectories { - let sub: fs::DirEntry = sub?; - let path = sub.path(); - if !path.is_dir() { - println!("Ignoring non-directory: {:?}", path); - continue; - } - let sub_name = path.file_name().unwrap().to_str().unwrap(); - let mut process = Subprocess::new(path.to_str().unwrap().to_string())?; - process.start(&options.program.replace("{}", sub_name))?; - if let Some(name) = path.file_name() { - processes.insert(name.to_str().unwrap().to_string(), process); - } - } + let processes = subprocesses::start(&options)?; match options.management { - Manager::Watch => watch::manage(processes, options), - Manager::Socket => socket::manage(processes, options)?, - Manager::None => { - for sub in processes.values_mut() { - if let Some(process) = &mut sub.process { - process.wait().unwrap(); - } - } - } + Manager::Watch => manager::watch::manage(processes, options), + Manager::Socket => manager::socket::manage(processes, options)?, + Manager::None => manager::none::manage(processes, options), } Ok(()) diff --git a/src/manager/mod.rs b/src/manager/mod.rs new file mode 100644 index 0000000..6f78ede --- /dev/null +++ b/src/manager/mod.rs @@ -0,0 +1,9 @@ +pub mod none; +pub mod socket; +pub mod watch; + +pub enum Manager { + Watch, + Socket, + None, +} diff --git a/src/manager/none.rs b/src/manager/none.rs new file mode 100644 index 0000000..f0a53b9 --- /dev/null +++ b/src/manager/none.rs @@ -0,0 +1,9 @@ +use crate::subprocesses::Processes; + +pub fn manage(mut processes: Processes, _options: crate::options::Options) { + for sub in processes.values_mut() { + if let Some(process) = &mut sub.process { + process.wait().unwrap(); + } + } +} diff --git a/src/socket.rs b/src/manager/socket.rs similarity index 94% rename from src/socket.rs rename to src/manager/socket.rs index 8abfb18..0acfea9 100644 --- a/src/socket.rs +++ b/src/manager/socket.rs @@ -1,3 +1,4 @@ +use crate::subprocesses::Processes; use regex::Regex; use std::fs; use std::io::Read; @@ -23,8 +24,8 @@ fn parse_msg(msg: &str) -> Option { } pub fn manage( - mut processes: super::Processes, - options: super::Options, + mut processes: Processes, + options: crate::options::Options, ) -> Result<(), std::io::Error> { let sock_path = options.sock_path.clone(); fs::remove_file(&sock_path).unwrap_or_default(); diff --git a/src/watch.rs b/src/manager/watch.rs similarity index 96% rename from src/watch.rs rename to src/manager/watch.rs index 9272044..cb3f723 100644 --- a/src/watch.rs +++ b/src/manager/watch.rs @@ -1,3 +1,4 @@ +use crate::subprocesses::Processes; use notify::{watcher, RecursiveMode, Watcher}; use regex::Regex; use std::time::Duration; @@ -18,7 +19,7 @@ fn handle_change( } } -pub fn manage(mut processes: super::Processes, options: super::Options) { +pub fn manage(mut processes: Processes, options: crate::options::Options) { let (sender, receiver) = std::sync::mpsc::channel(); let mut watcher = watcher(sender, Duration::from_secs(2)).unwrap(); diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 0000000..3af4f60 --- /dev/null +++ b/src/options.rs @@ -0,0 +1,89 @@ +use crate::manager::Manager; +use regex::Regex; + +fn print_usage(dollar0: &str, opts: getopts::Options) { + let brief = format!("Usage: {} [options] PROGRAM [root_dir]", dollar0); + println!("{}", opts.usage(&brief)); + println!("PROGRAM will be run in parallel in every subdirectory (SUB), as SUB's owner."); + println!("A placeholder \"{{}}\" is available to PROGRAM, it will be replaced with SUB."); +} + +pub struct Options { + pub sock_path: String, + pub program: String, + pub root_dir: String, + pub watch_ignore: Option, + pub management: super::Manager, +} + +pub fn get_options() -> Options { + let args: Vec = std::env::args().collect(); + let mut opts = getopts::Options::new(); + + opts.optopt( + "t", + "type", + "set the management type [choices: watch, socket, none] [default: none]", + "TYPE", + ); + + opts.optopt( + "s", + "socket", + "set the socket path. sending the socket a message like \"restart xxx\" will restart the process running in the directory \"xxx\". [default: ./subsocket]", + "NAME", + ); + + opts.optopt( + "i", + "watch-ignore", + "pattern to ignore when watching (matches whole path)", + "PATTERN", + ); + + opts.optflag("h", "help", "get help"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => panic!(f.to_string()), + }; + + let program = matches.free.get(0); + + let management = match matches.opt_str("t") { + Some(string) => match string.as_ref() { + "watch" => Some(Manager::Watch), + "socket" | "sock" => Some(Manager::Socket), + "none" => Some(Manager::None), + _ => None, + }, + None => Some(Manager::None), + }; + + let should_show_help = matches.opt_present("h") || program.is_none() || management.is_none(); + + if should_show_help { + print_usage(&args[0], opts); + std::process::exit(1); + } + + let default_root_dir = ".".to_string(); + let root_dir = matches.free.get(1).unwrap_or(&default_root_dir).to_string(); + + let sock_path = matches + .opt_str("s") + .unwrap_or_else(|| "subsocket".to_string()); + + let watch_ignore = match matches.opt_str("i") { + Some(string) => Some(Regex::new(&string).expect("-i was not a valid regex")), + None => None, + }; + + Options { + program: program.unwrap().to_string(), + watch_ignore, + sock_path, + root_dir, + management: management.unwrap(), + } +} diff --git a/src/subprocesses.rs b/src/subprocesses.rs new file mode 100644 index 0000000..ec69904 --- /dev/null +++ b/src/subprocesses.rs @@ -0,0 +1,71 @@ +use std::fs::{metadata, read_dir, DirEntry}; +use std::os::unix::prelude::*; +use std::process::{Child, Command}; + +pub struct Subprocess { + name: String, + uid: u32, + gid: u32, + dir: String, + pub process: Option, +} + +impl Subprocess { + fn make_process(&self, program: &str) -> Result { + Command::new("sh") + .arg("-c") + .arg(program) + .current_dir(&self.dir) + .uid(self.uid) + .gid(self.gid) + .spawn() + } + + fn new(path: &std::path::PathBuf) -> Result { + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + let dir = path.to_str().unwrap().to_string(); + let sub_metadata = metadata(&dir)?; + let uid = sub_metadata.uid(); + let gid = sub_metadata.gid(); + + Ok(Subprocess { + name, + uid, + gid, + dir, + process: None, + }) + } + + pub fn start(&mut self, program: &str) -> Result<(), std::io::Error> { + if let Some(process) = &mut self.process { + process.kill()?; + } + let program = program.replace("{}", &self.name); + self.process = Some(self.make_process(&program)?); + Ok(()) + } +} + +pub type Processes = std::collections::HashMap; + +pub fn start(options: &crate::options::Options) -> Result { + let subdirectories = read_dir(&options.root_dir)?; + let mut processes: Processes = std::collections::HashMap::new(); + + for sub in subdirectories { + let sub: DirEntry = sub?; + let path = sub.path(); + if !path.is_dir() { + println!("Ignoring non-directory: {:?}", path); + continue; + } + let mut process = Subprocess::new(&path)?; + process.start(&options.program)?; + if let Some(name) = path.file_name() { + processes.insert(name.to_str().unwrap().to_string(), process); + } + } + + Ok(processes) +}