diff --git a/src/exec/command.rs b/src/exec/command.rs new file mode 100644 index 0000000..58c923c --- /dev/null +++ b/src/exec/command.rs @@ -0,0 +1,98 @@ +// Copyright (c) 2017 fd developers +// Licensed under the Apache License, Version 2.0 +// +// or the MIT license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::process::Command; +use std::sync::{Arc, Mutex}; +use std::io; + +/// Executes a command. +#[cfg(not(unix))] +pub fn execute_command(mut cmd: Command, out_perm: Arc>) { + use std::process::Stdio; + use std::io::Write; + + // Spawn the supplied command. + let output = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).output(); + + // Then wait for the command to exit, if it was spawned. + match output { + Ok(output) => { + // While this lock is active, this thread will be the only thread allowed + // to write it's outputs. + let _lock = out_perm.lock().unwrap(); + + let stdout = io::stdout(); + let stderr = io::stderr(); + + let _ = stdout.lock().write_all(&output.stdout); + let _ = stderr.lock().write_all(&output.stderr); + } + Err(why) => eprintln!("fd: exec error: {}", why), + } +} + +/// Executes a command. +#[cfg(all(unix))] +pub fn execute_command(mut cmd: Command, out_perm: Arc>) { + use libc::{close, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; + use std::fs::File; + use std::os::unix::process::CommandExt; + use std::os::unix::io::FromRawFd; + + // Initial a pair of pipes that will be used to + // pipe the std{out,err} of the spawned process. + let mut stdout_fds = [0; 2]; + let mut stderr_fds = [0; 2]; + + unsafe { + pipe(stdout_fds.as_mut_ptr()); + pipe(stderr_fds.as_mut_ptr()); + } + + // Configure the pipes accordingly in the child. + let child = cmd.before_exec(move || unsafe { + // Redirect the child's std{out,err} to the write ends of our pipe. + dup2(stdout_fds[1], STDOUT_FILENO); + dup2(stderr_fds[1], STDERR_FILENO); + + // Close all the fds we created here, so EOF will be sent when the program exits. + close(stdout_fds[0]); + close(stdout_fds[1]); + close(stderr_fds[0]); + close(stderr_fds[1]); + Ok(()) + }).spawn(); + + // Open the read end of the pipes as `File`s. + let (mut pout, mut perr) = unsafe { + // Close the write ends of the pipes in the parent + close(stdout_fds[1]); + close(stderr_fds[1]); + ( + // But create files from the read ends. + File::from_raw_fd(stdout_fds[0]), + File::from_raw_fd(stderr_fds[0]), + ) + }; + + match child { + Ok(mut child) => { + let _ = child.wait(); + + // Create a lock to ensure that this thread has exclusive access to writing. + let _lock = self.out_perm.lock().unwrap(); + + // And then write the outputs of the process until EOF is sent to each file. + let stdout = io::stdout(); + let stderr = io::stderr(); + let _ = io::copy(&mut pout, &mut stdout.lock()); + let _ = io::copy(&mut perr, &mut stderr.lock()); + } + Err(why) => eprintln!("fd: exec error: {}", why), + } +} diff --git a/src/exec/job.rs b/src/exec/job.rs index 42e3882..409b7e5 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -34,6 +34,6 @@ pub fn job( // Drop the lock so that other threads can read from the the receiver. drop(lock); // Generate a command and execute it. - cmd.generate(&value, Arc::clone(&out_perm)).then_execute(); + cmd.generate_and_execute(&value, Arc::clone(&out_perm)); } } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index a29be4c..d0dff33 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -7,27 +7,28 @@ // according to those terms. // TODO: Possible optimization could avoid pushing characters on a buffer. -mod ticket; +mod command; mod token; mod job; mod input; use std::borrow::Cow; use std::path::Path; +use std::process::Command; use std::sync::{Arc, Mutex}; use regex::Regex; use self::input::{basename, dirname, remove_extension}; -use self::ticket::CommandTicket; +use self::command::execute_command; use self::token::Token; pub use self::job::job; /// Contains a collection of `TokenizedArgument`s that are utilized to generate command strings. /// /// The arguments are a representation of the supplied command template, and are meant to be coupled -/// with an input in order to generate a command. The `generate()` method will be used to generate -/// a command and obtain a ticket for executing that command. +/// with an input in order to generate a command. The `generate_and_execute()` method will be used +/// to generate a command and execute it. #[derive(Debug, Clone, PartialEq)] pub struct TokenizedCommand { args: Vec, @@ -74,7 +75,7 @@ impl TokenizedCommand { S: AsRef, { lazy_static! { - static ref PLACEHOLDER: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap(); + static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap(); } let mut args = Vec::new(); @@ -86,7 +87,7 @@ impl TokenizedCommand { let mut tokens = Vec::new(); let mut start = 0; - for placeholder in PLACEHOLDER.find_iter(arg) { + for placeholder in PLACEHOLDER_PATTERN.find_iter(arg) { // Leading text before the placeholder. if placeholder.start() > start { tokens.push(Token::Text(arg[start..placeholder.start()].to_owned())); @@ -128,24 +129,23 @@ impl TokenizedCommand { TokenizedCommand { args: args } } - /// Generates a ticket that is required to execute the generated command. + /// Generates and executes a command. /// - /// Using the internal `args` field, and a supplied `input` variable, arguments will be - /// collected in a Vec. Once all arguments have been processed, the Vec will be wrapped - /// within a `CommandTicket`, which will be responsible for executing the command. - pub fn generate(&self, input: &Path, out_perm: Arc>) -> CommandTicket { + /// Using the internal `args` field, and a supplied `input` variable, a `Command` will be + /// build. Once all arguments have been processed, the command is executed. + pub fn generate_and_execute(&self, input: &Path, out_perm: Arc>) { let input = input .strip_prefix(".") .unwrap_or(input) .to_string_lossy() .into_owned(); - let mut args = Vec::with_capacity(self.args.len()); - for arg in &self.args { - args.push(arg.generate(&input)); + let mut cmd = Command::new(self.args[0].generate(&input).as_ref()); + for arg in &self.args[1..] { + cmd.arg(arg.generate(&input).as_ref()); } - CommandTicket::new(args, out_perm) + execute_command(cmd, out_perm) } } diff --git a/src/exec/ticket.rs b/src/exec/ticket.rs deleted file mode 100644 index e9982c7..0000000 --- a/src/exec/ticket.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2017 fd developers -// Licensed under the Apache License, Version 2.0 -// -// or the MIT license , -// at your option. All files in the project carrying such -// notice may not be copied, modified, or distributed except -// according to those terms. - -use std::process::Command; -use std::sync::{Arc, Mutex}; -use std::io; - -/// A state that offers access to executing a generated command. -/// -/// The ticket holds the collection of arguments of a command to be executed. -pub struct CommandTicket { - args: Vec, - out_perm: Arc>, -} - -impl CommandTicket { - pub fn new(args: I, out_perm: Arc>) -> CommandTicket - where - I: IntoIterator, - S: AsRef, - { - CommandTicket { - args: args.into_iter().map(|x| x.as_ref().to_owned()).collect(), - out_perm: out_perm, - } - } - - /// Executes the command stored within the ticket. - #[cfg(not(unix))] - pub fn then_execute(self) { - use std::process::Stdio; - use std::io::Write; - - // Spawn the supplied command. - let cmd = Command::new(&self.args[0]) - .args(&self.args[1..]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output(); - - // Then wait for the command to exit, if it was spawned. - match cmd { - Ok(output) => { - // While this lock is active, this thread will be the only thread allowed - // to write it's outputs. - let _lock = self.out_perm.lock().unwrap(); - - let stdout = io::stdout(); - let stderr = io::stderr(); - - let _ = stdout.lock().write_all(&output.stdout); - let _ = stderr.lock().write_all(&output.stderr); - } - Err(why) => eprintln!("fd: exec error: {}", why), - } - } - - #[cfg(all(unix))] - pub fn then_execute(self) { - use libc::{close, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; - use std::fs::File; - use std::os::unix::process::CommandExt; - use std::os::unix::io::FromRawFd; - - // Initial a pair of pipes that will be used to - // pipe the std{out,err} of the spawned process. - let mut stdout_fds = [0; 2]; - let mut stderr_fds = [0; 2]; - - unsafe { - pipe(stdout_fds.as_mut_ptr()); - pipe(stderr_fds.as_mut_ptr()); - } - - // Spawn the supplied command. - let cmd = Command::new(&self.args[0]) - .args(&self.args[1..]) - // Configure the pipes accordingly in the child. - .before_exec(move || unsafe { - // Redirect the child's std{out,err} to the write ends of our pipe. - dup2(stdout_fds[1], STDOUT_FILENO); - dup2(stderr_fds[1], STDERR_FILENO); - - // Close all the fds we created here, so EOF will be sent when the program exits. - close(stdout_fds[0]); - close(stdout_fds[1]); - close(stderr_fds[0]); - close(stderr_fds[1]); - Ok(()) - }) - .spawn(); - - // Open the read end of the pipes as `File`s. - let (mut pout, mut perr) = unsafe { - // Close the write ends of the pipes in the parent - close(stdout_fds[1]); - close(stderr_fds[1]); - ( - // But create files from the read ends. - File::from_raw_fd(stdout_fds[0]), - File::from_raw_fd(stderr_fds[0]), - ) - }; - - match cmd { - Ok(mut child) => { - let _ = child.wait(); - - // Create a lock to ensure that this thread has exclusive access to writing. - let _lock = self.out_perm.lock().unwrap(); - - // And then write the outputs of the process until EOF is sent to each file. - let stdout = io::stdout(); - let stderr = io::stderr(); - let _ = io::copy(&mut pout, &mut stdout.lock()); - let _ = io::copy(&mut perr, &mut stderr.lock()); - } - Err(why) => eprintln!("fd: exec error: {}", why), - } - } -}