argmax/
lib.rs

1//! `argmax` is a library that allows applications to avoid *Argument list too long* errors
2//! (`E2BIG`) by providing a wrapper around [`std::process::Command`]. The [`Command::try_arg`]
3//! function can be used to add as many arguments as possible. The function returns `false` if no
4//! more arguments can be added (command execution would result in immediate failure).
5//!
6//! # Usage
7//! ```
8//! # #[cfg(unix)]
9//! # {
10//! use argmax::Command;
11//!
12//! let mut cmd = Command::new("echo");
13//!
14//! // Add as many arguments as possible
15//! while cmd.try_arg("foo").is_ok() {}
16//!
17//! assert!(cmd.status().unwrap().success());
18//! # }
19//! ```
20
21use std::ffi::OsStr;
22use std::io;
23use std::ops::Deref;
24use std::path::Path;
25use std::process::{self, Child, ExitStatus, Output, Stdio};
26
27mod constants;
28#[cfg(not(unix))]
29mod other;
30#[cfg(unix)]
31mod unix;
32
33#[cfg(not(unix))]
34use other as platform;
35#[cfg(unix)]
36use unix as platform;
37
38#[cfg(test)]
39mod experimental_limit;
40
41#[derive(Debug)]
42pub struct Command {
43    inner: process::Command,
44    remaining_argument_length: i64,
45}
46
47impl Command {
48    /// See [`std::process::Command::new`][process::Command#method.new].
49    pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
50        Command {
51            inner: process::Command::new(&program),
52            remaining_argument_length: platform::available_argument_length([program].iter())
53                .unwrap_or(constants::REASONABLE_DEFAULT_LENGTH),
54        }
55    }
56
57    /// Return the appropriate error for a too-big command line.
58    fn e2big() -> io::Error {
59        // io::ErrorKind::ArgumentListTooLong is unstable, so get it manually
60        #[cfg(unix)]
61        return io::Error::from_raw_os_error(libc::E2BIG);
62        #[cfg(not(unix))]
63        return io::ErrorKind::Other.into();
64    }
65
66    /// Get the size of some arguments, if they'll fit.
67    fn check_size<I, S>(&self, args: I) -> io::Result<i64>
68    where
69        I: IntoIterator<Item = S>,
70        S: AsRef<OsStr>,
71    {
72        let mut size = 0;
73        for arg in args {
74            let arg = arg.as_ref();
75            if arg.len() as i64 > platform::max_single_argument_length() {
76                return Err(Self::e2big());
77            }
78            size += platform::arg_size(arg);
79        }
80
81        if size > self.remaining_argument_length {
82            return Err(Self::e2big());
83        }
84
85        Ok(size)
86    }
87
88    /// Check if an additional argument would fit in the command.
89    pub fn arg_would_fit<S: AsRef<OsStr>>(&self, arg: S) -> bool {
90        self.args_would_fit(&[arg])
91    }
92
93    /// Check if multiple additional arguments would all fit in the command.
94    pub fn args_would_fit<I, S>(&self, args: I) -> bool
95    where
96        I: IntoIterator<Item = S>,
97        S: AsRef<OsStr>,
98    {
99        self.check_size(args).is_ok()
100    }
101
102    /// Like [`std::process::Command::arg`][process::Command#method.arg], add an argument to the
103    /// command, but only if it will fit.
104    pub fn try_arg<S: AsRef<OsStr>>(&mut self, arg: S) -> io::Result<&mut Self> {
105        self.try_args(&[arg])
106    }
107
108    /// Like [`std::process::Command::arg`][process::Command#method.args], add multiple arguments to
109    /// the command, but only if they will all fit.
110    pub fn try_args<I, S>(&mut self, args: I) -> io::Result<&mut Self>
111    where
112        I: IntoIterator<Item = S> + Copy,
113        S: AsRef<OsStr>,
114    {
115        let size = self.check_size(args)?;
116        self.inner.args(args);
117        self.remaining_argument_length -= size;
118        Ok(self)
119    }
120
121    /// See [`std::process::Command::current_dir`][process::Command#method.current_dir].
122    pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
123        self.inner.current_dir(dir);
124        self
125    }
126
127    /// See [`std::process::Command::stdin`][process::Command#method.stdin].
128    pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
129        self.inner.stdin(cfg);
130        self
131    }
132
133    /// See [`std::process::Command::stdout`][process::Command#method.stdout].
134    pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
135        self.inner.stdout(cfg);
136        self
137    }
138
139    /// See [`std::process::Command::stderr`][process::Command#method.stderr].
140    pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
141        self.inner.stderr(cfg);
142        self
143    }
144
145    /// See [`std::process::Command::spawn`][process::Command#method.spawn].
146    pub fn spawn(&mut self) -> io::Result<Child> {
147        self.inner.spawn()
148    }
149
150    /// See [`std::process::Command::output`][process::Command#method.output].
151    pub fn output(&mut self) -> io::Result<Output> {
152        self.inner.output()
153    }
154
155    /// See [`std::process::Command::status`][process::Command#method.status].
156    pub fn status(&mut self) -> io::Result<ExitStatus> {
157        self.inner.status()
158    }
159}
160
161impl Deref for Command {
162    type Target = process::Command;
163
164    fn deref(&self) -> &Self::Target {
165        &self.inner
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_api() {
175        let mut cmd = Command::new("echo");
176        cmd.try_arg("Hello").expect("try_arg() to succeed");
177        cmd.try_args(&["world", "!"])
178            .expect("try_args() to succeed");
179
180        cmd.current_dir(".");
181
182        cmd.stdin(Stdio::inherit());
183        cmd.stdout(Stdio::piped());
184        cmd.stderr(Stdio::null());
185
186        let mut output = cmd
187            .spawn()
188            .expect("spawn() to succeed")
189            .wait_with_output()
190            .expect("wait_with_output() to succeed");
191        assert!(output.stdout.len() > 13);
192        assert!(output.status.success());
193
194        output = cmd.output().expect("output() to succeed");
195        assert!(output.stdout.len() > 13);
196        assert!(output.status.success());
197
198        let status = cmd.status().expect("status() to succeed");
199        assert!(status.success());
200    }
201}