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