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 }