2016-11-05 04:03:31 +01:00
|
|
|
use std::path::PathBuf;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2017-04-06 22:31:52 +02:00
|
|
|
pub fn spawn(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Process {
|
|
|
|
self::imp::Process::new(cmd, updated_paths, no_shell).expect("unable to spawn process")
|
2016-11-23 18:59:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub use self::imp::Process;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
|
|
|
#[cfg(target_family = "unix")]
|
2016-10-26 22:14:57 +02:00
|
|
|
mod imp {
|
2016-12-19 17:37:12 +01:00
|
|
|
use libc::*;
|
2016-10-26 22:14:57 +02:00
|
|
|
use std::io::Result;
|
2016-10-29 01:27:08 +02:00
|
|
|
use std::path::PathBuf;
|
2016-10-26 22:14:57 +02:00
|
|
|
use std::process::Command;
|
2016-12-20 17:44:18 +01:00
|
|
|
use std::sync::*;
|
2017-03-14 00:52:50 +01:00
|
|
|
use signal::Signal;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2016-10-26 22:14:57 +02:00
|
|
|
pub struct Process {
|
2016-11-16 14:55:15 +01:00
|
|
|
pgid: pid_t,
|
2016-12-20 17:44:18 +01:00
|
|
|
lock: Mutex<bool>,
|
|
|
|
cvar: Condvar,
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2016-12-20 18:20:21 +01:00
|
|
|
#[allow(unknown_lints)]
|
|
|
|
#[allow(mutex_atomic)]
|
2016-10-26 22:14:57 +02:00
|
|
|
impl Process {
|
2017-04-06 22:31:52 +02:00
|
|
|
pub fn new(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Result<Process> {
|
2016-11-15 22:55:29 +01:00
|
|
|
use nix::unistd::*;
|
2016-11-16 14:55:15 +01:00
|
|
|
use std::io;
|
|
|
|
use std::os::unix::process::CommandExt;
|
2016-10-26 22:14:57 +02:00
|
|
|
|
2017-04-06 22:31:52 +02:00
|
|
|
// Assemble command to run.
|
|
|
|
// This is either the first argument from cmd (if no_shell was given) or "sh".
|
|
|
|
// Using "sh -c" gives us features like supportin pipes and redirects,
|
|
|
|
// but is a little less performant and can cause trouble when using custom signals
|
|
|
|
// (e.g. --signal SIGHUP)
|
|
|
|
let mut iter_args = cmd.split_whitespace();
|
|
|
|
let arg0 = match no_shell {
|
|
|
|
true => iter_args.next().unwrap(),
|
|
|
|
false => "sh",
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: There might be a better way of doing this with &str.
|
|
|
|
// I've had to fall back to String, as I wasn't able to join(" ") a Vec<&str>
|
|
|
|
// into a &str
|
|
|
|
let args: Vec<String> = match no_shell {
|
|
|
|
true => iter_args.map(str::to_string).collect(),
|
|
|
|
false => vec!["-c".to_string(), iter_args.collect::<Vec<&str>>().join(" ")],
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut command = Command::new(arg0);
|
|
|
|
command.args(args);
|
|
|
|
debug!("Assembled command {:?}", command);
|
2016-10-26 22:14:57 +02:00
|
|
|
|
2016-10-29 01:27:08 +02:00
|
|
|
if let Some(single_path) = super::get_single_updated_path(&updated_paths) {
|
|
|
|
command.env("WATCHEXEC_UPDATED_PATH", single_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(common_path) = super::get_longest_common_path(&updated_paths) {
|
|
|
|
command.env("WATCHEXEC_COMMON_PATH", common_path);
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
|
|
|
|
2017-03-23 23:39:38 +01:00
|
|
|
command
|
|
|
|
.before_exec(|| setpgid(0, 0).map_err(io::Error::from))
|
2017-03-23 23:12:00 +01:00
|
|
|
.spawn()
|
|
|
|
.and_then(|p| {
|
2017-03-23 23:39:38 +01:00
|
|
|
Ok(Process {
|
|
|
|
pgid: p.id() as i32,
|
|
|
|
lock: Mutex::new(false),
|
|
|
|
cvar: Condvar::new(),
|
|
|
|
})
|
|
|
|
})
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-12-20 17:44:18 +01:00
|
|
|
pub fn reap(&self) {
|
|
|
|
use nix::sys::wait::*;
|
|
|
|
|
|
|
|
let mut finished = true;
|
|
|
|
loop {
|
|
|
|
match waitpid(-self.pgid, Some(WNOHANG)) {
|
2016-12-20 18:20:21 +01:00
|
|
|
Ok(WaitStatus::Exited(_, _)) |
|
2016-12-20 17:44:18 +01:00
|
|
|
Ok(WaitStatus::Signaled(_, _, _)) => finished = finished && true,
|
2016-12-20 19:21:24 +01:00
|
|
|
Ok(_) => {
|
|
|
|
finished = false;
|
|
|
|
break;
|
|
|
|
}
|
2016-12-20 18:20:21 +01:00
|
|
|
Err(_) => break,
|
2016-12-20 17:44:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if finished {
|
|
|
|
let mut done = self.lock.lock().unwrap();
|
|
|
|
*done = true;
|
|
|
|
self.cvar.notify_one();
|
|
|
|
}
|
|
|
|
}
|
2016-12-15 02:19:58 +01:00
|
|
|
|
2017-03-13 18:55:12 +01:00
|
|
|
pub fn signal(&self, signal: Signal) {
|
2017-03-14 17:18:43 +01:00
|
|
|
use signal::ConvertToLibc;
|
2017-03-14 00:52:50 +01:00
|
|
|
|
2017-03-14 17:18:43 +01:00
|
|
|
let signo = signal.convert_to_libc();
|
2017-03-14 00:52:50 +01:00
|
|
|
debug!("Sending {:?} (int: {}) to child process", signal, signo);
|
|
|
|
self.c_signal(signo);
|
2017-03-13 18:55:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn c_signal(&self, sig: c_int) {
|
2016-10-26 22:14:57 +02:00
|
|
|
extern "C" {
|
2016-11-16 14:55:15 +01:00
|
|
|
fn killpg(pgrp: pid_t, sig: c_int) -> c_int;
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2016-10-26 22:14:57 +02:00
|
|
|
unsafe {
|
2016-12-15 02:19:58 +01:00
|
|
|
killpg(self.pgid, sig);
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
2016-12-15 02:19:58 +01:00
|
|
|
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-11-09 15:44:00 +01:00
|
|
|
pub fn wait(&self) {
|
2016-12-20 17:44:18 +01:00
|
|
|
let mut done = self.lock.lock().unwrap();
|
|
|
|
while !*done {
|
|
|
|
done = self.cvar.wait(done).unwrap();
|
|
|
|
}
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_family = "windows")]
|
2016-10-26 22:14:57 +02:00
|
|
|
mod imp {
|
|
|
|
use std::io;
|
|
|
|
use std::io::Result;
|
|
|
|
use std::mem;
|
2016-10-29 01:27:08 +02:00
|
|
|
use std::path::PathBuf;
|
2016-10-26 22:14:57 +02:00
|
|
|
use std::process::Command;
|
|
|
|
use kernel32::*;
|
|
|
|
use winapi::*;
|
2017-03-14 17:30:19 +01:00
|
|
|
use signal::Signal;
|
2016-10-26 22:14:57 +02:00
|
|
|
|
|
|
|
pub struct Process {
|
|
|
|
job: HANDLE,
|
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2016-10-26 22:14:57 +02:00
|
|
|
impl Process {
|
2017-04-06 23:04:57 +02:00
|
|
|
pub fn new(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Result<Process> {
|
2016-10-26 22:14:57 +02:00
|
|
|
use std::os::windows::io::IntoRawHandle;
|
|
|
|
|
|
|
|
fn last_err() -> io::Error {
|
|
|
|
io::Error::last_os_error()
|
|
|
|
}
|
|
|
|
|
|
|
|
let job = unsafe { CreateJobObjectW(0 as *mut _, 0 as *const _) };
|
|
|
|
if job.is_null() {
|
|
|
|
panic!("failed to create job object: {}", last_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { mem::zeroed() };
|
|
|
|
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
|
|
let r = unsafe {
|
|
|
|
SetInformationJobObject(job,
|
|
|
|
JobObjectExtendedLimitInformation,
|
|
|
|
&mut info as *mut _ as LPVOID,
|
|
|
|
mem::size_of_val(&info) as DWORD)
|
|
|
|
};
|
|
|
|
if r == 0 {
|
|
|
|
panic!("failed to set job info: {}", last_err());
|
|
|
|
}
|
|
|
|
|
2017-04-06 23:04:57 +02:00
|
|
|
let mut iter_args = cmd.split_whitespace();
|
|
|
|
let arg0 = match no_shell {
|
|
|
|
true => iter_args.next().unwrap(),
|
|
|
|
false => "cmd.exe",
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: There might be a better way of doing this with &str.
|
|
|
|
// I've had to fall back to String, as I wasn't able to join(" ") a Vec<&str>
|
|
|
|
// into a &str
|
|
|
|
let args: Vec<String> = match no_shell {
|
|
|
|
true => iter_args.map(str::to_string).collect(),
|
|
|
|
false => vec!["/C".to_string(), iter_args.collect::<Vec<&str>>().join(" ")],
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut command = Command::new(arg0);
|
|
|
|
command.args(args);
|
|
|
|
debug!("Assembled command {:?}", command);
|
2016-10-26 22:14:57 +02:00
|
|
|
|
2016-10-29 01:27:08 +02:00
|
|
|
if let Some(single_path) = super::get_single_updated_path(&updated_paths) {
|
|
|
|
command.env("WATCHEXEC_UPDATED_PATH", single_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(common_path) = super::get_longest_common_path(&updated_paths) {
|
|
|
|
command.env("WATCHEXEC_COMMON_PATH", common_path);
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
|
|
|
|
2017-03-23 23:39:38 +01:00
|
|
|
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());
|
|
|
|
}
|
2016-10-26 22:14:57 +02:00
|
|
|
|
2017-03-23 23:39:38 +01:00
|
|
|
Ok(Process { job: job })
|
|
|
|
})
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-12-20 18:20:21 +01:00
|
|
|
pub fn reap(&self) {}
|
2016-12-20 17:44:18 +01:00
|
|
|
|
2017-03-14 17:30:19 +01:00
|
|
|
pub fn signal(&self, signal: Signal) {
|
|
|
|
debug!("Ignoring signal {:?} (not supported by Windows)", signal);
|
2016-12-19 17:37:12 +01:00
|
|
|
unsafe {
|
|
|
|
let _ = TerminateJobObject(self.job, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-09 15:44:00 +01:00
|
|
|
pub fn wait(&self) {
|
2016-10-26 22:14:57 +02:00
|
|
|
unsafe {
|
|
|
|
let _ = WaitForSingleObject(self.job, INFINITE);
|
|
|
|
}
|
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-10-26 22:14:57 +02:00
|
|
|
impl Drop for Process {
|
2016-11-09 23:20:13 +01:00
|
|
|
fn drop(&mut self) {
|
2016-10-26 22:14:57 +02:00
|
|
|
unsafe {
|
|
|
|
let _ = CloseHandle(self.job);
|
|
|
|
}
|
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-10-26 22:14:57 +02:00
|
|
|
unsafe impl Send for Process {}
|
2016-11-09 15:44:00 +01:00
|
|
|
unsafe impl Sync for Process {}
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-10-30 17:37:34 +01:00
|
|
|
fn get_single_updated_path(paths: &[PathBuf]) -> Option<&str> {
|
2016-10-29 01:27:08 +02:00
|
|
|
paths.get(0).and_then(|p| p.to_str())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
|
|
|
|
match paths.len() {
|
|
|
|
0 => return None,
|
|
|
|
1 => return paths[0].to_str().map(|ref_val| ref_val.to_string()),
|
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
|
2016-11-05 04:03:31 +01:00
|
|
|
let mut longest_path: Vec<_> = paths[0].components().collect();
|
2016-10-29 01:27:08 +02:00
|
|
|
|
2016-11-05 04:03:31 +01:00
|
|
|
for path in &paths[1..] {
|
|
|
|
let mut greatest_distance = 0;
|
|
|
|
for component_pair in path.components().zip(longest_path.iter()) {
|
|
|
|
if component_pair.0 != *component_pair.1 {
|
|
|
|
break;
|
2016-10-29 01:27:08 +02:00
|
|
|
}
|
|
|
|
|
2016-11-05 04:03:31 +01:00
|
|
|
greatest_distance += 1;
|
|
|
|
}
|
2016-10-29 01:27:08 +02:00
|
|
|
|
2016-11-05 04:03:31 +01:00
|
|
|
if greatest_distance != longest_path.len() {
|
|
|
|
longest_path.truncate(greatest_distance);
|
2016-10-29 01:27:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut result = PathBuf::new();
|
2016-11-05 04:03:31 +01:00
|
|
|
for component in longest_path {
|
|
|
|
result.push(component.as_os_str());
|
2016-10-29 01:27:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result.to_str().map(|ref_val| ref_val.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-10-26 17:01:55 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
#[cfg(target_family = "unix")]
|
|
|
|
mod tests {
|
2016-12-20 19:42:39 +01:00
|
|
|
use std::path::PathBuf;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2016-11-23 18:59:56 +01:00
|
|
|
use super::spawn;
|
2016-10-29 01:27:08 +02:00
|
|
|
use super::get_longest_common_path;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_start() {
|
2017-04-06 23:05:53 +02:00
|
|
|
let _ = spawn("echo hi", vec![], true);
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-10-29 01:27:08 +02:00
|
|
|
#[test]
|
|
|
|
fn longest_common_path_should_return_correct_value() {
|
|
|
|
let single_path = vec![PathBuf::from("/tmp/random/")];
|
|
|
|
let single_result = get_longest_common_path(&single_path).unwrap();
|
|
|
|
assert_eq!(single_result, "/tmp/random/");
|
|
|
|
|
2016-10-30 17:28:54 +01:00
|
|
|
let common_paths = vec![PathBuf::from("/tmp/logs/hi"),
|
|
|
|
PathBuf::from("/tmp/logs/bye"),
|
|
|
|
PathBuf::from("/tmp/logs/bye"),
|
|
|
|
PathBuf::from("/tmp/logs/fly")];
|
2016-10-29 01:27:08 +02:00
|
|
|
|
|
|
|
let common_result = get_longest_common_path(&common_paths).unwrap();
|
|
|
|
assert_eq!(common_result, "/tmp/logs");
|
|
|
|
|
|
|
|
|
2016-10-30 17:28:54 +01:00
|
|
|
let diverging_paths = vec![PathBuf::from("/tmp/logs/hi"), PathBuf::from("/var/logs/hi")];
|
2016-10-29 01:27:08 +02:00
|
|
|
|
|
|
|
let diverging_result = get_longest_common_path(&diverging_paths).unwrap();
|
|
|
|
assert_eq!(diverging_result, "/");
|
|
|
|
|
2016-10-30 17:28:54 +01:00
|
|
|
let uneven_paths = vec![PathBuf::from("/tmp/logs/hi"),
|
|
|
|
PathBuf::from("/tmp/logs/"),
|
|
|
|
PathBuf::from("/tmp/logs/bye")];
|
2016-10-29 01:27:08 +02:00
|
|
|
|
|
|
|
let uneven_result = get_longest_common_path(&uneven_paths).unwrap();
|
|
|
|
assert_eq!(uneven_result, "/tmp/logs");
|
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|