gopkg.in/docker/docker.v23@v23.0.11/pkg/chrootarchive/archive_unix.go (about)

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