Merge pull request #157 from qguv/fix-child-env-overflow
This commit is contained in:
commit
0716830374
|
@ -13,6 +13,8 @@ args=(
|
||||||
'(-h --help)'{-h,--help}'[Prints help information]'
|
'(-h --help)'{-h,--help}'[Prints help information]'
|
||||||
'(-k --kill)'{-k,--kill}'[Send SIGKILL to child processes (deprecated, use -s SIGKILL instead)]'
|
'(-k --kill)'{-k,--kill}'[Send SIGKILL to child processes (deprecated, use -s SIGKILL instead)]'
|
||||||
'(-n --no-shell)'{-n,--no-shell}'[Do not wrap command in ''sh -c'' resp. ''cmd.exe /C'']'
|
'(-n --no-shell)'{-n,--no-shell}'[Do not wrap command in ''sh -c'' resp. ''cmd.exe /C'']'
|
||||||
|
'--no-environment[Do not set WATCHEXEC_*_PATH environment variables for child process]'
|
||||||
|
'--no-meta[Ignore metadata changes]'
|
||||||
'(-p --postpone)'{-p,--postpone}'[Wait until first change to execute command]'
|
'(-p --postpone)'{-p,--postpone}'[Wait until first change to execute command]'
|
||||||
'(-r --restart)'{-r,--restart}'[Restart the process if it''s still running]'
|
'(-r --restart)'{-r,--restart}'[Restart the process if it''s still running]'
|
||||||
'(-V --version)'{-V,--version}'[Prints version information]'
|
'(-V --version)'{-V,--version}'[Prints version information]'
|
||||||
|
|
|
@ -90,6 +90,8 @@
|
||||||
<dt><code>-f</code>, <code>--filter</code> <var>pattern</var></dt><dd><p>Ignores modifications from paths that do not match <var>pattern</var>. This option can be specified multiple times, where a match on any given pattern causes the path to trigger <var>command</var>.</p></dd>
|
<dt><code>-f</code>, <code>--filter</code> <var>pattern</var></dt><dd><p>Ignores modifications from paths that do not match <var>pattern</var>. This option can be specified multiple times, where a match on any given pattern causes the path to trigger <var>command</var>.</p></dd>
|
||||||
<dt><code>-s</code>, <code>--signal</code></dt><dd><p>Sends the specified signal (e.g. <code>SIGKILL</code>) to the child process. Defaults to <code>SIGTERM</code>.</p></dd>
|
<dt><code>-s</code>, <code>--signal</code></dt><dd><p>Sends the specified signal (e.g. <code>SIGKILL</code>) to the child process. Defaults to <code>SIGTERM</code>.</p></dd>
|
||||||
<dt><code>-n</code>, <code>--no-shell</code></dt><dd><p>Execute command directly, do not wrap it in <code>sh -c</code> resp. <code>cmd.exe /C</code>. This is especially useful in combination with <code>--signal</code>, as the signal is then send directly to the specified command. While <code>--no-shell</code> is a little more performant than the default, it prevents using shell-features like pipes and redirects.</p></dd>
|
<dt><code>-n</code>, <code>--no-shell</code></dt><dd><p>Execute command directly, do not wrap it in <code>sh -c</code> resp. <code>cmd.exe /C</code>. This is especially useful in combination with <code>--signal</code>, as the signal is then send directly to the specified command. While <code>--no-shell</code> is a little more performant than the default, it prevents using shell-features like pipes and redirects.</p></dd>
|
||||||
|
<dt><code>--no-meta</code></dt><dd><p>Ignore metadata changes</p></dd>
|
||||||
|
<dt><code>--no-environment</code></dt><dd><p>Do not set WATCHEXEC_*_PATH environment variables for child process.</p></dd>
|
||||||
<dt><code>-i</code>, <code>--ignore</code> <var>pattern</var></dt><dd><p>Ignores modifications from paths that match <var>pattern</var>. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.</p></dd>
|
<dt><code>-i</code>, <code>--ignore</code> <var>pattern</var></dt><dd><p>Ignores modifications from paths that match <var>pattern</var>. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.</p></dd>
|
||||||
<dt><code>-w</code>, <code>--watch</code> <var>path</var></dt><dd><p>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 <var>command</var> to be executed.</p></dd>
|
<dt><code>-w</code>, <code>--watch</code> <var>path</var></dt><dd><p>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 <var>command</var> to be executed.</p></dd>
|
||||||
<dt><code>-r</code>, <code>--restart</code></dt><dd><p>Terminates the child process group if it is still running when subsequent file modifications are detected. By default, sends <code>SIGTERM</code>; use <code>--kill</code> to send <code>SIGKILL</code>.</p></dd>
|
<dt><code>-r</code>, <code>--restart</code></dt><dd><p>Terminates the child process group if it is still running when subsequent file modifications are detected. By default, sends <code>SIGTERM</code>; use <code>--kill</code> to send <code>SIGKILL</code>.</p></dd>
|
||||||
|
|
|
@ -28,6 +28,12 @@ Sends the specified signal (e.g. `SIGKILL`) to the child process. Defaults to `S
|
||||||
* `-n`, `--no-shell`:
|
* `-n`, `--no-shell`:
|
||||||
Execute command directly, do not wrap it in `sh -c` resp. `cmd.exe /C`. This is especially useful in combination with `--signal`, as the signal is then send directly to the specified command. While `--no-shell` is a little more performant than the default, it prevents using shell-features like pipes and redirects.
|
Execute command directly, do not wrap it in `sh -c` resp. `cmd.exe /C`. This is especially useful in combination with `--signal`, as the signal is then send directly to the specified command. While `--no-shell` is a little more performant than the default, it prevents using shell-features like pipes and redirects.
|
||||||
|
|
||||||
|
* `--no-meta`:
|
||||||
|
Ignore metadata changes.
|
||||||
|
|
||||||
|
* `--no-environment`:
|
||||||
|
Do not set WATCHEXEC_*_PATH environment variables for child process.
|
||||||
|
|
||||||
* `-i`, `--ignore` <pattern>:
|
* `-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.
|
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.
|
||||||
|
|
||||||
|
|
14
src/cli.rs
14
src/cli.rs
|
@ -58,6 +58,12 @@ pub struct Args {
|
||||||
/// Do not wrap the commands in a shell.
|
/// Do not wrap the commands in a shell.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub no_shell: bool,
|
pub no_shell: bool,
|
||||||
|
/// Ignore metadata changes.
|
||||||
|
#[builder(default)]
|
||||||
|
pub no_meta: bool,
|
||||||
|
/// Do not set WATCHEXEC_*_PATH environment variables for child process.
|
||||||
|
#[builder(default)]
|
||||||
|
pub no_environment: bool,
|
||||||
/// Skip auto-loading .gitignore files
|
/// Skip auto-loading .gitignore files
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub no_vcs_ignore: bool,
|
pub no_vcs_ignore: bool,
|
||||||
|
@ -207,6 +213,12 @@ where
|
||||||
.help("Do not wrap command in 'sh -c' resp. 'cmd.exe /C'")
|
.help("Do not wrap command in 'sh -c' resp. 'cmd.exe /C'")
|
||||||
.short("n")
|
.short("n")
|
||||||
.long("no-shell"))
|
.long("no-shell"))
|
||||||
|
.arg(Arg::with_name("no-meta")
|
||||||
|
.help("Ignore metadata changes")
|
||||||
|
.long("no-meta"))
|
||||||
|
.arg(Arg::with_name("no-environment")
|
||||||
|
.help("Do not set WATCHEXEC_*_PATH environment variables for child process")
|
||||||
|
.long("no-environment"))
|
||||||
.arg(Arg::with_name("once").short("1").hidden(true))
|
.arg(Arg::with_name("once").short("1").hidden(true))
|
||||||
.arg(Arg::with_name("watch-when-idle")
|
.arg(Arg::with_name("watch-when-idle")
|
||||||
.help("Ignore events while the process is still running")
|
.help("Ignore events while the process is still running")
|
||||||
|
@ -302,6 +314,8 @@ where
|
||||||
debug: args.is_present("verbose"),
|
debug: args.is_present("verbose"),
|
||||||
run_initially: !args.is_present("postpone"),
|
run_initially: !args.is_present("postpone"),
|
||||||
no_shell: args.is_present("no-shell"),
|
no_shell: args.is_present("no-shell"),
|
||||||
|
no_meta: args.is_present("no-meta"),
|
||||||
|
no_environment: args.is_present("no-environment"),
|
||||||
no_vcs_ignore: args.is_present("no-vcs-ignore"),
|
no_vcs_ignore: args.is_present("no-vcs-ignore"),
|
||||||
no_ignore: args.is_present("no-ignore"),
|
no_ignore: args.is_present("no-ignore"),
|
||||||
once: args.is_present("once"),
|
once: args.is_present("once"),
|
||||||
|
|
13
src/error.rs
13
src/error.rs
|
@ -33,7 +33,18 @@ impl From<globset::Error> for Error {
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
impl From<io::Error> for Error {
|
||||||
fn from(err: io::Error) -> Self {
|
fn from(err: io::Error) -> Self {
|
||||||
Self::Io(err)
|
Self::Io(
|
||||||
|
match err.raw_os_error() {
|
||||||
|
Some(os_err) => match os_err {
|
||||||
|
7 => {
|
||||||
|
let msg = "There are so many changed files that the environment variables of the child process have been overrun. Try running with --no-meta or --no-environment.";
|
||||||
|
io::Error::new(io::ErrorKind::Other, msg)
|
||||||
|
},
|
||||||
|
_ => err,
|
||||||
|
}
|
||||||
|
None => err,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::borrow::{ToOwned};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
pub struct Gitignore {
|
pub struct Gitignore {
|
||||||
|
@ -58,7 +59,7 @@ pub fn load(paths: &[PathBuf]) -> Gitignore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p = current.parent().map(|p| p.to_owned());
|
p = current.parent().map(ToOwned::to_owned);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(root) = top_level_git_dir {
|
if let Some(root) = top_level_git_dir {
|
||||||
|
@ -70,7 +71,7 @@ pub fn load(paths: &[PathBuf]) -> Gitignore {
|
||||||
.filter(|e| e.file_name() == ".gitignore")
|
.filter(|e| e.file_name() == ".gitignore")
|
||||||
{
|
{
|
||||||
let gitignore_path = entry.path();
|
let gitignore_path = entry.path();
|
||||||
if let Ok(f) = GitignoreFile::new(&gitignore_path) {
|
if let Ok(f) = GitignoreFile::new(gitignore_path) {
|
||||||
debug!("Loaded {:?}", gitignore_path);
|
debug!("Loaded {:?}", gitignore_path);
|
||||||
files.push(f);
|
files.push(f);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -80,7 +80,7 @@ pub fn load(paths: &[PathBuf]) -> Ignore {
|
||||||
.filter(|e| e.file_name() == ".ignore")
|
.filter(|e| e.file_name() == ".ignore")
|
||||||
{
|
{
|
||||||
let ignore_path = entry.path();
|
let ignore_path = entry.path();
|
||||||
if let Ok(f) = IgnoreFile::new(&ignore_path) {
|
if let Ok(f) = IgnoreFile::new(ignore_path) {
|
||||||
debug!("Loaded {:?}", ignore_path);
|
debug!("Loaded {:?}", ignore_path);
|
||||||
files.push(f);
|
files.push(f);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,8 +5,8 @@ use crate::pathop::PathOp;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn spawn(cmd: &[String], updated_paths: &[PathOp], no_shell: bool) -> Result<Process> {
|
pub fn spawn(cmd: &[String], updated_paths: &[PathOp], no_shell: bool, no_environment: bool) -> Result<Process> {
|
||||||
self::imp::Process::new(cmd, updated_paths, no_shell).map_err(|e| e.into())
|
self::imp::Process::new(cmd, updated_paths, no_shell, no_environment).map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::imp::Process;
|
pub use self::imp::Process;
|
||||||
|
@ -83,7 +83,7 @@ mod imp {
|
||||||
|
|
||||||
#[allow(clippy::mutex_atomic)]
|
#[allow(clippy::mutex_atomic)]
|
||||||
impl Process {
|
impl Process {
|
||||||
pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool) -> Result<Self> {
|
pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool, no_environment: bool) -> Result<Self> {
|
||||||
use nix::unistd::*;
|
use nix::unistd::*;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
|
@ -107,7 +107,12 @@ mod imp {
|
||||||
|
|
||||||
debug!("Assembled command {:?}", command);
|
debug!("Assembled command {:?}", command);
|
||||||
|
|
||||||
let command_envs = super::collect_path_env_vars(updated_paths);
|
let command_envs = if no_environment {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
super::collect_path_env_vars(updated_paths)
|
||||||
|
};
|
||||||
|
|
||||||
for &(ref name, ref val) in &command_envs {
|
for &(ref name, ref val) in &command_envs {
|
||||||
command.env(name, val);
|
command.env(name, val);
|
||||||
}
|
}
|
||||||
|
@ -229,7 +234,7 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool) -> Result<Self> {
|
pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool, no_environment: bool) -> Result<Self> {
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::os::windows::io::IntoRawHandle;
|
use std::os::windows::io::IntoRawHandle;
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
|
@ -304,7 +309,12 @@ mod imp {
|
||||||
command.creation_flags(CREATE_SUSPENDED);
|
command.creation_flags(CREATE_SUSPENDED);
|
||||||
debug!("Assembled command {:?}", command);
|
debug!("Assembled command {:?}", command);
|
||||||
|
|
||||||
let command_envs = super::collect_path_env_vars(updated_paths);
|
let command_envs = if no_environment {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
super::collect_path_env_vars(updated_paths)
|
||||||
|
};
|
||||||
|
|
||||||
for &(ref name, ref val) in &command_envs {
|
for &(ref name, ref val) in &command_envs {
|
||||||
command.env(name, val);
|
command.env(name, val);
|
||||||
}
|
}
|
||||||
|
@ -508,7 +518,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_start() {
|
fn test_start() {
|
||||||
let _ = spawn(&["echo".into(), "hi".into()], &[], true);
|
let _ = spawn(&["echo".into(), "hi".into()], &[], true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -631,7 +641,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_start() {
|
fn test_start() {
|
||||||
let _ = spawn(&["echo".into(), "hi".into()], &[], true);
|
let _ = spawn(&["echo".into(), "hi".into()], &[], true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
12
src/run.rs
12
src/run.rs
|
@ -134,7 +134,7 @@ where
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
debug!("Waiting for filesystem activity");
|
debug!("Waiting for filesystem activity");
|
||||||
let paths = wait_fs(&rx, &filter, args.debounce);
|
let paths = wait_fs(&rx, &filter, args.debounce, args.no_meta);
|
||||||
debug!("Paths updated: {:?}", paths);
|
debug!("Paths updated: {:?}", paths);
|
||||||
|
|
||||||
if !handler.on_update(&paths)? {
|
if !handler.on_update(&paths)? {
|
||||||
|
@ -185,7 +185,7 @@ impl ExecHandler {
|
||||||
|
|
||||||
debug!("Launching child process");
|
debug!("Launching child process");
|
||||||
let mut guard = self.child_process.write()?;
|
let mut guard = self.child_process.write()?;
|
||||||
*guard = Some(process::spawn(&self.args.cmd, ops, self.args.no_shell)?);
|
*guard = Some(process::spawn(&self.args.cmd, ops, self.args.no_shell, self.args.no_environment)?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ pub fn run(args: Args) -> Result<()> {
|
||||||
watch(&ExecHandler::new(args)?)
|
watch(&ExecHandler::new(args)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64) -> Vec<PathOp> {
|
fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64, no_meta: bool) -> Vec<PathOp> {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
let mut cache = HashMap::new();
|
let mut cache = HashMap::new();
|
||||||
|
|
||||||
|
@ -291,6 +291,12 @@ fn wait_fs(rx: &Receiver<Event>, filter: &NotificationFilter, debounce: u64) ->
|
||||||
|
|
||||||
if let Some(ref path) = e.path {
|
if let Some(ref path) = e.path {
|
||||||
let pathop = PathOp::new(path, e.op.ok(), e.cookie);
|
let pathop = PathOp::new(path, e.op.ok(), e.cookie);
|
||||||
|
if let Some(op) = pathop.op {
|
||||||
|
if no_meta && PathOp::is_meta(op) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore cache for the initial file. Otherwise, in
|
// Ignore cache for the initial file. Otherwise, in
|
||||||
// debug mode it's hard to track what's going on
|
// debug mode it's hard to track what's going on
|
||||||
let excluded = filter.is_excluded(path);
|
let excluded = filter.is_excluded(path);
|
||||||
|
|
Loading…
Reference in New Issue