//go:build unix && !linux package chrootarchive // import "github.com/docker/docker/pkg/chrootarchive" import ( "bytes" "encoding/json" "fmt" "io" "os" "syscall" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/reexec" "github.com/pkg/errors" "golang.org/x/sys/unix" ) const ( packCmd = "chrootarchive-pack-in-chroot" unpackCmd = "chrootarchive-unpack-in-chroot" unpackLayerCmd = "chrootarchive-unpack-layer-in-chroot" ) func init() { reexec.Register(packCmd, reexecMain(packInChroot)) reexec.Register(unpackCmd, reexecMain(unpackInChroot)) reexec.Register(unpackLayerCmd, reexecMain(unpackLayerInChroot)) } func reexecMain(f func(options archive.TarOptions, args ...string) error) func() { return func() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "root parameter is required") os.Exit(1) } options, err := recvOptions() root := os.Args[1] if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } if err := syscall.Chroot(root); err != nil { fmt.Fprintln( os.Stderr, os.PathError{Op: "chroot", Path: root, Err: err}, ) os.Exit(2) } if err := f(*options, os.Args[2:]...); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(3) } } } func doUnpack(decompressedArchive io.Reader, relDest, root string, options *archive.TarOptions) error { optionsR, optionsW, err := os.Pipe() if err != nil { return err } defer optionsW.Close() defer optionsR.Close() stderr := bytes.NewBuffer(nil) cmd := reexec.Command(unpackCmd, root, relDest) cmd.Stdin = decompressedArchive cmd.Stderr = stderr cmd.ExtraFiles = []*os.File{ optionsR, } if err = cmd.Start(); err != nil { return errors.Wrap(err, "re-exec error") } if err = json.NewEncoder(optionsW).Encode(options); err != nil { return errors.Wrap(err, "tar options encoding failed") } if err = cmd.Wait(); err != nil { return errors.Wrap(err, stderr.String()) } return nil } func doPack(relSrc, root string, options *archive.TarOptions) (io.ReadCloser, error) { optionsR, optionsW, err := os.Pipe() if err != nil { return nil, err } defer optionsW.Close() defer optionsR.Close() stderr := bytes.NewBuffer(nil) cmd := reexec.Command(packCmd, root, relSrc) cmd.ExtraFiles = []*os.File{ optionsR, } cmd.Stderr = stderr stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } r, w := io.Pipe() if err = cmd.Start(); err != nil { return nil, errors.Wrap(err, "re-exec error") } go func() { _, _ = io.Copy(w, stdout) // Cleanup once stdout pipe is closed. if err = cmd.Wait(); err != nil { r.CloseWithError(errors.Wrap(err, stderr.String())) } else { r.Close() } }() if err = json.NewEncoder(optionsW).Encode(options); err != nil { return nil, errors.Wrap(err, "tar options encoding failed") } return r, nil } func doUnpackLayer(root string, layer io.Reader, options *archive.TarOptions) (int64, error) { var result int64 optionsR, optionsW, err := os.Pipe() if err != nil { return 0, err } defer optionsW.Close() defer optionsR.Close() buffer := bytes.NewBuffer(nil) cmd := reexec.Command(unpackLayerCmd, root) cmd.Stdin = layer cmd.Stdout = buffer cmd.Stderr = buffer cmd.ExtraFiles = []*os.File{ optionsR, } if err = cmd.Start(); err != nil { return 0, errors.Wrap(err, "re-exec error") } if err = json.NewEncoder(optionsW).Encode(options); err != nil { return 0, errors.Wrap(err, "tar options encoding failed") } if err = cmd.Wait(); err != nil { return 0, errors.Wrap(err, buffer.String()) } if err = json.NewDecoder(buffer).Decode(&result); err != nil { return 0, errors.Wrap(err, "json decoding error") } return result, nil } func unpackInChroot(options archive.TarOptions, args ...string) error { if len(args) < 1 { return fmt.Errorf("destination parameter is required") } relDest := args[0] return archive.Unpack(os.Stdin, relDest, &options) } func packInChroot(options archive.TarOptions, args ...string) error { if len(args) < 1 { return fmt.Errorf("source parameter is required") } relSrc := args[0] tb, err := archive.NewTarballer(relSrc, &options) if err != nil { return err } go tb.Do() _, err = io.Copy(os.Stdout, tb.Reader()) return err } func unpackLayerInChroot(options archive.TarOptions, _args ...string) error { // We need to be able to set any perms _ = unix.Umask(0) size, err := archive.UnpackLayer("/", os.Stdin, &options) if err != nil { return err } return json.NewEncoder(os.Stdout).Encode(size) } func recvOptions() (*archive.TarOptions, error) { var options archive.TarOptions optionsPipe := os.NewFile(3, "tar-options") if optionsPipe == nil { return nil, fmt.Errorf("could not read tar options from the pipe") } defer optionsPipe.Close() err := json.NewDecoder(optionsPipe).Decode(&options) if err != nil { return &options, err } return &options, nil }