2019-10-27 12:06:09 +01:00
|
|
|
#![allow(unsafe_code)]
|
|
|
|
|
2019-10-27 11:58:00 +01:00
|
|
|
use crate::error::Result;
|
|
|
|
use crate::pathop::PathOp;
|
2018-09-08 10:08:36 +02:00
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
use std::path::PathBuf;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2019-10-27 11:47:35 +01:00
|
|
|
pub fn spawn(cmd: &[String], updated_paths: &[PathOp], no_shell: bool) -> Result<Process> {
|
2019-01-26 05:26:33 +01:00
|
|
|
self::imp::Process::new(cmd, updated_paths, no_shell).map_err(|e| e.into())
|
2016-11-23 18:59:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub use self::imp::Process;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2018-09-09 06:00:45 +02:00
|
|
|
/*
|
2018-08-21 22:17:36 +02:00
|
|
|
fn needs_wrapping(s: &String) -> bool {
|
2018-08-21 14:13:45 +02:00
|
|
|
s.contains(|ch| match ch {
|
2018-08-21 22:17:36 +02:00
|
|
|
' ' | '\t' | '\'' | '"' => true,
|
2018-09-08 10:08:36 +02:00
|
|
|
_ => false,
|
2018-08-21 14:13:45 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_family = "unix")]
|
|
|
|
fn wrap_in_quotes(s: &String) -> String {
|
2018-09-08 10:08:36 +02:00
|
|
|
format!(
|
|
|
|
"'{}'",
|
|
|
|
if s.contains('\'') {
|
|
|
|
s.replace('\'', "'\"'\"'")
|
|
|
|
} else {
|
|
|
|
s.clone()
|
|
|
|
}
|
|
|
|
)
|
2018-08-21 14:13:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_family = "windows")]
|
|
|
|
fn wrap_in_quotes(s: &String) -> String {
|
2018-09-08 10:08:36 +02:00
|
|
|
format!(
|
|
|
|
"\"{}\"",
|
|
|
|
if s.contains('"') {
|
|
|
|
s.replace('"', "\"\"")
|
|
|
|
} else {
|
|
|
|
s.clone()
|
|
|
|
}
|
|
|
|
)
|
2018-08-21 14:13:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn wrap_commands(cmd: &Vec<String>) -> Vec<String> {
|
2018-09-08 10:08:36 +02:00
|
|
|
cmd.iter()
|
|
|
|
.map(|fragment| {
|
|
|
|
if needs_wrapping(fragment) {
|
|
|
|
wrap_in_quotes(fragment)
|
|
|
|
} else {
|
|
|
|
fragment.clone()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
2018-08-21 14:13:45 +02:00
|
|
|
}
|
2018-09-09 06:00:45 +02:00
|
|
|
*/
|
2018-08-21 14:13:45 +02:00
|
|
|
|
2016-10-26 17:01:55 +02:00
|
|
|
#[cfg(target_family = "unix")]
|
2016-10-26 22:14:57 +02:00
|
|
|
mod imp {
|
2018-09-09 06:00:45 +02:00
|
|
|
//use super::wrap_commands;
|
2019-10-27 11:58:00 +01:00
|
|
|
use crate::pathop::PathOp;
|
|
|
|
use crate::signal::Signal;
|
2019-10-27 12:31:52 +01:00
|
|
|
use nix::libc::*;
|
|
|
|
use nix::{self, Error};
|
2017-08-26 21:07:06 +02:00
|
|
|
use std::io::{self, Result};
|
2016-10-26 22:14:57 +02:00
|
|
|
use std::process::Command;
|
2016-12-20 17:44:18 +01:00
|
|
|
use std::sync::*;
|
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
|
|
|
|
2017-08-26 21:07:06 +02:00
|
|
|
fn from_nix_error(err: nix::Error) -> io::Error {
|
|
|
|
match err {
|
|
|
|
Error::Sys(errno) => io::Error::from_raw_os_error(errno as i32),
|
|
|
|
Error::InvalidPath => io::Error::new(io::ErrorKind::InvalidInput, err),
|
|
|
|
_ => io::Error::new(io::ErrorKind::Other, err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-27 11:47:35 +01:00
|
|
|
#[allow(clippy::mutex_atomic)]
|
2016-10-26 22:14:57 +02:00
|
|
|
impl Process {
|
2019-10-27 12:06:09 +01:00
|
|
|
pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool) -> Result<Self> {
|
2016-11-15 22:55:29 +01:00
|
|
|
use nix::unistd::*;
|
2019-10-27 12:31:52 +01:00
|
|
|
use std::convert::TryInto;
|
2016-11-16 14:55:15 +01:00
|
|
|
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".
|
2019-10-13 06:33:53 +02:00
|
|
|
// Using "sh -c" gives us features like supporting pipes and redirects,
|
2017-04-06 22:31:52 +02:00
|
|
|
// but is a little less performant and can cause trouble when using custom signals
|
|
|
|
// (e.g. --signal SIGHUP)
|
2017-04-10 14:13:27 +02:00
|
|
|
let mut command = if no_shell {
|
2019-10-27 13:10:54 +01:00
|
|
|
let (head, tail) = cmd.split_first().expect("cmd was empty");
|
2018-08-21 14:13:45 +02:00
|
|
|
let mut command = Command::new(head);
|
|
|
|
command.args(tail);
|
2017-04-10 14:13:27 +02:00
|
|
|
command
|
2017-04-10 00:17:03 +02:00
|
|
|
} else {
|
2017-04-10 14:13:27 +02:00
|
|
|
let mut command = Command::new("sh");
|
2018-09-09 06:00:45 +02:00
|
|
|
//command.arg("-c").arg(wrap_commands(cmd).join(" "));
|
|
|
|
command.arg("-c").arg(cmd.join(" "));
|
2017-04-10 14:13:27 +02:00
|
|
|
command
|
2017-04-06 22:31:52 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
debug!("Assembled command {:?}", command);
|
2016-10-26 22:14:57 +02:00
|
|
|
|
2019-01-26 05:20:29 +01:00
|
|
|
let command_envs = super::collect_path_env_vars(updated_paths);
|
2017-09-07 05:53:41 +02:00
|
|
|
for &(ref name, ref val) in &command_envs {
|
|
|
|
command.env(name, val);
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
|
|
|
|
2019-10-27 12:31:52 +01:00
|
|
|
unsafe {
|
|
|
|
command.pre_exec(|| setsid().map_err(from_nix_error).map(|_| ()));
|
|
|
|
}
|
|
|
|
command.spawn().and_then(|p| {
|
|
|
|
Ok(Self {
|
2019-10-27 13:10:54 +01:00
|
|
|
pgid: p.id().try_into().expect("u32 -> i32 failed in process::new"),
|
2019-10-27 12:31:52 +01:00
|
|
|
lock: Mutex::new(false),
|
|
|
|
cvar: Condvar::new(),
|
2018-09-08 10:08:36 +02:00
|
|
|
})
|
2019-10-27 12:31:52 +01:00
|
|
|
})
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-12-20 17:44:18 +01:00
|
|
|
pub fn reap(&self) {
|
|
|
|
use nix::sys::wait::*;
|
2017-08-26 21:07:06 +02:00
|
|
|
use nix::unistd::Pid;
|
2016-12-20 17:44:18 +01:00
|
|
|
|
|
|
|
let mut finished = true;
|
|
|
|
loop {
|
2018-07-30 21:20:12 +02:00
|
|
|
match waitpid(Pid::from_raw(-self.pgid), Some(WaitPidFlag::WNOHANG)) {
|
2019-10-27 12:31:52 +01:00
|
|
|
Ok(WaitStatus::Exited(_, _)) | Ok(WaitStatus::Signaled(_, _, _)) => {}
|
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 {
|
2019-10-27 13:10:54 +01:00
|
|
|
let mut done = self.lock.lock().expect("poisoned lock in process::reap");
|
2016-12-20 17:44:18 +01:00
|
|
|
*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) {
|
2019-10-27 11:58:00 +01:00
|
|
|
use crate::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-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2016-11-09 15:44:00 +01:00
|
|
|
pub fn wait(&self) {
|
2019-10-27 13:10:54 +01:00
|
|
|
let mut done = self.lock.lock().expect("poisoned lock in process::wait");
|
2016-12-20 17:44:18 +01:00
|
|
|
while !*done {
|
2019-10-27 13:10:54 +01:00
|
|
|
done = self.cvar.wait(done).expect("poisoned cvar in process::wait");
|
2016-12-20 17:44:18 +01:00
|
|
|
}
|
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 {
|
2018-09-09 06:00:45 +02:00
|
|
|
//use super::wrap_commands;
|
2019-10-27 11:58:00 +01:00
|
|
|
use crate::pathop::PathOp;
|
|
|
|
use crate::signal::Signal;
|
2019-10-27 12:31:52 +01:00
|
|
|
use kernel32::*;
|
2016-10-26 22:14:57 +02:00
|
|
|
use std::io;
|
|
|
|
use std::io::Result;
|
|
|
|
use std::mem;
|
|
|
|
use std::process::Command;
|
2017-05-18 19:22:46 +02:00
|
|
|
use std::ptr;
|
2016-10-26 22:14:57 +02:00
|
|
|
use winapi::*;
|
|
|
|
|
|
|
|
pub struct Process {
|
|
|
|
job: HANDLE,
|
2017-05-18 19:22:46 +02:00
|
|
|
completion_port: HANDLE,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
struct JOBOBJECT_ASSOCIATE_COMPLETION_PORT {
|
|
|
|
completion_key: PVOID,
|
|
|
|
completion_port: HANDLE,
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2016-10-26 22:14:57 +02:00
|
|
|
impl Process {
|
2019-01-26 05:32:48 +01:00
|
|
|
pub fn new(cmd: &Vec<String>, updated_paths: &[PathOp], no_shell: bool) -> Result<Process> {
|
2016-10-26 22:14:57 +02:00
|
|
|
use std::os::windows::io::IntoRawHandle;
|
2017-05-18 19:22:46 +02:00
|
|
|
use std::os::windows::process::CommandExt;
|
2016-10-26 22:14:57 +02:00
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2018-09-08 10:08:36 +02:00
|
|
|
let completion_port =
|
|
|
|
unsafe { CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), 0, 1) };
|
2017-05-18 19:22:46 +02:00
|
|
|
if job.is_null() {
|
2018-09-08 10:08:36 +02:00
|
|
|
panic!(
|
|
|
|
"unable to create IO completion port for job: {}",
|
|
|
|
last_err()
|
|
|
|
);
|
2017-05-18 19:22:46 +02:00
|
|
|
}
|
|
|
|
|
2018-09-08 10:08:36 +02:00
|
|
|
let mut associate_completion: JOBOBJECT_ASSOCIATE_COMPLETION_PORT =
|
|
|
|
unsafe { mem::zeroed() };
|
2017-05-18 19:22:46 +02:00
|
|
|
associate_completion.completion_key = job;
|
|
|
|
associate_completion.completion_port = completion_port;
|
|
|
|
unsafe {
|
2018-09-08 10:08:36 +02:00
|
|
|
let r = SetInformationJobObject(
|
|
|
|
job,
|
|
|
|
JobObjectAssociateCompletionPortInformation,
|
|
|
|
&mut associate_completion as *mut _ as LPVOID,
|
|
|
|
mem::size_of_val(&associate_completion) as DWORD,
|
|
|
|
);
|
2017-05-18 19:22:46 +02:00
|
|
|
if r == 0 {
|
2018-09-08 10:08:36 +02:00
|
|
|
panic!(
|
|
|
|
"failed to associate completion port with job: {}",
|
|
|
|
last_err()
|
|
|
|
);
|
2017-05-18 19:22:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-26 22:14:57 +02:00
|
|
|
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { mem::zeroed() };
|
|
|
|
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
|
|
let r = unsafe {
|
2018-09-08 10:08:36 +02:00
|
|
|
SetInformationJobObject(
|
|
|
|
job,
|
|
|
|
JobObjectExtendedLimitInformation,
|
|
|
|
&mut info as *mut _ as LPVOID,
|
|
|
|
mem::size_of_val(&info) as DWORD,
|
|
|
|
)
|
2016-10-26 22:14:57 +02:00
|
|
|
};
|
|
|
|
if r == 0 {
|
|
|
|
panic!("failed to set job info: {}", last_err());
|
|
|
|
}
|
|
|
|
|
2018-08-21 14:13:45 +02:00
|
|
|
let mut command;
|
|
|
|
if no_shell {
|
2019-10-27 13:10:54 +01:00
|
|
|
let (first, rest) = cmd.split_first().expect("command is empty");
|
2019-10-27 12:06:09 +01:00
|
|
|
command = Command::new(first);
|
|
|
|
command.args(rest);
|
2018-08-21 14:13:45 +02:00
|
|
|
} else {
|
|
|
|
command = Command::new("cmd.exe");
|
|
|
|
command.arg("/C");
|
2018-09-09 06:00:45 +02:00
|
|
|
//command.arg(wrap_commands(cmd).join(" "));
|
|
|
|
command.arg(cmd.join(" "));
|
2018-08-21 14:13:45 +02:00
|
|
|
}
|
2017-05-18 19:22:46 +02:00
|
|
|
|
|
|
|
command.creation_flags(CREATE_SUSPENDED);
|
2017-04-06 23:04:57 +02:00
|
|
|
debug!("Assembled command {:?}", command);
|
2016-10-26 22:14:57 +02:00
|
|
|
|
2019-01-26 05:20:29 +01:00
|
|
|
let command_envs = super::collect_path_env_vars(updated_paths);
|
2017-09-07 05:53:41 +02:00
|
|
|
for &(ref name, ref val) in &command_envs {
|
|
|
|
command.env(name, val);
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
|
|
|
|
2018-09-08 10:08:36 +02:00
|
|
|
command.spawn().and_then(|p| {
|
|
|
|
let handle = p.into_raw_handle();
|
|
|
|
let r = unsafe { AssignProcessToJobObject(job, handle) };
|
|
|
|
if r == 0 {
|
|
|
|
panic!("failed to add to job object: {}", last_err());
|
|
|
|
}
|
2016-10-26 22:14:57 +02:00
|
|
|
|
2018-09-08 10:08:36 +02:00
|
|
|
resume_threads(handle);
|
2017-05-18 19:22:46 +02:00
|
|
|
|
2018-09-08 10:08:36 +02:00
|
|
|
Ok(Process {
|
2019-10-27 11:47:35 +01:00
|
|
|
job,
|
|
|
|
completion_port,
|
2018-09-08 10:08:36 +02:00
|
|
|
})
|
|
|
|
})
|
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-05-18 19:22:46 +02:00
|
|
|
pub fn signal(&self, _signal: 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 {
|
2017-05-18 19:22:46 +02:00
|
|
|
loop {
|
|
|
|
let mut code: DWORD = 0;
|
|
|
|
let mut key: ULONG_PTR = 0;
|
|
|
|
let mut overlapped: LPOVERLAPPED = mem::uninitialized();
|
2018-09-08 10:08:36 +02:00
|
|
|
GetQueuedCompletionStatus(
|
|
|
|
self.completion_port,
|
|
|
|
&mut code,
|
|
|
|
&mut key,
|
|
|
|
&mut overlapped,
|
|
|
|
INFINITE,
|
|
|
|
);
|
2017-05-18 19:22:46 +02:00
|
|
|
|
|
|
|
if code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO && (key as HANDLE) == self.job {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-10-26 22:14:57 +02:00
|
|
|
}
|
|
|
|
}
|
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);
|
2017-05-18 19:22:46 +02:00
|
|
|
let _ = CloseHandle(self.completion_port);
|
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 impl Send for Process {}
|
2016-11-09 15:44:00 +01:00
|
|
|
unsafe impl Sync for Process {}
|
2017-05-18 19:22:46 +02:00
|
|
|
|
|
|
|
// This is pretty terrible, but it's either this or we re-implement all of Rust's std::process just to get at PROCESS_INFORMATION
|
|
|
|
fn resume_threads(child_process: HANDLE) {
|
|
|
|
unsafe {
|
|
|
|
let child_id = GetProcessId(child_process);
|
|
|
|
|
|
|
|
let h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
2018-09-08 10:08:36 +02:00
|
|
|
let mut entry = THREADENTRY32 {
|
|
|
|
dwSize: 28,
|
|
|
|
cntUsage: 0,
|
|
|
|
th32ThreadID: 0,
|
|
|
|
th32OwnerProcessID: 0,
|
|
|
|
tpBasePri: 0,
|
|
|
|
tpDeltaPri: 0,
|
|
|
|
dwFlags: 0,
|
|
|
|
};
|
2017-05-18 19:22:46 +02:00
|
|
|
|
|
|
|
let mut result = Thread32First(h, &mut entry);
|
|
|
|
while result != 0 {
|
|
|
|
if entry.th32OwnerProcessID == child_id {
|
|
|
|
let thread_handle = OpenThread(0x0002, 0, entry.th32ThreadID);
|
|
|
|
ResumeThread(thread_handle);
|
|
|
|
CloseHandle(thread_handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
result = Thread32Next(h, &mut entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandle(h);
|
|
|
|
}
|
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2017-09-07 05:53:41 +02:00
|
|
|
/// Collect `PathOp` details into op-categories to pass onto the exec'd command as env-vars
|
|
|
|
///
|
2019-10-27 12:06:09 +01:00
|
|
|
/// `WRITTEN` -> `notify::ops::WRITE`, `notify::ops::CLOSE_WRITE`
|
|
|
|
/// `META_CHANGED` -> `notify::ops::CHMOD`
|
|
|
|
/// `REMOVED` -> `notify::ops::REMOVE`
|
|
|
|
/// `CREATED` -> `notify::ops::CREATE`
|
|
|
|
/// `RENAMED` -> `notify::ops::RENAME`
|
2017-09-07 05:53:41 +02:00
|
|
|
fn collect_path_env_vars(pathops: &[PathOp]) -> Vec<(String, String)> {
|
|
|
|
#[cfg(target_family = "unix")]
|
2019-10-27 11:47:35 +01:00
|
|
|
const ENV_SEP: &str = ":";
|
2017-09-07 05:53:41 +02:00
|
|
|
#[cfg(not(target_family = "unix"))]
|
2019-10-27 11:47:35 +01:00
|
|
|
const ENV_SEP: &str = ";";
|
2017-09-07 05:53:41 +02:00
|
|
|
|
2018-09-08 10:08:36 +02:00
|
|
|
let mut by_op = HashMap::new(); // Paths as `String`s collected by `notify::op`
|
|
|
|
let mut all_pathbufs = HashSet::new(); // All unique `PathBuf`s
|
2017-09-07 05:53:41 +02:00
|
|
|
for pathop in pathops {
|
2018-09-08 10:08:36 +02:00
|
|
|
if let Some(op) = pathop.op {
|
|
|
|
// ignore pathops that don't have a `notify::op` set
|
|
|
|
if let Some(s) = pathop.path.to_str() {
|
|
|
|
// ignore invalid utf8 paths
|
2017-09-07 05:53:41 +02:00
|
|
|
all_pathbufs.insert(pathop.path.clone());
|
2017-10-06 12:55:21 +02:00
|
|
|
let e = by_op.entry(op).or_insert_with(Vec::new);
|
2017-09-07 05:53:41 +02:00
|
|
|
e.push(s.to_owned());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-26 05:26:33 +01:00
|
|
|
let mut vars = Vec::new();
|
2017-09-07 05:53:41 +02:00
|
|
|
// Only break off a common path if we have more than one unique path,
|
|
|
|
// otherwise we end up with a `COMMON_PATH` being set and other vars
|
|
|
|
// being present but empty.
|
|
|
|
let common_path = if all_pathbufs.len() > 1 {
|
|
|
|
let all_pathbufs: Vec<PathBuf> = all_pathbufs.into_iter().collect();
|
|
|
|
get_longest_common_path(&all_pathbufs)
|
2018-09-08 10:08:36 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2017-09-07 05:53:41 +02:00
|
|
|
if let Some(ref common_path) = common_path {
|
|
|
|
vars.push(("WATCHEXEC_COMMON_PATH".to_string(), common_path.to_string()));
|
|
|
|
}
|
2017-10-06 12:55:21 +02:00
|
|
|
for (op, paths) in by_op {
|
2017-09-07 05:53:41 +02:00
|
|
|
let key = match op {
|
2018-09-08 10:08:36 +02:00
|
|
|
op if PathOp::is_create(op) => "WATCHEXEC_CREATED_PATH",
|
|
|
|
op if PathOp::is_remove(op) => "WATCHEXEC_REMOVED_PATH",
|
|
|
|
op if PathOp::is_rename(op) => "WATCHEXEC_RENAMED_PATH",
|
|
|
|
op if PathOp::is_write(op) => "WATCHEXEC_WRITTEN_PATH",
|
|
|
|
op if PathOp::is_meta(op) => "WATCHEXEC_META_CHANGED_PATH",
|
|
|
|
_ => continue, // ignore `notify::op::RESCAN`s
|
2017-09-07 05:53:41 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let paths = if let Some(ref common_path) = common_path {
|
2018-09-08 10:08:36 +02:00
|
|
|
paths
|
|
|
|
.iter()
|
2019-10-27 11:47:35 +01:00
|
|
|
.map(|path_str| path_str.trim_start_matches(common_path).to_string())
|
2018-09-08 10:08:36 +02:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
} else {
|
|
|
|
paths
|
|
|
|
};
|
2017-09-07 05:53:41 +02:00
|
|
|
vars.push((key.to_string(), paths.as_slice().join(ENV_SEP)));
|
|
|
|
}
|
|
|
|
vars
|
2016-10-29 01:27:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
|
|
|
|
match paths.len() {
|
|
|
|
0 => return None,
|
2019-10-27 12:06:09 +01:00
|
|
|
1 => return paths[0].to_str().map(ToString::to_string),
|
2016-10-29 01:27:08 +02:00
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-10-27 12:06:09 +01:00
|
|
|
result.to_str().map(ToString::to_string)
|
2016-10-29 01:27:08 +02:00
|
|
|
}
|
|
|
|
|
2016-10-26 17:01:55 +02:00
|
|
|
#[cfg(test)]
|
2018-09-09 06:16:40 +02:00
|
|
|
#[cfg(target_family = "unix")]
|
2016-10-26 17:01:55 +02:00
|
|
|
mod tests {
|
2019-10-27 12:37:24 +01:00
|
|
|
use crate::pathop::PathOp;
|
2018-09-08 10:08:36 +02:00
|
|
|
use std::collections::HashSet;
|
|
|
|
use std::path::PathBuf;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
2017-09-07 05:53:41 +02:00
|
|
|
use super::collect_path_env_vars;
|
2018-09-08 10:08:36 +02:00
|
|
|
use super::get_longest_common_path;
|
|
|
|
use super::spawn;
|
2018-09-09 06:16:40 +02:00
|
|
|
//use super::wrap_commands;
|
2016-10-26 17:01:55 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_start() {
|
2019-10-27 12:37:24 +01:00
|
|
|
let _ = spawn(&["echo".into(), "hi".into()], &[], true);
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
|
|
|
|
2018-09-09 06:16:40 +02:00
|
|
|
/*
|
2018-08-21 22:17:36 +02:00
|
|
|
#[test]
|
|
|
|
fn wrap_commands_that_have_whitespace() {
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello world".into()]),
|
|
|
|
vec!["echo".into(), "'hello world'".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn wrap_commands_that_have_long_whitespace() {
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello world".into()]),
|
|
|
|
vec!["echo".into(), "'hello world'".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn wrap_commands_that_have_single_quotes() {
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello ' world".into()]),
|
|
|
|
vec!["echo".into(), "'hello '\"'\"' world'".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello'world".into()]),
|
|
|
|
vec!["echo".into(), "'hello'\"'\"'world'".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn wrap_commands_that_have_double_quotes() {
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello \" world".into()]),
|
|
|
|
vec!["echo".into(), "'hello \" world'".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello\"world".into()]),
|
|
|
|
vec!["echo".into(), "'hello\"world'".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
}
|
2018-09-09 06:16:40 +02:00
|
|
|
*/
|
2018-08-21 22:17:36 +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/")];
|
2019-10-27 13:10:54 +01:00
|
|
|
let single_result = get_longest_common_path(&single_path).expect("failed to get longest common path");
|
2016-10-29 01:27:08 +02:00
|
|
|
assert_eq!(single_result, "/tmp/random/");
|
|
|
|
|
2018-09-08 10:08:36 +02: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
|
|
|
|
2019-10-27 13:10:54 +01:00
|
|
|
let common_result = get_longest_common_path(&common_paths).expect("failed to get longest common path");
|
2016-10-29 01:27:08 +02:00
|
|
|
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
|
|
|
|
2019-10-27 13:10:54 +01:00
|
|
|
let diverging_result = get_longest_common_path(&diverging_paths).expect("failed to get longest common path");
|
2016-10-29 01:27:08 +02:00
|
|
|
assert_eq!(diverging_result, "/");
|
|
|
|
|
2018-09-08 10:08:36 +02: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
|
|
|
|
2019-10-27 13:10:54 +01:00
|
|
|
let uneven_result = get_longest_common_path(&uneven_paths).expect("failed to get longest common path");
|
2016-10-29 01:27:08 +02:00
|
|
|
assert_eq!(uneven_result, "/tmp/logs");
|
|
|
|
}
|
2017-09-07 05:53:41 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn pathops_collect_to_env_vars() {
|
|
|
|
let pathops = vec![
|
2018-09-08 10:08:36 +02:00
|
|
|
PathOp::new(
|
|
|
|
&PathBuf::from("/tmp/logs/hi"),
|
|
|
|
Some(notify::op::CREATE),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
PathOp::new(
|
|
|
|
&PathBuf::from("/tmp/logs/hey/there"),
|
|
|
|
Some(notify::op::CREATE),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
PathOp::new(
|
|
|
|
&PathBuf::from("/tmp/logs/bye"),
|
|
|
|
Some(notify::op::REMOVE),
|
|
|
|
None,
|
|
|
|
),
|
2017-09-07 05:53:41 +02:00
|
|
|
];
|
|
|
|
let expected_vars = vec![
|
|
|
|
("WATCHEXEC_COMMON_PATH".to_string(), "/tmp/logs".to_string()),
|
|
|
|
("WATCHEXEC_REMOVED_PATH".to_string(), "/bye".to_string()),
|
2018-09-08 10:08:36 +02:00
|
|
|
(
|
|
|
|
"WATCHEXEC_CREATED_PATH".to_string(),
|
|
|
|
"/hi:/hey/there".to_string(),
|
|
|
|
),
|
2017-09-07 05:53:41 +02:00
|
|
|
];
|
|
|
|
let vars = collect_path_env_vars(&pathops);
|
2018-09-08 10:08:36 +02:00
|
|
|
assert_eq!(
|
|
|
|
vars.iter().collect::<HashSet<_>>(),
|
|
|
|
expected_vars.iter().collect::<HashSet<_>>()
|
|
|
|
);
|
2017-09-07 05:53:41 +02:00
|
|
|
}
|
2016-10-26 17:01:55 +02:00
|
|
|
}
|
2017-05-18 19:22:46 +02:00
|
|
|
|
2018-08-21 22:17:36 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
#[cfg(target_family = "windows")]
|
|
|
|
mod tests {
|
|
|
|
use super::spawn;
|
2018-09-09 06:16:40 +02:00
|
|
|
//use super::wrap_commands;
|
2018-08-21 22:17:36 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_start() {
|
2019-01-26 05:26:33 +01:00
|
|
|
let _ = spawn(&vec!["echo".into(), "hi".into()], &[], true);
|
2018-08-21 22:17:36 +02:00
|
|
|
}
|
|
|
|
|
2018-09-09 06:16:40 +02:00
|
|
|
/*
|
2018-08-21 22:17:36 +02:00
|
|
|
#[test]
|
|
|
|
fn wrap_commands_that_have_whitespace() {
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello world".into()]),
|
|
|
|
vec!["echo".into(), "\"hello world\"".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn wrap_commands_that_have_long_whitespace() {
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello world".into()]),
|
|
|
|
vec!["echo".into(), "\"hello world\"".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn wrap_commands_that_have_single_quotes() {
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello ' world".into()]),
|
|
|
|
vec!["echo".into(), "\"hello ' world\"".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello'world".into()]),
|
|
|
|
vec!["echo".into(), "\"hello'world\"".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn wrap_commands_that_have_double_quotes() {
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello \" world".into()]),
|
|
|
|
vec!["echo".into(), "\"hello \"\" world\"".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
wrap_commands(&vec!["echo".into(), "hello\"world".into()]),
|
|
|
|
vec!["echo".into(), "\"hello\"\"world\"".into()] as Vec<String>
|
|
|
|
);
|
|
|
|
}
|
2018-09-09 06:16:40 +02:00
|
|
|
*/
|
2018-08-21 22:17:36 +02:00
|
|
|
}
|