archive.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. package archive
  2. import (
  3. "bufio"
  4. "bytes"
  5. "compress/bzip2"
  6. "compress/gzip"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "os"
  12. "os/exec"
  13. "path"
  14. "path/filepath"
  15. "strings"
  16. "syscall"
  17. "github.com/dotcloud/docker/pkg/system"
  18. "github.com/dotcloud/docker/utils"
  19. "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
  20. )
  21. type (
  22. Archive io.ReadCloser
  23. ArchiveReader io.Reader
  24. Compression int
  25. TarOptions struct {
  26. Includes []string
  27. Compression Compression
  28. }
  29. )
  30. var (
  31. ErrNotImplemented = errors.New("Function not implemented")
  32. )
  33. const (
  34. Uncompressed Compression = iota
  35. Bzip2
  36. Gzip
  37. Xz
  38. )
  39. func DetectCompression(source []byte) Compression {
  40. for compression, m := range map[Compression][]byte{
  41. Bzip2: {0x42, 0x5A, 0x68},
  42. Gzip: {0x1F, 0x8B, 0x08},
  43. Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
  44. } {
  45. if len(source) < len(m) {
  46. utils.Debugf("Len too short")
  47. continue
  48. }
  49. if bytes.Compare(m, source[:len(m)]) == 0 {
  50. return compression
  51. }
  52. }
  53. return Uncompressed
  54. }
  55. func xzDecompress(archive io.Reader) (io.ReadCloser, error) {
  56. args := []string{"xz", "-d", "-c", "-q"}
  57. return CmdStream(exec.Command(args[0], args[1:]...), archive)
  58. }
  59. func DecompressStream(archive io.Reader) (io.ReadCloser, error) {
  60. buf := bufio.NewReader(archive)
  61. bs, err := buf.Peek(10)
  62. if err != nil {
  63. return nil, err
  64. }
  65. utils.Debugf("[tar autodetect] n: %v", bs)
  66. compression := DetectCompression(bs)
  67. switch compression {
  68. case Uncompressed:
  69. return ioutil.NopCloser(buf), nil
  70. case Gzip:
  71. return gzip.NewReader(buf)
  72. case Bzip2:
  73. return ioutil.NopCloser(bzip2.NewReader(buf)), nil
  74. case Xz:
  75. return xzDecompress(buf)
  76. default:
  77. return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
  78. }
  79. }
  80. func CompressStream(dest io.WriteCloser, compression Compression) (io.WriteCloser, error) {
  81. switch compression {
  82. case Uncompressed:
  83. return utils.NopWriteCloser(dest), nil
  84. case Gzip:
  85. return gzip.NewWriter(dest), nil
  86. case Bzip2, Xz:
  87. // archive/bzip2 does not support writing, and there is no xz support at all
  88. // However, this is not a problem as docker only currently generates gzipped tars
  89. return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
  90. default:
  91. return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
  92. }
  93. }
  94. func (compression *Compression) Extension() string {
  95. switch *compression {
  96. case Uncompressed:
  97. return "tar"
  98. case Bzip2:
  99. return "tar.bz2"
  100. case Gzip:
  101. return "tar.gz"
  102. case Xz:
  103. return "tar.xz"
  104. }
  105. return ""
  106. }
  107. func addTarFile(path, name string, tw *tar.Writer) error {
  108. fi, err := os.Lstat(path)
  109. if err != nil {
  110. return err
  111. }
  112. link := ""
  113. if fi.Mode()&os.ModeSymlink != 0 {
  114. if link, err = os.Readlink(path); err != nil {
  115. return err
  116. }
  117. }
  118. hdr, err := tar.FileInfoHeader(fi, link)
  119. if err != nil {
  120. return err
  121. }
  122. if fi.IsDir() && !strings.HasSuffix(name, "/") {
  123. name = name + "/"
  124. }
  125. hdr.Name = name
  126. stat, ok := fi.Sys().(*syscall.Stat_t)
  127. if ok {
  128. // Currently go does not fill in the major/minors
  129. if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
  130. stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
  131. hdr.Devmajor = int64(major(uint64(stat.Rdev)))
  132. hdr.Devminor = int64(minor(uint64(stat.Rdev)))
  133. }
  134. }
  135. capability, _ := system.Lgetxattr(path, "security.capability")
  136. if capability != nil {
  137. hdr.Xattrs = make(map[string]string)
  138. hdr.Xattrs["security.capability"] = string(capability)
  139. }
  140. if err := tw.WriteHeader(hdr); err != nil {
  141. return err
  142. }
  143. if hdr.Typeflag == tar.TypeReg {
  144. if file, err := os.Open(path); err != nil {
  145. return err
  146. } else {
  147. _, err := io.Copy(tw, file)
  148. if err != nil {
  149. return err
  150. }
  151. file.Close()
  152. }
  153. }
  154. return nil
  155. }
  156. func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader) error {
  157. // hdr.Mode is in linux format, which we can use for sycalls,
  158. // but for os.Foo() calls we need the mode converted to os.FileMode,
  159. // so use hdrInfo.Mode() (they differ for e.g. setuid bits)
  160. hdrInfo := hdr.FileInfo()
  161. switch hdr.Typeflag {
  162. case tar.TypeDir:
  163. // Create directory unless it exists as a directory already.
  164. // In that case we just want to merge the two
  165. if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
  166. if err := os.Mkdir(path, hdrInfo.Mode()); err != nil {
  167. return err
  168. }
  169. }
  170. case tar.TypeReg, tar.TypeRegA:
  171. // Source is regular file
  172. file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode())
  173. if err != nil {
  174. return err
  175. }
  176. if _, err := io.Copy(file, reader); err != nil {
  177. file.Close()
  178. return err
  179. }
  180. file.Close()
  181. case tar.TypeBlock, tar.TypeChar, tar.TypeFifo:
  182. mode := uint32(hdr.Mode & 07777)
  183. switch hdr.Typeflag {
  184. case tar.TypeBlock:
  185. mode |= syscall.S_IFBLK
  186. case tar.TypeChar:
  187. mode |= syscall.S_IFCHR
  188. case tar.TypeFifo:
  189. mode |= syscall.S_IFIFO
  190. }
  191. if err := syscall.Mknod(path, mode, int(mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
  192. return err
  193. }
  194. case tar.TypeLink:
  195. if err := os.Link(filepath.Join(extractDir, hdr.Linkname), path); err != nil {
  196. return err
  197. }
  198. case tar.TypeSymlink:
  199. if err := os.Symlink(hdr.Linkname, path); err != nil {
  200. return err
  201. }
  202. case tar.TypeXGlobalHeader:
  203. utils.Debugf("PAX Global Extended Headers found and ignored")
  204. return nil
  205. default:
  206. return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag)
  207. }
  208. if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
  209. return err
  210. }
  211. for key, value := range hdr.Xattrs {
  212. if err := system.Lsetxattr(path, key, []byte(value), 0); err != nil {
  213. return err
  214. }
  215. }
  216. // There is no LChmod, so ignore mode for symlink. Also, this
  217. // must happen after chown, as that can modify the file mode
  218. if hdr.Typeflag != tar.TypeSymlink {
  219. if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
  220. return err
  221. }
  222. }
  223. ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
  224. // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
  225. if hdr.Typeflag != tar.TypeSymlink {
  226. if err := system.UtimesNano(path, ts); err != nil {
  227. return err
  228. }
  229. } else {
  230. if err := system.LUtimesNano(path, ts); err != nil {
  231. return err
  232. }
  233. }
  234. return nil
  235. }
  236. // Tar creates an archive from the directory at `path`, and returns it as a
  237. // stream of bytes.
  238. func Tar(path string, compression Compression) (io.ReadCloser, error) {
  239. return TarFilter(path, &TarOptions{Compression: compression})
  240. }
  241. func escapeName(name string) string {
  242. escaped := make([]byte, 0)
  243. for i, c := range []byte(name) {
  244. if i == 0 && c == '/' {
  245. continue
  246. }
  247. // all printable chars except "-" which is 0x2d
  248. if (0x20 <= c && c <= 0x7E) && c != 0x2d {
  249. escaped = append(escaped, c)
  250. } else {
  251. escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
  252. }
  253. }
  254. return string(escaped)
  255. }
  256. // TarFilter creates an archive from the directory at `srcPath` with `options`, and returns it as a
  257. // stream of bytes.
  258. //
  259. // Files are included according to `options.Includes`, default to including all files.
  260. // Stream is compressed according to `options.Compression', default to Uncompressed.
  261. func TarFilter(srcPath string, options *TarOptions) (io.ReadCloser, error) {
  262. pipeReader, pipeWriter := io.Pipe()
  263. compressWriter, err := CompressStream(pipeWriter, options.Compression)
  264. if err != nil {
  265. return nil, err
  266. }
  267. tw := tar.NewWriter(compressWriter)
  268. go func() {
  269. // In general we log errors here but ignore them because
  270. // during e.g. a diff operation the container can continue
  271. // mutating the filesystem and we can see transient errors
  272. // from this
  273. if options.Includes == nil {
  274. options.Includes = []string{"."}
  275. }
  276. for _, include := range options.Includes {
  277. filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
  278. if err != nil {
  279. utils.Debugf("Tar: Can't stat file %s to tar: %s\n", srcPath, err)
  280. return nil
  281. }
  282. relFilePath, err := filepath.Rel(srcPath, filePath)
  283. if err != nil {
  284. return nil
  285. }
  286. if err := addTarFile(filePath, relFilePath, tw); err != nil {
  287. utils.Debugf("Can't add file %s to tar: %s\n", srcPath, err)
  288. }
  289. return nil
  290. })
  291. }
  292. // Make sure to check the error on Close.
  293. if err := tw.Close(); err != nil {
  294. utils.Debugf("Can't close tar writer: %s\n", err)
  295. }
  296. if err := compressWriter.Close(); err != nil {
  297. utils.Debugf("Can't close compress writer: %s\n", err)
  298. }
  299. if err := pipeWriter.Close(); err != nil {
  300. utils.Debugf("Can't close pipe writer: %s\n", err)
  301. }
  302. }()
  303. return pipeReader, nil
  304. }
  305. // Untar reads a stream of bytes from `archive`, parses it as a tar archive,
  306. // and unpacks it into the directory at `path`.
  307. // The archive may be compressed with one of the following algorithms:
  308. // identity (uncompressed), gzip, bzip2, xz.
  309. // FIXME: specify behavior when target path exists vs. doesn't exist.
  310. func Untar(archive io.Reader, dest string, options *TarOptions) error {
  311. if archive == nil {
  312. return fmt.Errorf("Empty archive")
  313. }
  314. decompressedArchive, err := DecompressStream(archive)
  315. if err != nil {
  316. return err
  317. }
  318. defer decompressedArchive.Close()
  319. tr := tar.NewReader(decompressedArchive)
  320. var dirs []*tar.Header
  321. // Iterate through the files in the archive.
  322. for {
  323. hdr, err := tr.Next()
  324. if err == io.EOF {
  325. // end of tar archive
  326. break
  327. }
  328. if err != nil {
  329. return err
  330. }
  331. // Normalize name, for safety and for a simple is-root check
  332. hdr.Name = filepath.Clean(hdr.Name)
  333. if !strings.HasSuffix(hdr.Name, "/") {
  334. // Not the root directory, ensure that the parent directory exists
  335. parent := filepath.Dir(hdr.Name)
  336. parentPath := filepath.Join(dest, parent)
  337. if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
  338. err = os.MkdirAll(parentPath, 0777)
  339. if err != nil {
  340. return err
  341. }
  342. }
  343. }
  344. path := filepath.Join(dest, hdr.Name)
  345. // If path exits we almost always just want to remove and replace it
  346. // The only exception is when it is a directory *and* the file from
  347. // the layer is also a directory. Then we want to merge them (i.e.
  348. // just apply the metadata from the layer).
  349. if fi, err := os.Lstat(path); err == nil {
  350. if fi.IsDir() && hdr.Name == "." {
  351. continue
  352. }
  353. if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
  354. if err := os.RemoveAll(path); err != nil {
  355. return err
  356. }
  357. }
  358. }
  359. if err := createTarFile(path, dest, hdr, tr); err != nil {
  360. return err
  361. }
  362. // Directory mtimes must be handled at the end to avoid further
  363. // file creation in them to modify the directory mtime
  364. if hdr.Typeflag == tar.TypeDir {
  365. dirs = append(dirs, hdr)
  366. }
  367. }
  368. for _, hdr := range dirs {
  369. path := filepath.Join(dest, hdr.Name)
  370. ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
  371. if err := syscall.UtimesNano(path, ts); err != nil {
  372. return err
  373. }
  374. }
  375. return nil
  376. }
  377. // TarUntar is a convenience function which calls Tar and Untar, with
  378. // the output of one piped into the other. If either Tar or Untar fails,
  379. // TarUntar aborts and returns the error.
  380. func TarUntar(src string, dst string) error {
  381. utils.Debugf("TarUntar(%s %s)", src, dst)
  382. archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed})
  383. if err != nil {
  384. return err
  385. }
  386. defer archive.Close()
  387. return Untar(archive, dst, nil)
  388. }
  389. // UntarPath is a convenience function which looks for an archive
  390. // at filesystem path `src`, and unpacks it at `dst`.
  391. func UntarPath(src, dst string) error {
  392. archive, err := os.Open(src)
  393. if err != nil {
  394. return err
  395. }
  396. defer archive.Close()
  397. if err := Untar(archive, dst, nil); err != nil {
  398. return err
  399. }
  400. return nil
  401. }
  402. // CopyWithTar creates a tar archive of filesystem path `src`, and
  403. // unpacks it at filesystem path `dst`.
  404. // The archive is streamed directly with fixed buffering and no
  405. // intermediary disk IO.
  406. //
  407. func CopyWithTar(src, dst string) error {
  408. srcSt, err := os.Stat(src)
  409. if err != nil {
  410. return err
  411. }
  412. if !srcSt.IsDir() {
  413. return CopyFileWithTar(src, dst)
  414. }
  415. // Create dst, copy src's content into it
  416. utils.Debugf("Creating dest directory: %s", dst)
  417. if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
  418. return err
  419. }
  420. utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
  421. return TarUntar(src, dst)
  422. }
  423. // CopyFileWithTar emulates the behavior of the 'cp' command-line
  424. // for a single file. It copies a regular file from path `src` to
  425. // path `dst`, and preserves all its metadata.
  426. //
  427. // If `dst` ends with a trailing slash '/', the final destination path
  428. // will be `dst/base(src)`.
  429. func CopyFileWithTar(src, dst string) (err error) {
  430. utils.Debugf("CopyFileWithTar(%s, %s)", src, dst)
  431. srcSt, err := os.Stat(src)
  432. if err != nil {
  433. return err
  434. }
  435. if srcSt.IsDir() {
  436. return fmt.Errorf("Can't copy a directory")
  437. }
  438. // Clean up the trailing /
  439. if dst[len(dst)-1] == '/' {
  440. dst = path.Join(dst, filepath.Base(src))
  441. }
  442. // Create the holding directory if necessary
  443. if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
  444. return err
  445. }
  446. r, w := io.Pipe()
  447. errC := utils.Go(func() error {
  448. defer w.Close()
  449. srcF, err := os.Open(src)
  450. if err != nil {
  451. return err
  452. }
  453. defer srcF.Close()
  454. tw := tar.NewWriter(w)
  455. hdr, err := tar.FileInfoHeader(srcSt, "")
  456. if err != nil {
  457. return err
  458. }
  459. hdr.Name = filepath.Base(dst)
  460. if err := tw.WriteHeader(hdr); err != nil {
  461. return err
  462. }
  463. if _, err := io.Copy(tw, srcF); err != nil {
  464. return err
  465. }
  466. tw.Close()
  467. return nil
  468. })
  469. defer func() {
  470. if er := <-errC; err != nil {
  471. err = er
  472. }
  473. }()
  474. return Untar(r, filepath.Dir(dst), nil)
  475. }
  476. // CmdStream executes a command, and returns its stdout as a stream.
  477. // If the command fails to run or doesn't complete successfully, an error
  478. // will be returned, including anything written on stderr.
  479. func CmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, error) {
  480. if input != nil {
  481. stdin, err := cmd.StdinPipe()
  482. if err != nil {
  483. return nil, err
  484. }
  485. // Write stdin if any
  486. go func() {
  487. io.Copy(stdin, input)
  488. stdin.Close()
  489. }()
  490. }
  491. stdout, err := cmd.StdoutPipe()
  492. if err != nil {
  493. return nil, err
  494. }
  495. stderr, err := cmd.StderrPipe()
  496. if err != nil {
  497. return nil, err
  498. }
  499. pipeR, pipeW := io.Pipe()
  500. errChan := make(chan []byte)
  501. // Collect stderr, we will use it in case of an error
  502. go func() {
  503. errText, e := ioutil.ReadAll(stderr)
  504. if e != nil {
  505. errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
  506. }
  507. errChan <- errText
  508. }()
  509. // Copy stdout to the returned pipe
  510. go func() {
  511. _, err := io.Copy(pipeW, stdout)
  512. if err != nil {
  513. pipeW.CloseWithError(err)
  514. }
  515. errText := <-errChan
  516. if err := cmd.Wait(); err != nil {
  517. pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errText))
  518. } else {
  519. pipeW.Close()
  520. }
  521. }()
  522. // Run the command and return the pipe
  523. if err := cmd.Start(); err != nil {
  524. return nil, err
  525. }
  526. return pipeR, nil
  527. }
  528. // NewTempArchive reads the content of src into a temporary file, and returns the contents
  529. // of that file as an archive. The archive can only be read once - as soon as reading completes,
  530. // the file will be deleted.
  531. func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
  532. f, err := ioutil.TempFile(dir, "")
  533. if err != nil {
  534. return nil, err
  535. }
  536. if _, err := io.Copy(f, src); err != nil {
  537. return nil, err
  538. }
  539. if err = f.Sync(); err != nil {
  540. return nil, err
  541. }
  542. if _, err := f.Seek(0, 0); err != nil {
  543. return nil, err
  544. }
  545. st, err := f.Stat()
  546. if err != nil {
  547. return nil, err
  548. }
  549. size := st.Size()
  550. return &TempArchive{f, size}, nil
  551. }
  552. type TempArchive struct {
  553. *os.File
  554. Size int64 // Pre-computed from Stat().Size() as a convenience
  555. }
  556. func (archive *TempArchive) Read(data []byte) (int, error) {
  557. n, err := archive.File.Read(data)
  558. if err != nil {
  559. os.Remove(archive.File.Name())
  560. }
  561. return n, err
  562. }