Skip to content

Commit 097d11d

Browse files
authored
fix: avoid sending huge command when fetching extra files from workspace (#2043)
Right now to fetch extra files from the workspace (mainly licenses) we simply append the list of files to the `tar` command. This will lead to "-ash: sh: Argument list too long" for cases where the list of arguments surpasses `MAX_ARG_STRINGS` Instead we write the list to a file, and use the `-T/--files-from` option in order to populate the tar from that list. To avoid network problems, also the streaming of the list is divided in small chunks. Signed-off-by: Luca Di Maio <[email protected]>
1 parent d1d5744 commit 097d11d

File tree

1 file changed

+78
-3
lines changed

1 file changed

+78
-3
lines changed

pkg/container/qemu_runner.go

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,17 @@ func (bw *qemu) WorkspaceTar(ctx context.Context, cfg *Config, extraFiles []stri
421421
// Now, append those files to the extraFiles list (there should be no
422422
// duplicates)
423423
extraFiles = append(extraFiles, licenseFiles...)
424+
// We inject the list of extra files to a remote file in order to use it
425+
// with `-T` option of tar. This avoids passing too many arguments to
426+
// the command, that would lead to an "-ash: sh: Argument list too long"
427+
// error otherwise.
428+
if len(extraFiles) > 0 {
429+
err = streamExtraFilesList(ctx, cfg, extraFiles)
430+
if err != nil {
431+
clog.FromContext(ctx).Errorf("failed to send list of extra files: %v", err)
432+
return nil, err
433+
}
434+
}
424435

425436
outFile, err := os.Create(filepath.Join(cfg.WorkspaceDir, "melange-out.tar"))
426437
if err != nil {
@@ -436,11 +447,11 @@ func (bw *qemu) WorkspaceTar(ctx context.Context, cfg *Config, extraFiles []stri
436447
// We could just cp -a to /mnt as it is our shared workspace directory, but
437448
// this will lose some file metadata like hardlinks, owners and so on.
438449
// Example of package that won't work when using "cp -a" is glibc.
439-
retrieveCommand := "cd /mount/home/build && find melange-out -type p -delete 2>/dev/null || true && tar cvpf - --xattrs --acls melange-out"
450+
retrieveCommand := "cd /mount/home/build && find melange-out -type p -delete > /dev/null 2>&1 || true && tar cvpf - --xattrs --acls melange-out"
440451
// we append also all the necessary files that we might need, for example Licenses
441452
// for license checks
442-
for _, v := range extraFiles {
443-
retrieveCommand = fmt.Sprintf("%s %q", retrieveCommand, v)
453+
if len(extraFiles) > 0 {
454+
retrieveCommand = retrieveCommand + " -T extrafiles.txt"
444455
}
445456

446457
log := clog.FromContext(ctx)
@@ -947,6 +958,70 @@ func getWorkspaceLicenseFiles(ctx context.Context, cfg *Config, extraFiles []str
947958
return licenseFiles, nil
948959
}
949960

961+
// send to the builder the list of extra files, to avoid sending a single long
962+
// command (avoiding "-ash: sh: Argument list too long") we will use stdin to
963+
// stream it. this also has the upside of not relying on a single-shot connection
964+
// to pass potentially lot of data.
965+
// split in chunks (100 stirngs at time) in order to avoid flackiness in the
966+
// connection.
967+
func streamExtraFilesList(ctx context.Context, cfg *Config, extraFiles []string) error {
968+
writtenStrings := 0
969+
chunkSize := 100
970+
log := clog.FromContext(ctx)
971+
stdout, stderr := logwriter.New(log.Warn), logwriter.New(log.Error)
972+
for {
973+
session, err := cfg.SSHClient.NewSession()
974+
if err != nil {
975+
clog.FromContext(ctx).Errorf("Failed to create session: %s", err)
976+
return err
977+
}
978+
defer session.Close()
979+
980+
session.Stderr = stderr
981+
session.Stdout = stdout
982+
stdin, err := session.StdinPipe()
983+
if err != nil {
984+
return fmt.Errorf("failed to create stdin pipe: %v", err)
985+
}
986+
cmd := "cat >> /home/build/extrafiles.txt"
987+
if err := session.Start(cmd); err != nil {
988+
return fmt.Errorf("failed to start command: %v", err)
989+
}
990+
991+
// Write 100 strings there (or remainder)
992+
endIndex := writtenStrings + chunkSize
993+
endIndex = min(endIndex, len(extraFiles))
994+
chunk := extraFiles[writtenStrings:endIndex]
995+
996+
clog.FromContext(ctx).Debugf("sent %d of %d",
997+
writtenStrings, len(extraFiles))
998+
if _, err := io.Copy(stdin, strings.NewReader(
999+
strings.Join(chunk, "\n")+"\n"),
1000+
); err != nil {
1001+
return fmt.Errorf("failed to write content: %v", err)
1002+
}
1003+
1004+
if err := stdin.Close(); err != nil {
1005+
return fmt.Errorf("failed to close stdin: %v", err)
1006+
}
1007+
1008+
if err := session.Wait(); err != nil {
1009+
return fmt.Errorf("command failed: %v", err)
1010+
}
1011+
1012+
writtenStrings = endIndex
1013+
1014+
if writtenStrings >= len(extraFiles) {
1015+
break
1016+
}
1017+
}
1018+
1019+
clog.FromContext(ctx).Debugf("sent %d of %d",
1020+
writtenStrings, len(extraFiles))
1021+
1022+
return nil
1023+
}
1024+
9501025
func getKernelPath(ctx context.Context) (string, error) {
9511026
clog.FromContext(ctx).Debug("qemu: setting up kernel for vm")
9521027
kernel := "/boot/vmlinuz"

0 commit comments

Comments
 (0)