8b843732b3
For unix targets, `goInChroot()` is only implemented for `Linux`, hence FreeBSD build fails. This change - Adds FreeBSD-specific chrooted tar/untar implementation - Fixes statUnix() to accomodate to FreeBSD devminor/devmajor - quirk. See also: https://github.com/containerd/containerd/pull/5991 Signed-off-by: Artem Khramov <akhramov@pm.me> Co-authored-by: Cory Snider <corhere@gmail.com>
225 lines
4.8 KiB
Go
225 lines
4.8 KiB
Go
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 = "freebsd-pack-in-chroot"
|
|
unpackCmd = "freebsd-unpack-in-chroot"
|
|
unpackLayerCmd = "freebsd-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
|
|
}
|