@@ -59,8 +59,6 @@ static void bfs_exec_debug(const struct bfs_exec *execbuf, const char *format, .
59
59
va_end (args );
60
60
}
61
61
62
- extern char * * environ ;
63
-
64
62
/** Determine the size of a single argument, for comparison to arg_max. */
65
63
static size_t bfs_exec_arg_size (const char * arg ) {
66
64
return sizeof (arg ) + strlen (arg ) + 1 ;
@@ -79,6 +77,7 @@ static size_t bfs_exec_arg_max(const struct bfs_exec *execbuf) {
79
77
}
80
78
81
79
// We have to share space with the environment variables
80
+ extern char * * environ ;
82
81
for (char * * envp = environ ; * envp ; ++ envp ) {
83
82
arg_max -= bfs_exec_arg_size (* envp );
84
83
}
@@ -131,6 +130,7 @@ struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs
131
130
execbuf -> argv_cap = 0 ;
132
131
execbuf -> arg_size = 0 ;
133
132
execbuf -> arg_max = 0 ;
133
+ execbuf -> arg_min = 0 ;
134
134
execbuf -> wd_fd = -1 ;
135
135
execbuf -> wd_path = NULL ;
136
136
execbuf -> wd_len = 0 ;
@@ -183,6 +183,7 @@ struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs
183
183
execbuf -> argc = execbuf -> tmpl_argc - 1 ;
184
184
185
185
execbuf -> arg_max = bfs_exec_arg_max (execbuf );
186
+ execbuf -> arg_min = execbuf -> arg_max ;
186
187
}
187
188
188
189
return execbuf ;
@@ -461,6 +462,54 @@ static bool bfs_exec_args_remain(const struct bfs_exec *execbuf) {
461
462
return execbuf -> argc >= execbuf -> tmpl_argc ;
462
463
}
463
464
465
+ /** Compute the current ARG_MAX estimate for binary search. */
466
+ static size_t bfs_exec_estimate_max (const struct bfs_exec * execbuf ) {
467
+ size_t min = execbuf -> arg_min ;
468
+ size_t max = execbuf -> arg_max ;
469
+ return min + (max - min )/2 ;
470
+ }
471
+
472
+ /** Update the ARG_MAX lower bound from a successful execution. */
473
+ static void bfs_exec_update_min (struct bfs_exec * execbuf ) {
474
+ if (execbuf -> arg_size > execbuf -> arg_min ) {
475
+ execbuf -> arg_min = execbuf -> arg_size ;
476
+
477
+ // Don't let min exceed max
478
+ if (execbuf -> arg_min > execbuf -> arg_max ) {
479
+ execbuf -> arg_min = execbuf -> arg_max ;
480
+ }
481
+
482
+ size_t estimate = bfs_exec_estimate_max (execbuf );
483
+ bfs_exec_debug (execbuf , "ARG_MAX between [%zu, %zu], trying %zu\n" ,
484
+ execbuf -> arg_min , execbuf -> arg_max , estimate );
485
+ }
486
+ }
487
+
488
+ /** Update the ARG_MAX upper bound from a failed execution. */
489
+ static size_t bfs_exec_update_max (struct bfs_exec * execbuf ) {
490
+ bfs_exec_debug (execbuf , "Got E2BIG, shrinking argument list...\n" );
491
+
492
+ if (execbuf -> arg_size < execbuf -> arg_max ) {
493
+ execbuf -> arg_max = execbuf -> arg_size ;
494
+
495
+ // Don't let min exceed max
496
+ if (execbuf -> arg_min > execbuf -> arg_max ) {
497
+ execbuf -> arg_min = execbuf -> arg_max ;
498
+ }
499
+ }
500
+
501
+ if (execbuf -> arg_size <= execbuf -> arg_min ) {
502
+ // Lower bound was wrong, restart binary search.
503
+ execbuf -> arg_min = 0 ;
504
+ }
505
+
506
+ // Binary search for a more precise bound
507
+ size_t estimate = bfs_exec_estimate_max (execbuf );
508
+ bfs_exec_debug (execbuf , "ARG_MAX between [%zu, %zu], trying %zu\n" ,
509
+ execbuf -> arg_min , execbuf -> arg_max , estimate );
510
+ return estimate ;
511
+ }
512
+
464
513
/** Execute the pending command from a BFS_EXEC_MULTI execbuf. */
465
514
static int bfs_exec_flush (struct bfs_exec * execbuf ) {
466
515
int ret = 0 , error = 0 ;
@@ -470,30 +519,31 @@ static int bfs_exec_flush(struct bfs_exec *execbuf) {
470
519
execbuf -> argv [execbuf -> argc ] = NULL ;
471
520
ret = bfs_exec_spawn (execbuf );
472
521
error = errno ;
473
- if (ret == 0 || error != E2BIG ) {
522
+ if (ret == 0 ) {
523
+ bfs_exec_update_min (execbuf );
524
+ break ;
525
+ } else if (error != E2BIG ) {
474
526
break ;
475
527
}
476
528
477
529
// Try to recover from E2BIG by trying fewer and fewer arguments
478
530
// until they fit
479
- bfs_exec_debug (execbuf , "Got E2BIG, shrinking argument list...\n" );
480
- execbuf -> argv [execbuf -> argc ] = execbuf -> argv [execbuf -> argc - 1 ];
481
- execbuf -> arg_size -= bfs_exec_arg_size (execbuf -> argv [execbuf -> argc ]);
482
- -- execbuf -> argc ;
531
+ size_t new_max = bfs_exec_update_max (execbuf );
532
+ while (execbuf -> arg_size > new_max ) {
533
+ execbuf -> argv [execbuf -> argc ] = execbuf -> argv [execbuf -> argc - 1 ];
534
+ execbuf -> arg_size -= bfs_exec_arg_size (execbuf -> argv [execbuf -> argc ]);
535
+ -- execbuf -> argc ;
536
+ }
483
537
}
484
- size_t new_argc = execbuf -> argc ;
485
- size_t new_size = execbuf -> arg_size ;
486
538
539
+ size_t new_argc = execbuf -> argc ;
487
540
for (size_t i = execbuf -> tmpl_argc - 1 ; i < new_argc ; ++ i ) {
488
541
free (execbuf -> argv [i ]);
489
542
}
490
543
execbuf -> argc = execbuf -> tmpl_argc - 1 ;
491
544
execbuf -> arg_size = 0 ;
492
545
493
546
if (new_argc < orig_argc ) {
494
- execbuf -> arg_max = new_size ;
495
- bfs_exec_debug (execbuf , "ARG_MAX: %zu\n" , execbuf -> arg_max );
496
-
497
547
// If we recovered from E2BIG, there are unused arguments at the
498
548
// end of the list
499
549
for (size_t i = new_argc + 1 ; i <= orig_argc ; ++ i ) {
@@ -526,10 +576,11 @@ static bool bfs_exec_changed_dirs(const struct bfs_exec *execbuf, const struct B
526
576
527
577
/** Check if we need to flush the execbuf because we're too big. */
528
578
static bool bfs_exec_would_overflow (const struct bfs_exec * execbuf , const char * arg ) {
579
+ size_t arg_max = bfs_exec_estimate_max (execbuf );
529
580
size_t next_size = execbuf -> arg_size + bfs_exec_arg_size (arg );
530
- if (next_size > execbuf -> arg_max ) {
581
+ if (next_size > arg_max ) {
531
582
bfs_exec_debug (execbuf , "Command size (%zu) would exceed maximum (%zu), executing buffered command\n" ,
532
- next_size , execbuf -> arg_max );
583
+ next_size , arg_max );
533
584
return true;
534
585
}
535
586
0 commit comments