github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/chrootarchive/archive_unix.go (about)

     1  // +build !windows
     2  
     3  package chrootarchive // import "github.com/demonoid81/moby/pkg/chrootarchive"
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/json"
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  
    17  	"github.com/demonoid81/moby/pkg/archive"
    18  	"github.com/demonoid81/moby/pkg/reexec"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  // untar is the entry-point for docker-untar on re-exec. This is not used on
    23  // Windows as it does not support chroot, hence no point sandboxing through
    24  // chroot and rexec.
    25  func untar() {
    26  	runtime.LockOSThread()
    27  	flag.Parse()
    28  
    29  	var options archive.TarOptions
    30  
    31  	// read the options from the pipe "ExtraFiles"
    32  	if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {
    33  		fatal(err)
    34  	}
    35  
    36  	dst := flag.Arg(0)
    37  	var root string
    38  	if len(flag.Args()) > 1 {
    39  		root = flag.Arg(1)
    40  	}
    41  
    42  	if root == "" {
    43  		root = dst
    44  	}
    45  
    46  	if err := chroot(root); err != nil {
    47  		fatal(err)
    48  	}
    49  
    50  	if err := archive.Unpack(os.Stdin, dst, &options); err != nil {
    51  		fatal(err)
    52  	}
    53  	// fully consume stdin in case it is zero padded
    54  	if _, err := flush(os.Stdin); err != nil {
    55  		fatal(err)
    56  	}
    57  
    58  	os.Exit(0)
    59  }
    60  
    61  func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
    62  	if root == "" {
    63  		return errors.New("must specify a root to chroot to")
    64  	}
    65  
    66  	// We can't pass a potentially large exclude list directly via cmd line
    67  	// because we easily overrun the kernel's max argument/environment size
    68  	// when the full image list is passed (e.g. when this is used by
    69  	// `docker load`). We will marshall the options via a pipe to the
    70  	// child
    71  	r, w, err := os.Pipe()
    72  	if err != nil {
    73  		return fmt.Errorf("Untar pipe failure: %v", err)
    74  	}
    75  
    76  	if root != "" {
    77  		relDest, err := filepath.Rel(root, dest)
    78  		if err != nil {
    79  			return err
    80  		}
    81  		if relDest == "." {
    82  			relDest = "/"
    83  		}
    84  		if relDest[0] != '/' {
    85  			relDest = "/" + relDest
    86  		}
    87  		dest = relDest
    88  	}
    89  
    90  	cmd := reexec.Command("docker-untar", dest, root)
    91  	cmd.Stdin = decompressedArchive
    92  
    93  	cmd.ExtraFiles = append(cmd.ExtraFiles, r)
    94  	output := bytes.NewBuffer(nil)
    95  	cmd.Stdout = output
    96  	cmd.Stderr = output
    97  
    98  	if err := cmd.Start(); err != nil {
    99  		w.Close()
   100  		return fmt.Errorf("Untar error on re-exec cmd: %v", err)
   101  	}
   102  
   103  	// write the options to the pipe for the untar exec to read
   104  	if err := json.NewEncoder(w).Encode(options); err != nil {
   105  		w.Close()
   106  		return fmt.Errorf("Untar json encode to pipe failed: %v", err)
   107  	}
   108  	w.Close()
   109  
   110  	if err := cmd.Wait(); err != nil {
   111  		// when `xz -d -c -q | docker-untar ...` failed on docker-untar side,
   112  		// we need to exhaust `xz`'s output, otherwise the `xz` side will be
   113  		// pending on write pipe forever
   114  		io.Copy(ioutil.Discard, decompressedArchive)
   115  
   116  		return fmt.Errorf("Error processing tar file(%v): %s", err, output)
   117  	}
   118  	return nil
   119  }
   120  
   121  func tar() {
   122  	runtime.LockOSThread()
   123  	flag.Parse()
   124  
   125  	src := flag.Arg(0)
   126  	var root string
   127  	if len(flag.Args()) > 1 {
   128  		root = flag.Arg(1)
   129  	}
   130  
   131  	if root == "" {
   132  		root = src
   133  	}
   134  
   135  	if err := realChroot(root); err != nil {
   136  		fatal(err)
   137  	}
   138  
   139  	var options archive.TarOptions
   140  	if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil {
   141  		fatal(err)
   142  	}
   143  
   144  	rdr, err := archive.TarWithOptions(src, &options)
   145  	if err != nil {
   146  		fatal(err)
   147  	}
   148  	defer rdr.Close()
   149  
   150  	if _, err := io.Copy(os.Stdout, rdr); err != nil {
   151  		fatal(err)
   152  	}
   153  
   154  	os.Exit(0)
   155  }
   156  
   157  func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
   158  	if root == "" {
   159  		return nil, errors.New("root path must not be empty")
   160  	}
   161  
   162  	relSrc, err := filepath.Rel(root, srcPath)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	if relSrc == "." {
   167  		relSrc = "/"
   168  	}
   169  	if relSrc[0] != '/' {
   170  		relSrc = "/" + relSrc
   171  	}
   172  
   173  	// make sure we didn't trim a trailing slash with the call to `Rel`
   174  	if strings.HasSuffix(srcPath, "/") && !strings.HasSuffix(relSrc, "/") {
   175  		relSrc += "/"
   176  	}
   177  
   178  	cmd := reexec.Command("docker-tar", relSrc, root)
   179  
   180  	errBuff := bytes.NewBuffer(nil)
   181  	cmd.Stderr = errBuff
   182  
   183  	tarR, tarW := io.Pipe()
   184  	cmd.Stdout = tarW
   185  
   186  	stdin, err := cmd.StdinPipe()
   187  	if err != nil {
   188  		return nil, errors.Wrap(err, "error getting options pipe for tar process")
   189  	}
   190  
   191  	if err := cmd.Start(); err != nil {
   192  		return nil, errors.Wrap(err, "tar error on re-exec cmd")
   193  	}
   194  
   195  	go func() {
   196  		err := cmd.Wait()
   197  		err = errors.Wrapf(err, "error processing tar file: %s", errBuff)
   198  		tarW.CloseWithError(err)
   199  	}()
   200  
   201  	if err := json.NewEncoder(stdin).Encode(options); err != nil {
   202  		stdin.Close()
   203  		return nil, errors.Wrap(err, "tar json encode to pipe failed")
   204  	}
   205  	stdin.Close()
   206  
   207  	return tarR, nil
   208  }