github.com/cdoern/storage@v1.12.13/pkg/chrootarchive/archive_unix.go (about)

     1  // +build !windows
     2  
     3  package 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/containers/storage/pkg/archive"
    18  	"github.com/containers/storage/pkg/reexec"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  // untar is the entry-point for storage-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("storage-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  		return fmt.Errorf("Untar error on re-exec cmd: %v", err)
   100  	}
   101  
   102  	//write the options to the pipe for the untar exec to read
   103  	if err := json.NewEncoder(w).Encode(options); err != nil {
   104  		return fmt.Errorf("Untar json encode to pipe failed: %v", err)
   105  	}
   106  	w.Close()
   107  
   108  	if err := cmd.Wait(); err != nil {
   109  		// when `xz -d -c -q | storage-untar ...` failed on storage-untar side,
   110  		// we need to exhaust `xz`'s output, otherwise the `xz` side will be
   111  		// pending on write pipe forever
   112  		io.Copy(ioutil.Discard, decompressedArchive)
   113  
   114  		return fmt.Errorf("Error processing tar file(%v): %s", err, output)
   115  	}
   116  	return nil
   117  }
   118  
   119  func tar() {
   120  	runtime.LockOSThread()
   121  	flag.Parse()
   122  
   123  	src := flag.Arg(0)
   124  	var root string
   125  	if len(flag.Args()) > 1 {
   126  		root = flag.Arg(1)
   127  	}
   128  
   129  	if root == "" {
   130  		root = src
   131  	}
   132  
   133  	if err := realChroot(root); err != nil {
   134  		fatal(err)
   135  	}
   136  
   137  	var options archive.TarOptions
   138  	if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil {
   139  		fatal(err)
   140  	}
   141  
   142  	rdr, err := archive.TarWithOptions(src, &options)
   143  	if err != nil {
   144  		fatal(err)
   145  	}
   146  	defer rdr.Close()
   147  
   148  	if _, err := io.Copy(os.Stdout, rdr); err != nil {
   149  		fatal(err)
   150  	}
   151  
   152  	os.Exit(0)
   153  }
   154  
   155  func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
   156  	if root == "" {
   157  		return nil, errors.New("root path must not be empty")
   158  	}
   159  
   160  	relSrc, err := filepath.Rel(root, srcPath)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	if relSrc == "." {
   165  		relSrc = "/"
   166  	}
   167  	if relSrc[0] != '/' {
   168  		relSrc = "/" + relSrc
   169  	}
   170  
   171  	// make sure we didn't trim a trailing slash with the call to `Rel`
   172  	if strings.HasSuffix(srcPath, "/") && !strings.HasSuffix(relSrc, "/") {
   173  		relSrc += "/"
   174  	}
   175  
   176  	cmd := reexec.Command("storage-tar", relSrc, root)
   177  
   178  	errBuff := bytes.NewBuffer(nil)
   179  	cmd.Stderr = errBuff
   180  
   181  	tarR, tarW := io.Pipe()
   182  	cmd.Stdout = tarW
   183  
   184  	stdin, err := cmd.StdinPipe()
   185  	if err != nil {
   186  		return nil, errors.Wrap(err, "error getting options pipe for tar process")
   187  	}
   188  
   189  	if err := cmd.Start(); err != nil {
   190  		return nil, errors.Wrap(err, "tar error on re-exec cmd")
   191  	}
   192  
   193  	go func() {
   194  		err := cmd.Wait()
   195  		err = errors.Wrapf(err, "error processing tar file: %s", errBuff)
   196  		tarW.CloseWithError(err)
   197  	}()
   198  
   199  	if err := json.NewEncoder(stdin).Encode(options); err != nil {
   200  		stdin.Close()
   201  		return nil, errors.Wrap(err, "tar json encode to pipe failed")
   202  	}
   203  	stdin.Close()
   204  
   205  	return tarR, nil
   206  }