moby/pkg/chrootarchive/archive_unix_nolinux.go
Marat Radchenko 9e3ed7b429 pkg/chrootarchive: fix Darwin build
Before this commit, `doPack`, `doUnpack` and `doUnpackLayer` were not implemented for Darwin, causing build failure.

This change allows all non-Linux Unixes to use FreeBSD reexec-based pack/unpack implementation

See also: moby/buildkit#4059
See also: 8b843732b3

Signed-off-by: Marat Radchenko <marat@slonopotamus.org>
2023-09-28 16:23:33 +03:00

226 lines
4.8 KiB
Go

//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
}