Skip to content

Commit 8d90e4f

Browse files
committed
Added a function to src/cmd/go/internal/work/exec.go that copies arguments into a response file if the total length of all the args is larger than the limitation imposed by windows. Also Modified runOut to use this when executing a sub-command.
Closes issue golang#18468
1 parent 8ff6d98 commit 8d90e4f

File tree

1 file changed

+102
-1
lines changed

1 file changed

+102
-1
lines changed

src/cmd/go/internal/work/exec.go

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"fmt"
1414
"io"
1515
"io/ioutil"
16+
"bufio"
1617
"log"
1718
"os"
1819
"os/exec"
@@ -1508,7 +1509,26 @@ func (b *Builder) runOut(dir string, desc string, env []string, cmdargs ...inter
15081509
}
15091510

15101511
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
15121532
cmd.Stdout = &buf
15131533
cmd.Stderr = &buf
15141534
cmd.Dir = dir
@@ -2428,3 +2448,84 @@ func mkAbsFiles(dir string, files []string) []string {
24282448
}
24292449
return abs
24302450
}
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

Comments
 (0)