@@ -13,6 +13,7 @@ import (
13
13
"fmt"
14
14
"io"
15
15
"io/ioutil"
16
+ "bufio"
16
17
"log"
17
18
"os"
18
19
"os/exec"
@@ -1508,7 +1509,26 @@ func (b *Builder) runOut(dir string, desc string, env []string, cmdargs ...inter
1508
1509
}
1509
1510
1510
1511
var buf bytes.Buffer
1511
- cmd := exec .Command (cmdline [0 ], cmdline [1 :]... )
1512
+
1513
+ // Execute the requested cmdline. If we're on windows and the size of the
1514
+ // cmdline is larger than the windows limitation, then write the arguments
1515
+ // into a response file and use that.
1516
+ response , cmd := buildLongCommand (cmdline )
1517
+
1518
+ // if a response file was returned, then make sure to remove it after usage
1519
+ if response != nil {
1520
+ defer func (file * os.File ) {
1521
+ filename := file .Name ()
1522
+ if err := response .Close (); err != nil {
1523
+ log .Fatalf ("Unable to close response file (%s): %#v" , filename , err )
1524
+ }
1525
+ if err := os .Remove (filename ); err != nil {
1526
+ log .Fatalf ("Unable to remove response file (%s): %#v" , filename , err )
1527
+ }
1528
+ }(response )
1529
+ }
1530
+
1531
+ // Continue populating the *exec.Cmd structure
1512
1532
cmd .Stdout = & buf
1513
1533
cmd .Stderr = & buf
1514
1534
cmd .Dir = dir
@@ -2428,3 +2448,84 @@ func mkAbsFiles(dir string, files []string) []string {
2428
2448
}
2429
2449
return abs
2430
2450
}
2451
+
2452
+ // Escape all the specified chars in the input string using a backslash.
2453
+ func escapeString (input , characters string ) string {
2454
+ var result string
2455
+ const escape = `\`
2456
+
2457
+ // Iterate through the input string looking for any chars that match
2458
+ for _ , char := range input {
2459
+ // If a specified char was found (or the escape char), then prefix it.
2460
+ if strings .Contains (characters + escape , string (char )) {
2461
+ result += escape
2462
+ }
2463
+
2464
+ // Now we can append the next character
2465
+ result += string (char )
2466
+ }
2467
+
2468
+ // ...and now we should be good to go!
2469
+ return result
2470
+ }
2471
+
2472
+ // buildLongCommand returns a response os.File and an os/exec.Cmd if we're on windows
2473
+ // and the cmdline is larger than the limitation of UNICODE_STRING (0x8000)
2474
+ //
2475
+ // See Issue 18468.
2476
+ func buildLongCommand (cmdline []string ) (* os.File , * exec.Cmd ) {
2477
+ const MAX_UNICODE_STRING = 0x7fff
2478
+ const IFS = "\n "
2479
+
2480
+ const response_file_prefix = "resp-"
2481
+
2482
+ // Sum up the total number of bytes occupied by the commandline
2483
+ var argLen int
2484
+ for _ , arg := range cmdline {
2485
+ argLen += len (arg )
2486
+ argLen += len (" " ) // command line arg separator
2487
+ }
2488
+
2489
+ // Check if we're not running windows, if there's no commandline arguments,
2490
+ // or if there's no need to use a response file because we're under the limit.
2491
+ if runtime .GOOS != "windows" || len (cmdline ) <= 1 || argLen < MAX_UNICODE_STRING {
2492
+ //log.Printf("Executing command (%s) normally (%d < %d).", cmdline[0], argLen, MAX_UNICODE_STRING)
2493
+ return nil , exec .Command (cmdline [0 ], cmdline [1 :]... )
2494
+ }
2495
+ //log.Printf("Executing command (%s) with response file (%d >= %d).", cmdline[0], argLen, MAX_UNICODE_STRING)
2496
+
2497
+ // Create a temporary response file containing the old commandline arguments
2498
+ file , err := ioutil .TempFile ("" , response_file_prefix )
2499
+ if err != nil {
2500
+ log .Fatalf ("Unable to open up a temporary file to insert response parameters: %#v" , err )
2501
+ }
2502
+
2503
+ // Populate temporary file with contents of cmdline[1:]
2504
+ f := bufio .NewWriter (file )
2505
+ for ai , arg := range cmdline [1 :] {
2506
+ var row string
2507
+
2508
+ // FIXME: Figure out what the proper way to quote args in a response file are..
2509
+
2510
+ // If it's not already quoted, then double-quote and escape it
2511
+ if ! strings .HasPrefix (arg , `"` ) && ! strings .HasSuffix (arg , `"` ) {
2512
+ row = fmt .Sprintf (`"%s"` , escapeString (arg , `\"` ))
2513
+
2514
+ // Otherwise we can just add it as-is since the user has already quoted it
2515
+ } else {
2516
+ row = arg
2517
+ }
2518
+
2519
+ // Write it to the response file separated by IFS
2520
+ if _ , err := fmt .Fprintf (f , `%s%s` , row , IFS ); err != nil {
2521
+ log .Fatalf ("Unable to write cmdline[%d] to response file (%s): %#v" , ai , file .Name (), err )
2522
+ }
2523
+ }
2524
+
2525
+ // Flush everything written to the response file
2526
+ if err := f .Flush (); err != nil {
2527
+ log .Fatalf ("Unable to flush output to response file (%s): %#v" , file .Name (), err )
2528
+ }
2529
+
2530
+ return file , exec .Command (cmdline [0 ], "@" + file .Name ())
2531
+ }
0 commit comments