archive_unix.go 4.4 KB

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