From 68caf042690d0b2110d047c42b764e59b0a5d470 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Mon, 13 Mar 2017 18:55:12 +0100 Subject: [PATCH 01/17] First commit to add a generic --signal flag --- src/cli.rs | 20 ++++++++++++++------ src/main.rs | 29 ++++++++++++++--------------- src/process.rs | 21 ++++++++++++++++----- src/signal.rs | 29 ++++++++++++++++++++++++----- 4 files changed, 68 insertions(+), 31 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 3e9e9069..3fe02814 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -10,7 +10,7 @@ pub struct Args { pub filters: Vec, pub ignores: Vec, pub clear_screen: bool, - pub kill: bool, + pub signal: String, pub restart: bool, pub debug: bool, pub run_initially: bool, @@ -60,6 +60,13 @@ pub fn get_args() -> Args { .help("Restart the process if it's still running") .short("r") .long("restart")) + .arg(Arg::with_name("signal") // TODO: --signal only makes sense when used with --restart + .help("Signal to send when --restart is used, defaults to SIGTERM") + .short("s") + .long("signal") + .takes_value(true) + .number_of_values(1) + .value_name("signal")) .arg(Arg::with_name("debug") .help("Print debugging messages to stderr") .short("d") @@ -91,10 +98,6 @@ pub fn get_args() -> Args { .help("Forces polling mode") .long("force-poll") .value_name("interval")) - .arg(Arg::with_name("kill") - .help("Send SIGKILL to child processes") - .short("k") - .long("kill")) .arg(Arg::with_name("once") .short("1") .hidden(true)) @@ -103,6 +106,11 @@ pub fn get_args() -> Args { let cmd = values_t!(args.values_of("command"), String).unwrap().join(" "); let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]); + // TODO: I suppose there must be a better way of getting a string and a default value in clap + let signal = values_t!(args.values_of("signal"), String) + .unwrap_or(vec![String::from("SIGTERM")]) // TODO: Use SIGHUP as default? + .join(" "); + let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(vec![]); if let Some(extensions) = args.values_of("extensions") { @@ -134,8 +142,8 @@ pub fn get_args() -> Args { paths: paths, filters: filters, ignores: ignores, + signal: signal, clear_screen: args.is_present("clear"), - kill: args.is_present("kill"), restart: args.is_present("restart"), debug: args.is_present("debug"), run_initially: !args.is_present("postpone"), diff --git a/src/main.rs b/src/main.rs index 2690062a..ac875515 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,19 +54,20 @@ fn main() { let args = cli::get_args(); let child_process: Arc>> = Arc::new(RwLock::new(None)); let weak_child = Arc::downgrade(&child_process); - let kill = args.kill; + + // Convert signal string to the corresponding integer + let signal = signal::new(&*args.signal); signal::install_handler(move |sig: Signal| { if let Some(lock) = weak_child.upgrade() { let strong = lock.read().unwrap(); if let Some(ref child) = *strong { match sig { + // TODO: This should be generalized to use new --signal flag + // TODO: Not sure what this is doing tbh :( Signal::Terminate => { - if kill { - child.kill(); - } else { - child.terminate(); - } + // TODO: Removed kill variable for now + child.terminate(); } Signal::Stop => child.pause(), Signal::Continue => child.resume(), @@ -123,7 +124,8 @@ fn main() { } // Wait for current child process to exit - wait_process(&child_process, kill, args.restart); + // Note: signal is cloned here automatically + wait_process(&child_process, signal, args.restart); // Launch child process if args.clear_screen { @@ -138,7 +140,8 @@ fn main() { // Handle once option for integration testing if args.once { - wait_process(&child_process, kill, false); + // Note: signal is cloned here automatically + wait_process(&child_process, signal, false); break; } } @@ -188,17 +191,13 @@ fn wait_fs(rx: &Receiver, filter: &NotificationFilter) -> Vec { paths } -fn wait_process(process: &RwLock>, kill: bool, restart: bool) { +fn wait_process(process: &RwLock>, signal: signal::Signal, restart: bool) { let guard = process.read().unwrap(); if let Some(ref child) = *guard { if restart { - debug!("Stopping child process"); - if kill { - child.kill(); - } else { - child.terminate(); - } + debug!("Stopping child process with {} signal", signal); + child.signal(signal); } debug!("Waiting for process to exit..."); diff --git a/src/process.rs b/src/process.rs index 2cef8f73..5efba179 100644 --- a/src/process.rs +++ b/src/process.rs @@ -13,6 +13,7 @@ mod imp { use std::path::PathBuf; use std::process::Command; use std::sync::*; + use signal::Signal; pub struct Process { pgid: pid_t, @@ -51,12 +52,14 @@ mod imp { }) } + // TODO: Required? pub fn kill(&self) { - self.signal(SIGKILL); + self.c_signal(SIGKILL); } + // TODO: Required? pub fn pause(&self) { - self.signal(SIGTSTP); + self.c_signal(SIGTSTP); } pub fn reap(&self) { @@ -82,11 +85,18 @@ mod imp { } } + // TODO: Is this required? - This can probably be streamlined with just using --signal SIGCONT pub fn resume(&self) { - self.signal(SIGCONT); + self.c_signal(SIGCONT); } - fn signal(&self, sig: c_int) { + pub fn signal(&self, signal: Signal) { + // TODO: Sending dummy signal for now + println!("DEBUG: {}", signal); + self.c_signal(SIGCONT); + } + + fn c_signal(&self, sig: c_int) { extern "C" { fn killpg(pgrp: pid_t, sig: c_int) -> c_int; } @@ -97,8 +107,9 @@ mod imp { } + // TODO: Is this required? pub fn terminate(&self) { - self.signal(SIGTERM); + self.c_signal(SIGTERM); } pub fn wait(&self) { diff --git a/src/signal.rs b/src/signal.rs index be6c7c93..88468ead 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -1,15 +1,31 @@ use std::sync::Mutex; +use std::fmt; lazy_static! { static ref CLEANUP: Mutex>> = Mutex::new(None); } -#[allow(dead_code)] +// TODO: Probably a good idea to use +// https://nix-rust.github.io/nix/nix/sys/signal/enum.Signal.html +#[derive(Debug, Clone, Copy)] pub enum Signal { - Terminate, - Stop, - Continue, - ChildExit, + // TODO: Probably a good idea to use original names here: + // TODO: Add SIGUSR1+2 SIGHUP here? + Terminate, // SIGTERM + Stop, // SIGTSTP + Continue, // SIGCONT + ChildExit, // SIGCHLD +} + +impl fmt::Display for Signal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +pub fn new(signal_name: &str) -> Signal { + println!("Using signal {}", signal_name); + Signal::Terminate } #[cfg(unix)] @@ -25,9 +41,12 @@ pub fn install_handler(handler: F) let mut mask = SigSet::empty(); mask.add(SIGTERM); mask.add(SIGINT); + // mask.add(SIGHUP); mask.add(SIGTSTP); mask.add(SIGCONT); mask.add(SIGCHLD); + // mask.add(SIGUSR1); + // mask.add(SIGUSR2); mask.thread_set_mask().expect("unable to set signal mask"); set_handler(handler); From 24e9b0c1c5c25e726bc8e7791538f952aa8ff552 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Mon, 13 Mar 2017 19:00:45 +0100 Subject: [PATCH 02/17] Migrate to nix::sys::signal::Signal --- src/main.rs | 15 ++++++++------- src/process.rs | 3 +-- src/signal.rs | 46 ++++++++++------------------------------------ 3 files changed, 19 insertions(+), 45 deletions(-) diff --git a/src/main.rs b/src/main.rs index ac875515..b548130f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,8 +34,8 @@ use std::time::Duration; use notification_filter::NotificationFilter; use process::Process; -use signal::Signal; use watcher::{Event, Watcher}; +use nix::sys::signal::Signal; fn init_logger(debug: bool) { let mut log_builder = env_logger::LogBuilder::new(); @@ -65,13 +65,14 @@ fn main() { match sig { // TODO: This should be generalized to use new --signal flag // TODO: Not sure what this is doing tbh :( - Signal::Terminate => { + Signal::SIGTERM => { // TODO: Removed kill variable for now child.terminate(); } - Signal::Stop => child.pause(), - Signal::Continue => child.resume(), - Signal::ChildExit => child.reap(), + Signal::SIGSTOP => child.pause(), + Signal::SIGCONT => child.resume(), + Signal::SIGCHLD => child.reap(), + _ => debug!("Unhandled signal: {:?}", sig), } } } @@ -191,12 +192,12 @@ fn wait_fs(rx: &Receiver, filter: &NotificationFilter) -> Vec { paths } -fn wait_process(process: &RwLock>, signal: signal::Signal, restart: bool) { +fn wait_process(process: &RwLock>, signal: Signal, restart: bool) { let guard = process.read().unwrap(); if let Some(ref child) = *guard { if restart { - debug!("Stopping child process with {} signal", signal); + debug!("Stopping child process with {:?} signal", signal); child.signal(signal); } diff --git a/src/process.rs b/src/process.rs index 5efba179..739cd466 100644 --- a/src/process.rs +++ b/src/process.rs @@ -13,7 +13,7 @@ mod imp { use std::path::PathBuf; use std::process::Command; use std::sync::*; - use signal::Signal; + use nix::sys::signal::Signal; pub struct Process { pgid: pid_t, @@ -92,7 +92,6 @@ mod imp { pub fn signal(&self, signal: Signal) { // TODO: Sending dummy signal for now - println!("DEBUG: {}", signal); self.c_signal(SIGCONT); } diff --git a/src/signal.rs b/src/signal.rs index 88468ead..5308fba1 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -1,31 +1,13 @@ use std::sync::Mutex; -use std::fmt; +use nix::sys::signal::Signal; lazy_static! { static ref CLEANUP: Mutex>> = Mutex::new(None); } -// TODO: Probably a good idea to use -// https://nix-rust.github.io/nix/nix/sys/signal/enum.Signal.html -#[derive(Debug, Clone, Copy)] -pub enum Signal { - // TODO: Probably a good idea to use original names here: - // TODO: Add SIGUSR1+2 SIGHUP here? - Terminate, // SIGTERM - Stop, // SIGTSTP - Continue, // SIGCONT - ChildExit, // SIGCHLD -} - -impl fmt::Display for Signal { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - pub fn new(signal_name: &str) -> Signal { println!("Using signal {}", signal_name); - Signal::Terminate + Signal::SIGTERM } #[cfg(unix)] @@ -64,36 +46,28 @@ pub fn install_handler(handler: F) // Spawn a thread to catch these signals thread::spawn(move || { loop { - let raw_signal = mask.wait().expect("unable to sigwait"); - debug!("Received {:?}", raw_signal); - - let sig = match raw_signal { - SIGTERM | SIGINT => self::Signal::Terminate, - SIGTSTP => self::Signal::Stop, - SIGCONT => self::Signal::Continue, - SIGCHLD => self::Signal::ChildExit, - _ => unreachable!(), - }; + let signal = mask.wait().expect("Unable to sigwait"); + debug!("Received {:?}", signal); // Invoke closure - invoke(sig); + invoke(signal); // Restore default behavior for received signal and unmask it - if raw_signal != SIGCHLD { + if signal != SIGCHLD { let default_action = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()); unsafe { - let _ = sigaction(raw_signal, &default_action); + let _ = sigaction(signal, &default_action); } } let mut new_mask = SigSet::empty(); - new_mask.add(raw_signal); + new_mask.add(signal); // Re-raise with signal unmasked let _ = new_mask.thread_unblock(); - let _ = raise(raw_signal); + let _ = raise(signal); let _ = new_mask.thread_block(); } }); @@ -107,7 +81,7 @@ pub fn install_handler(handler: F) use winapi::{BOOL, DWORD, FALSE, TRUE}; pub unsafe extern "system" fn ctrl_handler(_: DWORD) -> BOOL { - invoke(self::Signal::Terminate); + invoke(self::Signal::SIGTERM); FALSE } From eebe966b5057c31ebffc7a6b2524c764f8843a66 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Tue, 14 Mar 2017 00:50:42 +0100 Subject: [PATCH 03/17] Remove deprecated signal functions --- src/process.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/process.rs b/src/process.rs index 739cd466..bf3e6431 100644 --- a/src/process.rs +++ b/src/process.rs @@ -52,16 +52,6 @@ mod imp { }) } - // TODO: Required? - pub fn kill(&self) { - self.c_signal(SIGKILL); - } - - // TODO: Required? - pub fn pause(&self) { - self.c_signal(SIGTSTP); - } - pub fn reap(&self) { use nix::sys::wait::*; @@ -85,11 +75,6 @@ mod imp { } } - // TODO: Is this required? - This can probably be streamlined with just using --signal SIGCONT - pub fn resume(&self) { - self.c_signal(SIGCONT); - } - pub fn signal(&self, signal: Signal) { // TODO: Sending dummy signal for now self.c_signal(SIGCONT); @@ -106,11 +91,6 @@ mod imp { } - // TODO: Is this required? - pub fn terminate(&self) { - self.c_signal(SIGTERM); - } - pub fn wait(&self) { let mut done = self.lock.lock().unwrap(); while !*done { From 382981e22d76b80e781fd4836d04cff1000629df Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Tue, 14 Mar 2017 00:52:14 +0100 Subject: [PATCH 04/17] Add more signals to SigSet mask --- src/signal.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/signal.rs b/src/signal.rs index 5308fba1..fde96009 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -21,14 +21,15 @@ pub fn install_handler(handler: F) // Mask all signals interesting to us. The mask propagates // to all threads started after this point. let mut mask = SigSet::empty(); + mask.add(SIGKILL); mask.add(SIGTERM); mask.add(SIGINT); - // mask.add(SIGHUP); - mask.add(SIGTSTP); + mask.add(SIGHUP); + mask.add(SIGSTOP); mask.add(SIGCONT); mask.add(SIGCHLD); - // mask.add(SIGUSR1); - // mask.add(SIGUSR2); + mask.add(SIGUSR1); + mask.add(SIGUSR2); mask.thread_set_mask().expect("unable to set signal mask"); set_handler(handler); From 4adde457dd840cc6c051cdd742cfe6f8be1d94e5 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Tue, 14 Mar 2017 00:52:50 +0100 Subject: [PATCH 05/17] Actually use signal specified in --signal option --- src/main.rs | 14 +++----------- src/process.rs | 22 +++++++++++++++++++--- src/signal.rs | 16 ++++++++++++++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index b548130f..a0d0b454 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,17 +62,10 @@ fn main() { if let Some(lock) = weak_child.upgrade() { let strong = lock.read().unwrap(); if let Some(ref child) = *strong { + use nix::sys::signal::*; match sig { - // TODO: This should be generalized to use new --signal flag - // TODO: Not sure what this is doing tbh :( - Signal::SIGTERM => { - // TODO: Removed kill variable for now - child.terminate(); - } - Signal::SIGSTOP => child.pause(), - Signal::SIGCONT => child.resume(), - Signal::SIGCHLD => child.reap(), - _ => debug!("Unhandled signal: {:?}", sig), + SIGCHLD => child.reap(), // SIGCHLD is special, initiate reap() + _ => child.signal(sig), } } } @@ -197,7 +190,6 @@ fn wait_process(process: &RwLock>, signal: Signal, restart: bool if let Some(ref child) = *guard { if restart { - debug!("Stopping child process with {:?} signal", signal); child.signal(signal); } diff --git a/src/process.rs b/src/process.rs index bf3e6431..e0b95268 100644 --- a/src/process.rs +++ b/src/process.rs @@ -13,7 +13,7 @@ mod imp { use std::path::PathBuf; use std::process::Command; use std::sync::*; - use nix::sys::signal::Signal; + use signal::Signal; pub struct Process { pgid: pid_t, @@ -76,8 +76,24 @@ mod imp { } pub fn signal(&self, signal: Signal) { - // TODO: Sending dummy signal for now - self.c_signal(SIGCONT); + + // Convert from signal::Signal enum to libc::* c_int constants + // TODO: This probably belongs into signal.rs (Maybe directly using libc::SIG*) + let signo = match signal { + Signal::SIGKILL => SIGKILL, + Signal::SIGTERM => SIGTERM, + Signal::SIGINT => SIGINT, + Signal::SIGHUP => SIGHUP, + Signal::SIGSTOP => SIGSTOP, + Signal::SIGCONT => SIGCONT, + Signal::SIGCHLD => SIGCHLD, + Signal::SIGUSR1 => SIGUSR1, + Signal::SIGUSR2 => SIGUSR2, + _ => panic!("unsupported signal: {:?}", signal), + }; + + debug!("Sending {:?} (int: {}) to child process", signal, signo); + self.c_signal(signo); } fn c_signal(&self, sig: c_int) { diff --git a/src/signal.rs b/src/signal.rs index fde96009..751b1a30 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -6,8 +6,20 @@ lazy_static! { } pub fn new(signal_name: &str) -> Signal { - println!("Using signal {}", signal_name); - Signal::SIGTERM + use nix::sys::signal::*; + + match signal_name { + "SIGKILL" | "KILL" => SIGKILL, + "SIGTERM" | "TERM" => SIGTERM, + "SIGINT" | "INT" => SIGINT, + "SIGHUP" | "HUP" => SIGHUP, + "SIGSTOP" | "STOP" => SIGSTOP, + "SIGCONT" | "CONT" => SIGCONT, + "SIGCHLD" | "CHLD" => SIGCHLD, + "SIGUSR1" | "USR1" => SIGUSR1, + "SIGUSR2" | "USR2" => SIGUSR2, + _ => panic!("unsupported signal: {}", signal_name), + } } #[cfg(unix)] From 0a445c9f76f27f66ca920ccfd6f00d9a888914a7 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Tue, 14 Mar 2017 17:18:43 +0100 Subject: [PATCH 06/17] Move convert_to_libc() method to signal.rs --- src/process.rs | 17 ++--------------- src/signal.rs | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/process.rs b/src/process.rs index e0b95268..0d52f1f7 100644 --- a/src/process.rs +++ b/src/process.rs @@ -76,22 +76,9 @@ mod imp { } pub fn signal(&self, signal: Signal) { + use signal::ConvertToLibc; - // Convert from signal::Signal enum to libc::* c_int constants - // TODO: This probably belongs into signal.rs (Maybe directly using libc::SIG*) - let signo = match signal { - Signal::SIGKILL => SIGKILL, - Signal::SIGTERM => SIGTERM, - Signal::SIGINT => SIGINT, - Signal::SIGHUP => SIGHUP, - Signal::SIGSTOP => SIGSTOP, - Signal::SIGCONT => SIGCONT, - Signal::SIGCHLD => SIGCHLD, - Signal::SIGUSR1 => SIGUSR1, - Signal::SIGUSR2 => SIGUSR2, - _ => panic!("unsupported signal: {:?}", signal), - }; - + let signo = signal.convert_to_libc(); debug!("Sending {:?} (int: {}) to child process", signal, signo); self.c_signal(signo); } diff --git a/src/signal.rs b/src/signal.rs index 751b1a30..cbaf3dfb 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -1,3 +1,4 @@ +use libc::*; use std::sync::Mutex; use nix::sys::signal::Signal; @@ -5,6 +6,28 @@ lazy_static! { static ref CLEANUP: Mutex>> = Mutex::new(None); } +pub trait ConvertToLibc { + fn convert_to_libc(self) -> c_int; +} + +impl ConvertToLibc for Signal { + fn convert_to_libc(self) -> c_int { + // Convert from signal::Signal enum to libc::* c_int constants + match self { + Signal::SIGKILL => SIGKILL, + Signal::SIGTERM => SIGTERM, + Signal::SIGINT => SIGINT, + Signal::SIGHUP => SIGHUP, + Signal::SIGSTOP => SIGSTOP, + Signal::SIGCONT => SIGCONT, + Signal::SIGCHLD => SIGCHLD, + Signal::SIGUSR1 => SIGUSR1, + Signal::SIGUSR2 => SIGUSR2, + _ => panic!("unsupported signal: {:?}", self), + } + } +} + pub fn new(signal_name: &str) -> Signal { use nix::sys::signal::*; From a2f6b0013f74a0240a3d1637aa746ac3af631d86 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Tue, 14 Mar 2017 17:30:19 +0100 Subject: [PATCH 07/17] Add windows compatibility --- src/main.rs | 5 ++--- src/process.rs | 12 +++--------- src/signal.rs | 45 ++++++++++++++++++++++++++++++++------------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index a0d0b454..fe87b62c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,8 +34,8 @@ use std::time::Duration; use notification_filter::NotificationFilter; use process::Process; +use signal::Signal; use watcher::{Event, Watcher}; -use nix::sys::signal::Signal; fn init_logger(debug: bool) { let mut log_builder = env_logger::LogBuilder::new(); @@ -62,9 +62,8 @@ fn main() { if let Some(lock) = weak_child.upgrade() { let strong = lock.read().unwrap(); if let Some(ref child) = *strong { - use nix::sys::signal::*; match sig { - SIGCHLD => child.reap(), // SIGCHLD is special, initiate reap() + Signal::SIGCHLD => child.reap(), // SIGCHLD is special, initiate reap() _ => child.signal(sig), } } diff --git a/src/process.rs b/src/process.rs index 0d52f1f7..38c8ec9a 100644 --- a/src/process.rs +++ b/src/process.rs @@ -112,6 +112,7 @@ mod imp { use std::process::Command; use kernel32::*; use winapi::*; + use signal::Signal; pub struct Process { job: HANDLE, @@ -163,17 +164,10 @@ mod imp { }) } - pub fn kill(&self) { - self.terminate(); - } - - pub fn pause(&self) {} - pub fn reap(&self) {} - pub fn resume(&self) {} - - pub fn terminate(&self) { + pub fn signal(&self, signal: Signal) { + debug!("Ignoring signal {:?} (not supported by Windows)", signal); unsafe { let _ = TerminateJobObject(self.job, 1); } diff --git a/src/signal.rs b/src/signal.rs index cbaf3dfb..17e6aba7 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -1,15 +1,36 @@ -use libc::*; use std::sync::Mutex; -use nix::sys::signal::Signal; lazy_static! { static ref CLEANUP: Mutex>> = Mutex::new(None); } +#[cfg(unix)] +pub use nix::sys::signal::Signal; + +// This is a dummy enum for Windows +#[cfg(windows)] +#[derive(Debug, Copy, Clone)] +pub enum Signal { + SIGKILL, + SIGTERM, + SIGINT, + SIGHUP, + SIGSTOP, + SIGCONT, + SIGCHLD, + SIGUSR1, + SIGUSR2, +} + +#[cfg(unix)] +use libc::*; + +#[cfg(unix)] pub trait ConvertToLibc { fn convert_to_libc(self) -> c_int; } +#[cfg(unix)] impl ConvertToLibc for Signal { fn convert_to_libc(self) -> c_int { // Convert from signal::Signal enum to libc::* c_int constants @@ -29,18 +50,16 @@ impl ConvertToLibc for Signal { } pub fn new(signal_name: &str) -> Signal { - use nix::sys::signal::*; - match signal_name { - "SIGKILL" | "KILL" => SIGKILL, - "SIGTERM" | "TERM" => SIGTERM, - "SIGINT" | "INT" => SIGINT, - "SIGHUP" | "HUP" => SIGHUP, - "SIGSTOP" | "STOP" => SIGSTOP, - "SIGCONT" | "CONT" => SIGCONT, - "SIGCHLD" | "CHLD" => SIGCHLD, - "SIGUSR1" | "USR1" => SIGUSR1, - "SIGUSR2" | "USR2" => SIGUSR2, + "SIGKILL" | "KILL" => Signal::SIGKILL, + "SIGTERM" | "TERM" => Signal::SIGTERM, + "SIGINT" | "INT" => Signal::SIGINT, + "SIGHUP" | "HUP" => Signal::SIGHUP, + "SIGSTOP" | "STOP" => Signal::SIGSTOP, + "SIGCONT" | "CONT" => Signal::SIGCONT, + "SIGCHLD" | "CHLD" => Signal::SIGCHLD, + "SIGUSR1" | "USR1" => Signal::SIGUSR1, + "SIGUSR2" | "USR2" => Signal::SIGUSR2, _ => panic!("unsupported signal: {}", signal_name), } } From debfafb47b93d657c72a113281396d9a543e34a1 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Tue, 14 Mar 2017 23:47:11 +0100 Subject: [PATCH 08/17] Adapt README to --signal option --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f45fc84..abfd20c8 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Call/restart `python server.py` when any Python file in the current directory (a Call/restart `my_server` when any file in the current directory (and all subdirectories) changes, sending `SIGKILL` to stop the child process: - $ watchexec -r -k my_server + $ watchexec -r -s SIGKILL my_server Run `make` when any file changes, using the `.gitignore` file in the current directory to filter: From 3a5ff290d5e16dfdf306e4096b75812ae0150558 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Thu, 23 Mar 2017 21:23:55 +0100 Subject: [PATCH 09/17] Use a more idiomatic way to set --signal --- src/cli.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 3fe02814..1846bbe6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -105,11 +105,7 @@ pub fn get_args() -> Args { let cmd = values_t!(args.values_of("command"), String).unwrap().join(" "); let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]); - - // TODO: I suppose there must be a better way of getting a string and a default value in clap - let signal = values_t!(args.values_of("signal"), String) - .unwrap_or(vec![String::from("SIGTERM")]) // TODO: Use SIGHUP as default? - .join(" "); + let signal = args.value_of("signal").unwrap_or("SIGTERM").to_owned(); let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(vec![]); From 56ddfcbaee9b35f98537944252e4174c5ba299cf Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Thu, 23 Mar 2017 23:39:38 +0100 Subject: [PATCH 10/17] Apply rustfmt-0.8.1 --- src/gitignore.rs | 21 ++++++++++----------- src/main.rs | 17 +++++++++-------- src/notification_filter.rs | 10 +++++----- src/process.rs | 31 +++++++++++++++++-------------- src/signal.rs | 3 ++- 5 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/gitignore.rs b/src/gitignore.rs index 35de420b..473b06e4 100644 --- a/src/gitignore.rs +++ b/src/gitignore.rs @@ -137,19 +137,17 @@ impl GitignoreFile { pat = pat + "/**"; } - let glob = try!(GlobBuilder::new(&pat) - .literal_separator(true) - .build()); + let glob = try!(GlobBuilder::new(&pat).literal_separator(true).build()); builder.add(glob); patterns.push(p); } Ok(GitignoreFile { - set: try!(builder.build()), - patterns: patterns, - root: root.to_owned(), - }) + set: try!(builder.build()), + patterns: patterns, + root: root.to_owned(), + }) } @@ -169,9 +167,9 @@ impl GitignoreFile { for &i in matches.iter().rev() { let pattern = &self.patterns[i]; return match pattern.pattern_type { - PatternType::Whitelist => MatchResult::Whitelist, - PatternType::Ignore => MatchResult::Ignore, - }; + PatternType::Whitelist => MatchResult::Whitelist, + PatternType::Ignore => MatchResult::Ignore, + }; } MatchResult::None @@ -182,7 +180,8 @@ impl GitignoreFile { } fn parse(contents: Vec<&str>) -> Vec { - contents.iter() + contents + .iter() .filter(|l| !l.is_empty()) .filter(|l| !l.starts_with('#')) .map(|l| Pattern::parse(l)) diff --git a/src/main.rs b/src/main.rs index fe87b62c..614761fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,8 @@ fn init_logger(debug: bool) { log::LogLevelFilter::Warn }; - log_builder.format(|r| format!("*** {}", r.args())) + log_builder + .format(|r| format!("*** {}", r.args())) .filter(None, level); log_builder.init().expect("unable to initialize logger"); } @@ -75,11 +76,11 @@ fn main() { let paths: Vec = args.paths .iter() .map(|p| { - Path::new(&p) - .canonicalize() - .expect(&format!("unable to canonicalize \"{}\"", &p)) - .to_owned() - }) + Path::new(&p) + .canonicalize() + .expect(&format!("unable to canonicalize \"{}\"", &p)) + .to_owned() + }) .collect(); let gitignore = if !args.no_vcs_ignore { @@ -92,8 +93,8 @@ fn main() { .expect("unable to create notification filter"); let (tx, rx) = channel(); - let watcher = Watcher::new(tx, &paths, args.poll, args.poll_interval) - .expect("unable to create watcher"); + let watcher = + Watcher::new(tx, &paths, args.poll, args.poll_interval).expect("unable to create watcher"); if watcher.is_polling() { warn!("Polling for changes every {} ms", args.poll_interval); diff --git a/src/notification_filter.rs b/src/notification_filter.rs index f0a0302a..8c0c607e 100644 --- a/src/notification_filter.rs +++ b/src/notification_filter.rs @@ -44,11 +44,11 @@ impl NotificationFilter { let ignore_set = try!(ignore_set_builder.build()); Ok(NotificationFilter { - filters: filter_set, - filter_count: filters.len(), - ignores: ignore_set, - ignore_files: ignore_files, - }) + filters: filter_set, + filter_count: filters.len(), + ignores: ignore_set, + ignore_files: ignore_files, + }) } pub fn is_excluded(&self, path: &Path) -> bool { diff --git a/src/process.rs b/src/process.rs index 38c8ec9a..b0b7ff9b 100644 --- a/src/process.rs +++ b/src/process.rs @@ -41,15 +41,16 @@ mod imp { } // Until process_exec lands in stable, handle fork/exec ourselves - command.before_exec(|| setpgid(0, 0).map_err(io::Error::from)) + command + .before_exec(|| setpgid(0, 0).map_err(io::Error::from)) .spawn() .and_then(|p| { - Ok(Process { - pgid: p.id() as i32, - lock: Mutex::new(false), - cvar: Condvar::new(), - }) - }) + Ok(Process { + pgid: p.id() as i32, + lock: Mutex::new(false), + cvar: Condvar::new(), + }) + }) } pub fn reap(&self) { @@ -154,14 +155,16 @@ mod imp { command.env("WATCHEXEC_COMMON_PATH", common_path); } - command.spawn().and_then(|p| { - let r = unsafe { AssignProcessToJobObject(job, p.into_raw_handle()) }; - if r == 0 { - panic!("failed to add to job object: {}", last_err()); - } + command + .spawn() + .and_then(|p| { + let r = unsafe { AssignProcessToJobObject(job, p.into_raw_handle()) }; + if r == 0 { + panic!("failed to add to job object: {}", last_err()); + } - Ok(Process { job: job }) - }) + Ok(Process { job: job }) + }) } pub fn reap(&self) {} diff --git a/src/signal.rs b/src/signal.rs index 17e6aba7..fd61c8b7 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -84,7 +84,8 @@ pub fn install_handler(handler: F) mask.add(SIGCHLD); mask.add(SIGUSR1); mask.add(SIGUSR2); - mask.thread_set_mask().expect("unable to set signal mask"); + mask.thread_set_mask() + .expect("unable to set signal mask"); set_handler(handler); From c98d0e6cfd4331f455f6abee460c848ee8dccfa0 Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Thu, 23 Mar 2017 23:50:39 +0100 Subject: [PATCH 11/17] Decouple --restart and --signal, so they both make sense This change takes account of the following four use cases: 1. Make sure the previous run was ended, then run the command again (default) 2. Just send a specified signal to the child, do nothing more (--signal given) 3. Send SIGTERM to the child, wait for it to exit, then run the command again (--restart given) 4. Send a specified signal to the child, wait for it to exit, then run the command again (--restart and --signal given) --- src/cli.rs | 141 +++++++++++++++++++++++++------------------------- src/main.rs | 91 +++++++++++++++++++++++++------- src/signal.rs | 30 ++++++----- 3 files changed, 161 insertions(+), 101 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 1846bbe6..7f4e6b2d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -10,7 +10,7 @@ pub struct Args { pub filters: Vec, pub ignores: Vec, pub clear_screen: bool, - pub signal: String, + pub signal: Option, pub restart: bool, pub debug: bool, pub run_initially: bool, @@ -33,79 +33,78 @@ pub fn clear_screen() { #[allow(unknown_lints)] #[allow(or_fun_call)] pub fn get_args() -> Args { - let args = App::new("watchexec") - .version(crate_version!()) - .about("Execute commands when watched files change") - .arg(Arg::with_name("command") - .help("Command to execute") - .multiple(true) - .required(true)) - .arg(Arg::with_name("extensions") - .help("Comma-separated list of file extensions to watch (js,css,html)") - .short("e") - .long("exts") - .takes_value(true)) - .arg(Arg::with_name("path") - .help("Watch a specific directory") - .short("w") - .long("watch") - .number_of_values(1) - .multiple(true) - .takes_value(true)) - .arg(Arg::with_name("clear") - .help("Clear screen before executing command") - .short("c") - .long("clear")) - .arg(Arg::with_name("restart") - .help("Restart the process if it's still running") - .short("r") - .long("restart")) - .arg(Arg::with_name("signal") // TODO: --signal only makes sense when used with --restart - .help("Signal to send when --restart is used, defaults to SIGTERM") - .short("s") - .long("signal") - .takes_value(true) - .number_of_values(1) - .value_name("signal")) - .arg(Arg::with_name("debug") - .help("Print debugging messages to stderr") - .short("d") - .long("debug")) - .arg(Arg::with_name("filter") - .help("Ignore all modifications except those matching the pattern") - .short("f") - .long("filter") - .number_of_values(1) - .multiple(true) - .takes_value(true) - .value_name("pattern")) - .arg(Arg::with_name("ignore") - .help("Ignore modifications to paths matching the pattern") - .short("i") - .long("ignore") - .number_of_values(1) - .multiple(true) - .takes_value(true) - .value_name("pattern")) - .arg(Arg::with_name("no-vcs-ignore") - .help("Skip auto-loading of .gitignore files for filtering") - .long("no-vcs-ignore")) - .arg(Arg::with_name("postpone") - .help("Wait until first change to execute command") - .short("p") - .long("postpone")) - .arg(Arg::with_name("poll") - .help("Forces polling mode") - .long("force-poll") - .value_name("interval")) - .arg(Arg::with_name("once") - .short("1") - .hidden(true)) - .get_matches(); + let args = + App::new("watchexec") + .version(crate_version!()) + .about("Execute commands when watched files change") + .arg(Arg::with_name("command") + .help("Command to execute") + .multiple(true) + .required(true)) + .arg(Arg::with_name("extensions") + .help("Comma-separated list of file extensions to watch (js,css,html)") + .short("e") + .long("exts") + .takes_value(true)) + .arg(Arg::with_name("path") + .help("Watch a specific directory") + .short("w") + .long("watch") + .number_of_values(1) + .multiple(true) + .takes_value(true)) + .arg(Arg::with_name("clear") + .help("Clear screen before executing command") + .short("c") + .long("clear")) + .arg(Arg::with_name("restart") + .help("Restart the process if it's still running") + .short("r") + .long("restart")) + .arg(Arg::with_name("signal") + .help("Send signal to process upon changes, e.g. SIGHUP") + .short("s") + .long("signal") + .takes_value(true) + .number_of_values(1) + .value_name("signal")) + .arg(Arg::with_name("debug") + .help("Print debugging messages to stderr") + .short("d") + .long("debug")) + .arg(Arg::with_name("filter") + .help("Ignore all modifications except those matching the pattern") + .short("f") + .long("filter") + .number_of_values(1) + .multiple(true) + .takes_value(true) + .value_name("pattern")) + .arg(Arg::with_name("ignore") + .help("Ignore modifications to paths matching the pattern") + .short("i") + .long("ignore") + .number_of_values(1) + .multiple(true) + .takes_value(true) + .value_name("pattern")) + .arg(Arg::with_name("no-vcs-ignore") + .help("Skip auto-loading of .gitignore files for filtering") + .long("no-vcs-ignore")) + .arg(Arg::with_name("postpone") + .help("Wait until first change to execute command") + .short("p") + .long("postpone")) + .arg(Arg::with_name("poll") + .help("Forces polling mode") + .long("force-poll") + .value_name("interval")) + .arg(Arg::with_name("once").short("1").hidden(true)) + .get_matches(); let cmd = values_t!(args.values_of("command"), String).unwrap().join(" "); let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]); - let signal = args.value_of("signal").unwrap_or("SIGTERM").to_owned(); + let signal = args.value_of("signal").map(str::to_string); // Convert Option<&str> to Option let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(vec![]); diff --git a/src/main.rs b/src/main.rs index 614761fc..da8c9642 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,7 +57,7 @@ fn main() { let weak_child = Arc::downgrade(&child_process); // Convert signal string to the corresponding integer - let signal = signal::new(&*args.signal); + let signal = signal::new(args.signal); signal::install_handler(move |sig: Signal| { if let Some(lock) = weak_child.upgrade() { @@ -117,24 +117,76 @@ fn main() { debug!("Path updated: {:?}", path); } - // Wait for current child process to exit - // Note: signal is cloned here automatically - wait_process(&child_process, signal, args.restart); + // We have three scenarios here: + // + // 1. Make sure the previous run was ended, then run the command again + // 2. Just send a specified signal to the child, do nothing more + // 3. Send SIGTERM to the child, wait for it to exit, then run the command again + // 4. Send a specified signal to the child, wait for it to exit, then run the command again + // + match args.restart { + // Custom restart behaviour (--restart was given, and --signal specified): + // Send specified signal to the child, wait for it to exit, then run the command again + true if signal.is_some() => { + wait_process(&child_process, signal, true); - // Launch child process - if args.clear_screen { - cli::clear_screen(); - } + // Launch child process + if args.clear_screen { + cli::clear_screen(); + } - debug!("Launching child process"); - { - let mut guard = child_process.write().unwrap(); - *guard = Some(process::spawn(&args.cmd, paths)); + debug!("Launching child process"); + { + let mut guard = child_process.write().unwrap(); + *guard = Some(process::spawn(&args.cmd, paths)); + } + } + + // Default restart behaviour (--restart was given, but --signal wasn't specified): + // Send SIGTERM to the child, wait for it to exit, then run the command again + true if signal.is_none() => { + let sigterm = signal::new(Some("SIGTERM".to_owned())); + wait_process(&child_process, sigterm, true); + + // Launch child process + if args.clear_screen { + cli::clear_screen(); + } + + debug!("Launching child process"); + { + let mut guard = child_process.write().unwrap(); + *guard = Some(process::spawn(&args.cmd, paths)); + } + } + + // SIGHUP scenario: --signal was given, but --restart was not + // Just send a signal (e.g. SIGHUP) to the child, do nothing more + false if signal.is_some() => wait_process(&child_process, signal, false), + + // Default behaviour (neither --signal nor --restart specified): + // Make sure the previous run was ended, then run the command again + false if signal.is_none() => { + wait_process(&child_process, None, true); + + // Launch child process + if args.clear_screen { + cli::clear_screen(); + } + + debug!("Launching child process"); + { + let mut guard = child_process.write().unwrap(); + *guard = Some(process::spawn(&args.cmd, paths)); + } + } + + // Catch everything else, just to be sure. + _ => panic!("This should never be called. Please file a bug report!"), } // Handle once option for integration testing if args.once { - // Note: signal is cloned here automatically wait_process(&child_process, signal, false); break; } @@ -185,15 +237,18 @@ fn wait_fs(rx: &Receiver, filter: &NotificationFilter) -> Vec { paths } -fn wait_process(process: &RwLock>, signal: Signal, restart: bool) { +// wait_process sends signal to process. It waits for the process to exit if wait is true +fn wait_process(process: &RwLock>, signal: Option, wait: bool) { let guard = process.read().unwrap(); if let Some(ref child) = *guard { - if restart { - child.signal(signal); + if let Some(s) = signal { + child.signal(s); } - debug!("Waiting for process to exit..."); - child.wait(); + if wait { + debug!("Waiting for process to exit..."); + child.wait(); + } } } diff --git a/src/signal.rs b/src/signal.rs index fd61c8b7..ea1c99e1 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -49,18 +49,24 @@ impl ConvertToLibc for Signal { } } -pub fn new(signal_name: &str) -> Signal { - match signal_name { - "SIGKILL" | "KILL" => Signal::SIGKILL, - "SIGTERM" | "TERM" => Signal::SIGTERM, - "SIGINT" | "INT" => Signal::SIGINT, - "SIGHUP" | "HUP" => Signal::SIGHUP, - "SIGSTOP" | "STOP" => Signal::SIGSTOP, - "SIGCONT" | "CONT" => Signal::SIGCONT, - "SIGCHLD" | "CHLD" => Signal::SIGCHLD, - "SIGUSR1" | "USR1" => Signal::SIGUSR1, - "SIGUSR2" | "USR2" => Signal::SIGUSR2, - _ => panic!("unsupported signal: {}", signal_name), +pub fn new(signal_name: Option) -> Option { + if let Some(signame) = signal_name { + let signal = match signame.as_ref() { + "SIGKILL" | "KILL" => Signal::SIGKILL, + "SIGTERM" | "TERM" => Signal::SIGTERM, + "SIGINT" | "INT" => Signal::SIGINT, + "SIGHUP" | "HUP" => Signal::SIGHUP, + "SIGSTOP" | "STOP" => Signal::SIGSTOP, + "SIGCONT" | "CONT" => Signal::SIGCONT, + "SIGCHLD" | "CHLD" => Signal::SIGCHLD, + "SIGUSR1" | "USR1" => Signal::SIGUSR1, + "SIGUSR2" | "USR2" => Signal::SIGUSR2, + _ => panic!("unsupported signal: {}", signame), + }; + + Some(signal) + } else { + None } } From 14941c89d28b8ae5da405ada933e2f0ace00e6ca Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Thu, 23 Mar 2017 23:51:40 +0100 Subject: [PATCH 12/17] Add SIGHUP example to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index abfd20c8..4db681ca 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ Call/restart `my_server` when any file in the current directory (and all subdire $ watchexec -r -s SIGKILL my_server +Send a SIGHUP to the child process upon changes: + + $ watchexec -s SIGHUP my_server + Run `make` when any file changes, using the `.gitignore` file in the current directory to filter: $ watchexec make From d5da94fcafb0bc4ef3a892734b48cc27bb55258e Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Sun, 26 Mar 2017 18:17:58 +0200 Subject: [PATCH 13/17] Add check for conflicting --signal and --postpone arguments --- src/cli.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cli.rs b/src/cli.rs index 7f4e6b2d..dc74523e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use std::path::MAIN_SEPARATOR; use std::process::Command; -use clap::{App, Arg}; +use clap::{App, Arg, Error}; #[derive(Debug)] pub struct Args { @@ -132,6 +132,12 @@ pub fn get_args() -> Args { 1000 }; + if signal.is_some() && args.is_present("postpone") { + // TODO: Error::argument_conflict() might be the better fit, usage was unclear, though + Error::value_validation_auto(format!("--postpone and --signal are mutually exclusive")) + .exit(); + } + Args { cmd: cmd, paths: paths, From f5f65c3a287af0943fe9e10e5c81b961f84bfd2a Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Sun, 26 Mar 2017 18:25:13 +0200 Subject: [PATCH 14/17] Add --signal documentation to doc/* --- doc/watchexec.1 | 4 ++-- doc/watchexec.1.html | 2 +- doc/watchexec.1.ronn | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/watchexec.1 b/doc/watchexec.1 index ac2c7eb5..c95a17da 100644 --- a/doc/watchexec.1 +++ b/doc/watchexec.1 @@ -30,8 +30,8 @@ Comma\-separated list of file extensions to filter by\. Leading dots are allowed Ignores modifications from paths that do not match \fIpattern\fR\. This option can be specified multiple times, where a match on any given pattern causes the path to trigger \fIcommand\fR\. . .TP -\fB\-k\fR, \fB\-\-kill\fR -Send \fBSIGKILL\fR to the child process group instead of \fBSIGTERM\fR\. +\fB\-s\fR, \fB\-\-signal \fR +Sends the specified signal (e\.g\. \fBSIGKILL\fR) to the child process\. Defaults to \fBSIGTERM\fR\. . .TP \fB\-i\fR, \fB\-\-ignore\fR \fIpattern\fR diff --git a/doc/watchexec.1.html b/doc/watchexec.1.html index ed03541c..765f5cca 100644 --- a/doc/watchexec.1.html +++ b/doc/watchexec.1.html @@ -88,7 +88,7 @@
command

Command to run when watched files are modified, and at startup, unless --postpone is specified. All arguments are passed to command.

-e, --exts extensions

Comma-separated list of file extensions to filter by. Leading dots are allowed (.rs) are allowed. (This is a shorthand for -f).

-f, --filter pattern

Ignores modifications from paths that do not match pattern. This option can be specified multiple times, where a match on any given pattern causes the path to trigger command.

-
-k, --kill

Send SIGKILL to the child process group instead of SIGTERM.

+
-s, --signal SIGNAL

Sends the specified signal (e.g. SIGKILl) to the child process. Defaults to SIGTERM.

-i, --ignore pattern

Ignores modifications from paths that match pattern. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.

-w, --watch path

Monitor a specific path for changes. By default, the current working directory is watched. This may be specified multiple times, where a change in any watched directory (and subdirectories) causes command to be executed.

-r, --restart

Terminates the child process group if it is still running when subsequent file modifications are detected. By default, sends SIGTERM; use --kill to send SIGKILL.

diff --git a/doc/watchexec.1.ronn b/doc/watchexec.1.ronn index 4fa6cc0a..1b8bbe39 100644 --- a/doc/watchexec.1.ronn +++ b/doc/watchexec.1.ronn @@ -22,8 +22,8 @@ Comma-separated list of file extensions to filter by. Leading dots are allowed ( * `-f`, `--filter` : Ignores modifications from paths that do not match . This option can be specified multiple times, where a match on any given pattern causes the path to trigger . -* `-k`, `--kill`: -Send `SIGKILL` to the child process group instead of `SIGTERM`. +* `-s`, `--signal`: +Sends the specified signal (e.g. `SIGKILL`) to the child process. Defaults to `SIGTERM`. * `-i`, `--ignore` : Ignores modifications from paths that match . This option can be specified multiple times, and a match on any pattern causes the path to be ignored. From 627f828b3c39c1b614950ecaf35c94b340cd7ada Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Sun, 2 Apr 2017 20:53:56 +0200 Subject: [PATCH 15/17] Rename wait_process() to signal_process() --- src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index da8c9642..aaff2881 100644 --- a/src/main.rs +++ b/src/main.rs @@ -128,7 +128,7 @@ fn main() { // Custom restart behaviour (--restart was given, and --signal specified): // Send specified signal to the child, wait for it to exit, then run the command again true if signal.is_some() => { - wait_process(&child_process, signal, true); + signal_process(&child_process, signal, true); // Launch child process if args.clear_screen { @@ -146,7 +146,7 @@ fn main() { // Send SIGTERM to the child, wait for it to exit, then run the command again true if signal.is_none() => { let sigterm = signal::new(Some("SIGTERM".to_owned())); - wait_process(&child_process, sigterm, true); + signal_process(&child_process, sigterm, true); // Launch child process if args.clear_screen { @@ -162,12 +162,12 @@ fn main() { // SIGHUP scenario: --signal was given, but --restart was not // Just send a signal (e.g. SIGHUP) to the child, do nothing more - false if signal.is_some() => wait_process(&child_process, signal, false), + false if signal.is_some() => signal_process(&child_process, signal, false), // Default behaviour (neither --signal nor --restart specified): // Make sure the previous run was ended, then run the command again false if signal.is_none() => { - wait_process(&child_process, None, true); + signal_process(&child_process, None, true); // Launch child process if args.clear_screen { @@ -187,7 +187,7 @@ fn main() { // Handle once option for integration testing if args.once { - wait_process(&child_process, signal, false); + signal_process(&child_process, signal, false); break; } } @@ -237,8 +237,8 @@ fn wait_fs(rx: &Receiver, filter: &NotificationFilter) -> Vec { paths } -// wait_process sends signal to process. It waits for the process to exit if wait is true -fn wait_process(process: &RwLock>, signal: Option, wait: bool) { +// signal_process sends signal to process. It waits for the process to exit if wait is true +fn signal_process(process: &RwLock>, signal: Option, wait: bool) { let guard = process.read().unwrap(); if let Some(ref child) = *guard { From 4763de37905861f36ebd72eb7fb16b922661e57a Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Sun, 2 Apr 2017 21:13:44 +0200 Subject: [PATCH 16/17] Re-add --kill flag for compatibility --kill translates to --signal SIGKILL --- src/cli.rs | 160 +++++++++++++++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 72 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index dc74523e..3ef1a7fe 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -33,86 +33,96 @@ pub fn clear_screen() { #[allow(unknown_lints)] #[allow(or_fun_call)] pub fn get_args() -> Args { - let args = - App::new("watchexec") - .version(crate_version!()) - .about("Execute commands when watched files change") - .arg(Arg::with_name("command") - .help("Command to execute") - .multiple(true) - .required(true)) - .arg(Arg::with_name("extensions") - .help("Comma-separated list of file extensions to watch (js,css,html)") - .short("e") - .long("exts") - .takes_value(true)) - .arg(Arg::with_name("path") - .help("Watch a specific directory") - .short("w") - .long("watch") - .number_of_values(1) - .multiple(true) - .takes_value(true)) - .arg(Arg::with_name("clear") - .help("Clear screen before executing command") - .short("c") - .long("clear")) - .arg(Arg::with_name("restart") - .help("Restart the process if it's still running") - .short("r") - .long("restart")) - .arg(Arg::with_name("signal") - .help("Send signal to process upon changes, e.g. SIGHUP") - .short("s") - .long("signal") - .takes_value(true) - .number_of_values(1) - .value_name("signal")) - .arg(Arg::with_name("debug") - .help("Print debugging messages to stderr") - .short("d") - .long("debug")) - .arg(Arg::with_name("filter") - .help("Ignore all modifications except those matching the pattern") - .short("f") - .long("filter") - .number_of_values(1) - .multiple(true) - .takes_value(true) - .value_name("pattern")) - .arg(Arg::with_name("ignore") - .help("Ignore modifications to paths matching the pattern") - .short("i") - .long("ignore") - .number_of_values(1) - .multiple(true) - .takes_value(true) - .value_name("pattern")) - .arg(Arg::with_name("no-vcs-ignore") - .help("Skip auto-loading of .gitignore files for filtering") - .long("no-vcs-ignore")) - .arg(Arg::with_name("postpone") - .help("Wait until first change to execute command") - .short("p") - .long("postpone")) - .arg(Arg::with_name("poll") - .help("Forces polling mode") - .long("force-poll") - .value_name("interval")) - .arg(Arg::with_name("once").short("1").hidden(true)) - .get_matches(); + let args = App::new("watchexec") + .version(crate_version!()) + .about("Execute commands when watched files change") + .arg(Arg::with_name("command") + .help("Command to execute") + .multiple(true) + .required(true)) + .arg(Arg::with_name("extensions") + .help("Comma-separated list of file extensions to watch (js,css,html)") + .short("e") + .long("exts") + .takes_value(true)) + .arg(Arg::with_name("path") + .help("Watch a specific directory") + .short("w") + .long("watch") + .number_of_values(1) + .multiple(true) + .takes_value(true)) + .arg(Arg::with_name("clear") + .help("Clear screen before executing command") + .short("c") + .long("clear")) + .arg(Arg::with_name("restart") + .help("Restart the process if it's still running") + .short("r") + .long("restart")) + .arg(Arg::with_name("signal") + .help("Send signal to process upon changes, e.g. SIGHUP") + .short("s") + .long("signal") + .takes_value(true) + .number_of_values(1) + .value_name("signal")) + .arg(Arg::with_name("kill") + .help("Send SIGKILL to child processes (deprecated, use -s SIGKILL instead)") + .short("k") + .long("kill")) + .arg(Arg::with_name("debug") + .help("Print debugging messages to stderr") + .short("d") + .long("debug")) + .arg(Arg::with_name("filter") + .help("Ignore all modifications except those matching the pattern") + .short("f") + .long("filter") + .number_of_values(1) + .multiple(true) + .takes_value(true) + .value_name("pattern")) + .arg(Arg::with_name("ignore") + .help("Ignore modifications to paths matching the pattern") + .short("i") + .long("ignore") + .number_of_values(1) + .multiple(true) + .takes_value(true) + .value_name("pattern")) + .arg(Arg::with_name("no-vcs-ignore") + .help("Skip auto-loading of .gitignore files for filtering") + .long("no-vcs-ignore")) + .arg(Arg::with_name("postpone") + .help("Wait until first change to execute command") + .short("p") + .long("postpone")) + .arg(Arg::with_name("poll") + .help("Forces polling mode") + .long("force-poll") + .value_name("interval")) + .arg(Arg::with_name("once").short("1").hidden(true)) + .get_matches(); - let cmd = values_t!(args.values_of("command"), String).unwrap().join(" "); + let cmd = values_t!(args.values_of("command"), String) + .unwrap() + .join(" "); let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]); - let signal = args.value_of("signal").map(str::to_string); // Convert Option<&str> to Option + + // Treat --kill as --signal SIGKILL (for compatibility with older syntax) + let signal = match args.is_present("kill") { + true => Some("SIGKILL".to_string()), + false => args.value_of("signal").map(str::to_string), // Convert Option<&str> to Option + }; let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(vec![]); if let Some(extensions) = args.values_of("extensions") { for exts in extensions { filters.extend(exts.split(',') - .filter(|ext| !ext.is_empty()) - .map(|ext| format!("*.{}", ext.replace(".", "")))); + .filter(|ext| !ext.is_empty()) + .map(|ext| format!("*.{}", ext.replace(".", "")))); } } @@ -138,6 +148,12 @@ pub fn get_args() -> Args { .exit(); } + if signal.is_some() && args.is_present("kill") { + // TODO: Error::argument_conflict() might be the better fit, usage was unclear, though + Error::value_validation_auto(format!("--kill and --signal is ambiguous.\n Hint: Use only '--signal SIGKILL' without --kill")) + .exit(); + } + Args { cmd: cmd, paths: paths, From 264ec282d68221f9abe76f9e4bf506734554b40f Mon Sep 17 00:00:00 2001 From: Chris Aumann Date: Sun, 2 Apr 2017 21:28:00 +0200 Subject: [PATCH 17/17] Use a tuple to match scenarios --- src/main.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index aaff2881..ed1ec3fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,10 +124,12 @@ fn main() { // 3. Send SIGTERM to the child, wait for it to exit, then run the command again // 4. Send a specified signal to the child, wait for it to exit, then run the command again // - match args.restart { + let scenario = (args.restart, signal.is_some()); + + match scenario { // Custom restart behaviour (--restart was given, and --signal specified): // Send specified signal to the child, wait for it to exit, then run the command again - true if signal.is_some() => { + (true, true) => { signal_process(&child_process, signal, true); // Launch child process @@ -144,7 +146,7 @@ fn main() { // Default restart behaviour (--restart was given, but --signal wasn't specified): // Send SIGTERM to the child, wait for it to exit, then run the command again - true if signal.is_none() => { + (true, false) => { let sigterm = signal::new(Some("SIGTERM".to_owned())); signal_process(&child_process, sigterm, true); @@ -162,11 +164,11 @@ fn main() { // SIGHUP scenario: --signal was given, but --restart was not // Just send a signal (e.g. SIGHUP) to the child, do nothing more - false if signal.is_some() => signal_process(&child_process, signal, false), + (false, true) => signal_process(&child_process, signal, false), // Default behaviour (neither --signal nor --restart specified): // Make sure the previous run was ended, then run the command again - false if signal.is_none() => { + (false, false) => { signal_process(&child_process, None, true); // Launch child process @@ -180,9 +182,6 @@ fn main() { *guard = Some(process::spawn(&args.cmd, paths)); } } - - // Catch everything else, just to be sure. - _ => panic!("This should never be called. Please file a bug report!"), } // Handle once option for integration testing