moby/pkg/chrootarchive/archive_freebsd.go
Artem Khramov 8b843732b3 pkg/chrootarchive: fix FreeBSD build
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>
2023-07-18 18:42:08 +02:00

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
}