@@ -33,6 +33,7 @@ mod options {
3333 pub const NULL : & str = "null" ;
3434 pub const REPLACE : & str = "replace" ;
3535 pub const REPLACE_I : & str = "replace-I" ;
36+ pub const SHOW_LIMITS : & str = "show-limits" ;
3637 pub const VERBOSE : & str = "verbose" ;
3738}
3839
@@ -46,6 +47,7 @@ struct Options {
4647 no_run_if_empty : bool ,
4748 null : bool ,
4849 replace : Option < String > ,
50+ show_limits : bool ,
4951 verbose : bool ,
5052 eof_delimiter : Option < String > ,
5153}
@@ -175,20 +177,76 @@ impl MaxCharsCommandSizeLimiter {
175177
176178 #[ cfg( unix) ]
177179 fn new_system ( env : & HashMap < OsString , OsString > ) -> Self {
178- // POSIX requires that we leave 2048 bytes of space so that the child processes
179- // can have room to set their own environment variables.
180- const ARG_HEADROOM : usize = 2048 ;
181- let arg_max = unsafe { uucore:: libc:: sysconf ( uucore:: libc:: _SC_ARG_MAX) } as usize ;
180+ Self :: new ( system_command_size_limit ( env) )
181+ }
182+ }
182183
183- let env_size: usize = env
184- . iter ( )
185- . map ( |( var, value) | count_osstr_chars_for_exec ( var) + count_osstr_chars_for_exec ( value) )
186- . sum ( ) ;
184+ const POSIX_MIN_ARG_MAX : usize = 4096 ;
187185
188- Self :: new ( arg_max - ARG_HEADROOM - env_size)
186+ #[ cfg( unix) ]
187+ const ARG_HEADROOM : usize = 2048 ;
188+
189+ #[ cfg( unix) ]
190+ fn system_arg_max ( ) -> usize {
191+ let arg_max = unsafe { uucore:: libc:: sysconf ( uucore:: libc:: _SC_ARG_MAX) } ;
192+ if arg_max > 0 {
193+ arg_max as usize
194+ } else {
195+ POSIX_MIN_ARG_MAX
189196 }
190197}
191198
199+ #[ cfg( windows) ]
200+ fn system_arg_max ( ) -> usize {
201+ // Taken from the CreateProcess docs.
202+ 32767
203+ }
204+
205+ fn environment_size ( env : & HashMap < OsString , OsString > ) -> usize {
206+ env. iter ( )
207+ . map ( |( var, value) | count_osstr_chars_for_exec ( var) + count_osstr_chars_for_exec ( value) )
208+ . sum ( )
209+ }
210+
211+ #[ cfg( unix) ]
212+ fn system_command_size_limit ( env : & HashMap < OsString , OsString > ) -> usize {
213+ // POSIX requires that we leave 2048 bytes of space so that the child
214+ // processes can have room to set their own environment variables.
215+ system_arg_max ( )
216+ . saturating_sub ( ARG_HEADROOM )
217+ . saturating_sub ( environment_size ( env) )
218+ }
219+
220+ #[ cfg( windows) ]
221+ fn system_command_size_limit ( _env : & HashMap < OsString , OsString > ) -> usize {
222+ system_arg_max ( )
223+ }
224+
225+ fn show_limits ( env : & HashMap < OsString , OsString > , max_chars : Option < usize > ) {
226+ // Match the GNU xargs diagnostics that downstream scripts parse:
227+ // https://git.savannah.gnu.org/cgit/findutils.git/tree/xargs/xargs.c?h=v4.10.0#n795
228+ let system_limit = system_command_size_limit ( env) ;
229+ let buffer_size = max_chars. unwrap_or ( system_limit) . min ( system_limit) ;
230+
231+ eprintln ! (
232+ "Your environment variables take up {} bytes" ,
233+ environment_size( env)
234+ ) ;
235+ eprintln ! (
236+ "POSIX upper limit on argument length (this system): {}" ,
237+ system_arg_max( )
238+ ) ;
239+ eprintln ! (
240+ "POSIX smallest allowable upper limit on argument length (all systems): {POSIX_MIN_ARG_MAX}"
241+ ) ;
242+ eprintln ! ( "Maximum length of command we could actually use: {system_limit}" ) ;
243+ eprintln ! ( "Size of command buffer we are actually using: {buffer_size}" ) ;
244+ eprintln ! (
245+ "Maximum parallelism (--max-procs must be no greater): {}" ,
246+ i32 :: MAX
247+ ) ;
248+ }
249+
192250impl CommandSizeLimiter for MaxCharsCommandSizeLimiter {
193251 fn try_arg (
194252 & mut self ,
@@ -884,6 +942,7 @@ fn normalize_options(options: Options, matches: &clap::ArgMatches) -> Options {
884942 no_run_if_empty : options. no_run_if_empty ,
885943 null : options. null ,
886944 replace,
945+ show_limits : options. show_limits ,
887946 verbose : options. verbose ,
888947 eof_delimiter,
889948 }
@@ -985,6 +1044,12 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
9851044 )
9861045 . value_parser ( validate_positive_usize) ,
9871046 )
1047+ . arg (
1048+ Arg :: new ( options:: SHOW_LIMITS )
1049+ . long ( options:: SHOW_LIMITS )
1050+ . help ( "Display the command-line length limits and exit" )
1051+ . action ( ArgAction :: SetTrue ) ,
1052+ )
9881053 . arg (
9891054 Arg :: new ( options:: VERBOSE )
9901055 . short ( 't' )
@@ -1082,6 +1147,7 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
10821147 . map_or_else ( || "{}" . to_string ( ) , std:: borrow:: ToOwned :: to_owned)
10831148 } )
10841149 } ) ,
1150+ show_limits : matches. get_flag ( options:: SHOW_LIMITS ) ,
10851151 verbose : matches. get_flag ( options:: VERBOSE ) ,
10861152 eof_delimiter : [ options:: EOF_E , options:: EOF ] . iter ( ) . find_map ( |& option| {
10871153 matches. contains_id ( option) . then ( || {
@@ -1102,6 +1168,11 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
11021168 } ;
11031169 let env = std:: env:: vars_os ( ) . collect ( ) ;
11041170
1171+ if options. show_limits {
1172+ show_limits ( & env, options. max_chars ) ;
1173+ return Ok ( CommandResult :: Success ) ;
1174+ }
1175+
11051176 let mut limiters = LimiterCollection :: new ( ) ;
11061177 if let Some ( max_args) = options. max_args {
11071178 limiters. add ( MaxArgsCommandSizeLimiter :: new ( max_args) ) ;
0 commit comments