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 }