archive_unix.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. //go:build !windows
  2. // +build !windows
  3. package chrootarchive // import "github.com/docker/docker/pkg/chrootarchive"
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "flag"
  8. "fmt"
  9. "io"
  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. // reexec.Command() sets cmd.SysProcAttr.Pdeathsig on Linux, which
  82. // causes the started process to be signaled when the creating OS thread
  83. // dies. Ensure that the reexec is not prematurely signaled. See
  84. // https://go.dev/issue/27505 for more information.
  85. runtime.LockOSThread()
  86. defer runtime.UnlockOSThread()
  87. if err := cmd.Start(); err != nil {
  88. w.Close()
  89. return fmt.Errorf("Untar error on re-exec cmd: %v", err)
  90. }
  91. // write the options to the pipe for the untar exec to read
  92. if err := json.NewEncoder(w).Encode(options); err != nil {
  93. w.Close()
  94. return fmt.Errorf("Untar json encode to pipe failed: %v", err)
  95. }
  96. w.Close()
  97. if err := cmd.Wait(); err != nil {
  98. // when `xz -d -c -q | docker-untar ...` failed on docker-untar side,
  99. // we need to exhaust `xz`'s output, otherwise the `xz` side will be
  100. // pending on write pipe forever
  101. io.Copy(io.Discard, decompressedArchive)
  102. return fmt.Errorf("Error processing tar file(%v): %s", err, output)
  103. }
  104. return nil
  105. }
  106. func tar() {
  107. runtime.LockOSThread()
  108. flag.Parse()
  109. src := flag.Arg(0)
  110. var root string
  111. if len(flag.Args()) > 1 {
  112. root = flag.Arg(1)
  113. }
  114. if root == "" {
  115. root = src
  116. }
  117. if err := realChroot(root); err != nil {
  118. fatal(err)
  119. }
  120. var options archive.TarOptions
  121. if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil {
  122. fatal(err)
  123. }
  124. rdr, err := archive.TarWithOptions(src, &options)
  125. if err != nil {
  126. fatal(err)
  127. }
  128. defer rdr.Close()
  129. if _, err := io.Copy(os.Stdout, rdr); err != nil {
  130. fatal(err)
  131. }
  132. os.Exit(0)
  133. }
  134. func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
  135. if root == "" {
  136. return nil, errors.New("root path must not be empty")
  137. }
  138. relSrc, err := filepath.Rel(root, srcPath)
  139. if err != nil {
  140. return nil, err
  141. }
  142. if relSrc == "." {
  143. relSrc = "/"
  144. }
  145. if relSrc[0] != '/' {
  146. relSrc = "/" + relSrc
  147. }
  148. // make sure we didn't trim a trailing slash with the call to `Rel`
  149. if strings.HasSuffix(srcPath, "/") && !strings.HasSuffix(relSrc, "/") {
  150. relSrc += "/"
  151. }
  152. cmd := reexec.Command("docker-tar", relSrc, root)
  153. errBuff := bytes.NewBuffer(nil)
  154. cmd.Stderr = errBuff
  155. tarR, tarW := io.Pipe()
  156. cmd.Stdout = tarW
  157. stdin, err := cmd.StdinPipe()
  158. if err != nil {
  159. return nil, errors.Wrap(err, "error getting options pipe for tar process")
  160. }
  161. started := make(chan error)
  162. go func() {
  163. // reexec.Command() sets cmd.SysProcAttr.Pdeathsig on Linux,
  164. // which causes the started process to be signaled when the
  165. // creating OS thread dies. Ensure that the subprocess is not
  166. // prematurely signaled. See https://go.dev/issue/27505 for more
  167. // information.
  168. runtime.LockOSThread()
  169. defer runtime.UnlockOSThread()
  170. if err := cmd.Start(); err != nil {
  171. started <- err
  172. return
  173. }
  174. close(started)
  175. err := cmd.Wait()
  176. err = errors.Wrapf(err, "error processing tar file: %s", errBuff)
  177. tarW.CloseWithError(err)
  178. }()
  179. if err := <-started; err != nil {
  180. return nil, errors.Wrap(err, "tar error on re-exec cmd")
  181. }
  182. if err := json.NewEncoder(stdin).Encode(options); err != nil {
  183. stdin.Close()
  184. return nil, errors.Wrap(err, "tar json encode to pipe failed")
  185. }
  186. stdin.Close()
  187. return tarR, nil
  188. }