tar.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. /*
  2. Copyright The containerd Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package archive
  14. import (
  15. "archive/tar"
  16. "context"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "os"
  21. "path/filepath"
  22. "runtime"
  23. "strings"
  24. "sync"
  25. "syscall"
  26. "time"
  27. "github.com/containerd/containerd/log"
  28. "github.com/containerd/continuity/fs"
  29. "github.com/pkg/errors"
  30. )
  31. var bufPool = &sync.Pool{
  32. New: func() interface{} {
  33. buffer := make([]byte, 32*1024)
  34. return &buffer
  35. },
  36. }
  37. var errInvalidArchive = errors.New("invalid archive")
  38. // Diff returns a tar stream of the computed filesystem
  39. // difference between the provided directories.
  40. //
  41. // Produces a tar using OCI style file markers for deletions. Deleted
  42. // files will be prepended with the prefix ".wh.". This style is
  43. // based off AUFS whiteouts.
  44. // See https://github.com/opencontainers/image-spec/blob/master/layer.md
  45. func Diff(ctx context.Context, a, b string) io.ReadCloser {
  46. r, w := io.Pipe()
  47. go func() {
  48. err := WriteDiff(ctx, w, a, b)
  49. if err = w.CloseWithError(err); err != nil {
  50. log.G(ctx).WithError(err).Debugf("closing tar pipe failed")
  51. }
  52. }()
  53. return r
  54. }
  55. // WriteDiff writes a tar stream of the computed difference between the
  56. // provided directories.
  57. //
  58. // Produces a tar using OCI style file markers for deletions. Deleted
  59. // files will be prepended with the prefix ".wh.". This style is
  60. // based off AUFS whiteouts.
  61. // See https://github.com/opencontainers/image-spec/blob/master/layer.md
  62. func WriteDiff(ctx context.Context, w io.Writer, a, b string) error {
  63. cw := newChangeWriter(w, b)
  64. err := fs.Changes(ctx, a, b, cw.HandleChange)
  65. if err != nil {
  66. return errors.Wrap(err, "failed to create diff tar stream")
  67. }
  68. return cw.Close()
  69. }
  70. const (
  71. // whiteoutPrefix prefix means file is a whiteout. If this is followed by a
  72. // filename this means that file has been removed from the base layer.
  73. // See https://github.com/opencontainers/image-spec/blob/master/layer.md#whiteouts
  74. whiteoutPrefix = ".wh."
  75. // whiteoutMetaPrefix prefix means whiteout has a special meaning and is not
  76. // for removing an actual file. Normally these files are excluded from exported
  77. // archives.
  78. whiteoutMetaPrefix = whiteoutPrefix + whiteoutPrefix
  79. // whiteoutLinkDir is a directory AUFS uses for storing hardlink links to other
  80. // layers. Normally these should not go into exported archives and all changed
  81. // hardlinks should be copied to the top layer.
  82. whiteoutLinkDir = whiteoutMetaPrefix + "plnk"
  83. // whiteoutOpaqueDir file means directory has been made opaque - meaning
  84. // readdir calls to this directory do not follow to lower layers.
  85. whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
  86. paxSchilyXattr = "SCHILY.xattr."
  87. )
  88. // Apply applies a tar stream of an OCI style diff tar.
  89. // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
  90. func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int64, error) {
  91. root = filepath.Clean(root)
  92. var options ApplyOptions
  93. for _, opt := range opts {
  94. if err := opt(&options); err != nil {
  95. return 0, errors.Wrap(err, "failed to apply option")
  96. }
  97. }
  98. if options.Filter == nil {
  99. options.Filter = all
  100. }
  101. return apply(ctx, root, tar.NewReader(r), options)
  102. }
  103. // applyNaive applies a tar stream of an OCI style diff tar.
  104. // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
  105. func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
  106. var (
  107. dirs []*tar.Header
  108. // Used for handling opaque directory markers which
  109. // may occur out of order
  110. unpackedPaths = make(map[string]struct{})
  111. // Used for aufs plink directory
  112. aufsTempdir = ""
  113. aufsHardlinks = make(map[string]*tar.Header)
  114. )
  115. // Iterate through the files in the archive.
  116. for {
  117. select {
  118. case <-ctx.Done():
  119. return 0, ctx.Err()
  120. default:
  121. }
  122. hdr, err := tr.Next()
  123. if err == io.EOF {
  124. // end of tar archive
  125. break
  126. }
  127. if err != nil {
  128. return 0, err
  129. }
  130. size += hdr.Size
  131. // Normalize name, for safety and for a simple is-root check
  132. hdr.Name = filepath.Clean(hdr.Name)
  133. accept, err := options.Filter(hdr)
  134. if err != nil {
  135. return 0, err
  136. }
  137. if !accept {
  138. continue
  139. }
  140. if skipFile(hdr) {
  141. log.G(ctx).Warnf("file %q ignored: archive may not be supported on system", hdr.Name)
  142. continue
  143. }
  144. // Split name and resolve symlinks for root directory.
  145. ppath, base := filepath.Split(hdr.Name)
  146. ppath, err = fs.RootPath(root, ppath)
  147. if err != nil {
  148. return 0, errors.Wrap(err, "failed to get root path")
  149. }
  150. // Join to root before joining to parent path to ensure relative links are
  151. // already resolved based on the root before adding to parent.
  152. path := filepath.Join(ppath, filepath.Join("/", base))
  153. if path == root {
  154. log.G(ctx).Debugf("file %q ignored: resolved to root", hdr.Name)
  155. continue
  156. }
  157. // If file is not directly under root, ensure parent directory
  158. // exists or is created.
  159. if ppath != root {
  160. parentPath := ppath
  161. if base == "" {
  162. parentPath = filepath.Dir(path)
  163. }
  164. if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
  165. err = mkdirAll(parentPath, 0700)
  166. if err != nil {
  167. return 0, err
  168. }
  169. }
  170. }
  171. // Skip AUFS metadata dirs
  172. if strings.HasPrefix(hdr.Name, whiteoutMetaPrefix) {
  173. // Regular files inside /.wh..wh.plnk can be used as hardlink targets
  174. // We don't want this directory, but we need the files in them so that
  175. // such hardlinks can be resolved.
  176. if strings.HasPrefix(hdr.Name, whiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
  177. basename := filepath.Base(hdr.Name)
  178. aufsHardlinks[basename] = hdr
  179. if aufsTempdir == "" {
  180. if aufsTempdir, err = ioutil.TempDir(os.Getenv("XDG_RUNTIME_DIR"), "dockerplnk"); err != nil {
  181. return 0, err
  182. }
  183. defer os.RemoveAll(aufsTempdir)
  184. }
  185. p, err := fs.RootPath(aufsTempdir, basename)
  186. if err != nil {
  187. return 0, err
  188. }
  189. if err := createTarFile(ctx, p, root, hdr, tr); err != nil {
  190. return 0, err
  191. }
  192. }
  193. if hdr.Name != whiteoutOpaqueDir {
  194. continue
  195. }
  196. }
  197. if strings.HasPrefix(base, whiteoutPrefix) {
  198. dir := filepath.Dir(path)
  199. if base == whiteoutOpaqueDir {
  200. _, err := os.Lstat(dir)
  201. if err != nil {
  202. return 0, err
  203. }
  204. err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
  205. if err != nil {
  206. if os.IsNotExist(err) {
  207. err = nil // parent was deleted
  208. }
  209. return err
  210. }
  211. if path == dir {
  212. return nil
  213. }
  214. if _, exists := unpackedPaths[path]; !exists {
  215. err := os.RemoveAll(path)
  216. return err
  217. }
  218. return nil
  219. })
  220. if err != nil {
  221. return 0, err
  222. }
  223. continue
  224. }
  225. originalBase := base[len(whiteoutPrefix):]
  226. originalPath := filepath.Join(dir, originalBase)
  227. // Ensure originalPath is under dir
  228. if dir[len(dir)-1] != filepath.Separator {
  229. dir += string(filepath.Separator)
  230. }
  231. if !strings.HasPrefix(originalPath, dir) {
  232. return 0, errors.Wrapf(errInvalidArchive, "invalid whiteout name: %v", base)
  233. }
  234. if err := os.RemoveAll(originalPath); err != nil {
  235. return 0, err
  236. }
  237. continue
  238. }
  239. // If path exits we almost always just want to remove and replace it.
  240. // The only exception is when it is a directory *and* the file from
  241. // the layer is also a directory. Then we want to merge them (i.e.
  242. // just apply the metadata from the layer).
  243. if fi, err := os.Lstat(path); err == nil {
  244. if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
  245. if err := os.RemoveAll(path); err != nil {
  246. return 0, err
  247. }
  248. }
  249. }
  250. srcData := io.Reader(tr)
  251. srcHdr := hdr
  252. // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
  253. // we manually retarget these into the temporary files we extracted them into
  254. if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), whiteoutLinkDir) {
  255. linkBasename := filepath.Base(hdr.Linkname)
  256. srcHdr = aufsHardlinks[linkBasename]
  257. if srcHdr == nil {
  258. return 0, fmt.Errorf("invalid aufs hardlink")
  259. }
  260. p, err := fs.RootPath(aufsTempdir, linkBasename)
  261. if err != nil {
  262. return 0, err
  263. }
  264. tmpFile, err := os.Open(p)
  265. if err != nil {
  266. return 0, err
  267. }
  268. defer tmpFile.Close()
  269. srcData = tmpFile
  270. }
  271. if err := createTarFile(ctx, path, root, srcHdr, srcData); err != nil {
  272. return 0, err
  273. }
  274. // Directory mtimes must be handled at the end to avoid further
  275. // file creation in them to modify the directory mtime
  276. if hdr.Typeflag == tar.TypeDir {
  277. dirs = append(dirs, hdr)
  278. }
  279. unpackedPaths[path] = struct{}{}
  280. }
  281. for _, hdr := range dirs {
  282. path, err := fs.RootPath(root, hdr.Name)
  283. if err != nil {
  284. return 0, err
  285. }
  286. if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
  287. return 0, err
  288. }
  289. }
  290. return size, nil
  291. }
  292. func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header, reader io.Reader) error {
  293. // hdr.Mode is in linux format, which we can use for syscalls,
  294. // but for os.Foo() calls we need the mode converted to os.FileMode,
  295. // so use hdrInfo.Mode() (they differ for e.g. setuid bits)
  296. hdrInfo := hdr.FileInfo()
  297. switch hdr.Typeflag {
  298. case tar.TypeDir:
  299. // Create directory unless it exists as a directory already.
  300. // In that case we just want to merge the two
  301. if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
  302. if err := mkdir(path, hdrInfo.Mode()); err != nil {
  303. return err
  304. }
  305. }
  306. case tar.TypeReg, tar.TypeRegA:
  307. file, err := openFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdrInfo.Mode())
  308. if err != nil {
  309. return err
  310. }
  311. _, err = copyBuffered(ctx, file, reader)
  312. if err1 := file.Close(); err == nil {
  313. err = err1
  314. }
  315. if err != nil {
  316. return err
  317. }
  318. case tar.TypeBlock, tar.TypeChar:
  319. // Handle this is an OS-specific way
  320. if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
  321. return err
  322. }
  323. case tar.TypeFifo:
  324. // Handle this is an OS-specific way
  325. if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
  326. return err
  327. }
  328. case tar.TypeLink:
  329. targetPath, err := hardlinkRootPath(extractDir, hdr.Linkname)
  330. if err != nil {
  331. return err
  332. }
  333. if err := os.Link(targetPath, path); err != nil {
  334. return err
  335. }
  336. case tar.TypeSymlink:
  337. if err := os.Symlink(hdr.Linkname, path); err != nil {
  338. return err
  339. }
  340. case tar.TypeXGlobalHeader:
  341. log.G(ctx).Debug("PAX Global Extended Headers found and ignored")
  342. return nil
  343. default:
  344. return errors.Errorf("unhandled tar header type %d\n", hdr.Typeflag)
  345. }
  346. // Lchown is not supported on Windows.
  347. if runtime.GOOS != "windows" {
  348. if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
  349. return err
  350. }
  351. }
  352. for key, value := range hdr.PAXRecords {
  353. if strings.HasPrefix(key, paxSchilyXattr) {
  354. key = key[len(paxSchilyXattr):]
  355. if err := setxattr(path, key, value); err != nil {
  356. if errors.Cause(err) == syscall.ENOTSUP {
  357. log.G(ctx).WithError(err).Warnf("ignored xattr %s in archive", key)
  358. continue
  359. }
  360. return err
  361. }
  362. }
  363. }
  364. // There is no LChmod, so ignore mode for symlink. Also, this
  365. // must happen after chown, as that can modify the file mode
  366. if err := handleLChmod(hdr, path, hdrInfo); err != nil {
  367. return err
  368. }
  369. return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
  370. }
  371. type changeWriter struct {
  372. tw *tar.Writer
  373. source string
  374. whiteoutT time.Time
  375. inodeSrc map[uint64]string
  376. inodeRefs map[uint64][]string
  377. addedDirs map[string]struct{}
  378. }
  379. func newChangeWriter(w io.Writer, source string) *changeWriter {
  380. return &changeWriter{
  381. tw: tar.NewWriter(w),
  382. source: source,
  383. whiteoutT: time.Now(),
  384. inodeSrc: map[uint64]string{},
  385. inodeRefs: map[uint64][]string{},
  386. addedDirs: map[string]struct{}{},
  387. }
  388. }
  389. func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, err error) error {
  390. if err != nil {
  391. return err
  392. }
  393. if k == fs.ChangeKindDelete {
  394. whiteOutDir := filepath.Dir(p)
  395. whiteOutBase := filepath.Base(p)
  396. whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
  397. hdr := &tar.Header{
  398. Typeflag: tar.TypeReg,
  399. Name: whiteOut[1:],
  400. Size: 0,
  401. ModTime: cw.whiteoutT,
  402. AccessTime: cw.whiteoutT,
  403. ChangeTime: cw.whiteoutT,
  404. }
  405. if err := cw.includeParents(hdr); err != nil {
  406. return err
  407. }
  408. if err := cw.tw.WriteHeader(hdr); err != nil {
  409. return errors.Wrap(err, "failed to write whiteout header")
  410. }
  411. } else {
  412. var (
  413. link string
  414. err error
  415. source = filepath.Join(cw.source, p)
  416. )
  417. switch {
  418. case f.Mode()&os.ModeSocket != 0:
  419. return nil // ignore sockets
  420. case f.Mode()&os.ModeSymlink != 0:
  421. if link, err = os.Readlink(source); err != nil {
  422. return err
  423. }
  424. }
  425. hdr, err := tar.FileInfoHeader(f, link)
  426. if err != nil {
  427. return err
  428. }
  429. hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
  430. name := p
  431. if strings.HasPrefix(name, string(filepath.Separator)) {
  432. name, err = filepath.Rel(string(filepath.Separator), name)
  433. if err != nil {
  434. return errors.Wrap(err, "failed to make path relative")
  435. }
  436. }
  437. name, err = tarName(name)
  438. if err != nil {
  439. return errors.Wrap(err, "cannot canonicalize path")
  440. }
  441. // suffix with '/' for directories
  442. if f.IsDir() && !strings.HasSuffix(name, "/") {
  443. name += "/"
  444. }
  445. hdr.Name = name
  446. if err := setHeaderForSpecialDevice(hdr, name, f); err != nil {
  447. return errors.Wrap(err, "failed to set device headers")
  448. }
  449. // additionalLinks stores file names which must be linked to
  450. // this file when this file is added
  451. var additionalLinks []string
  452. inode, isHardlink := fs.GetLinkInfo(f)
  453. if isHardlink {
  454. // If the inode has a source, always link to it
  455. if source, ok := cw.inodeSrc[inode]; ok {
  456. hdr.Typeflag = tar.TypeLink
  457. hdr.Linkname = source
  458. hdr.Size = 0
  459. } else {
  460. if k == fs.ChangeKindUnmodified {
  461. cw.inodeRefs[inode] = append(cw.inodeRefs[inode], name)
  462. return nil
  463. }
  464. cw.inodeSrc[inode] = name
  465. additionalLinks = cw.inodeRefs[inode]
  466. delete(cw.inodeRefs, inode)
  467. }
  468. } else if k == fs.ChangeKindUnmodified {
  469. // Nothing to write to diff
  470. return nil
  471. }
  472. if capability, err := getxattr(source, "security.capability"); err != nil {
  473. return errors.Wrap(err, "failed to get capabilities xattr")
  474. } else if capability != nil {
  475. if hdr.PAXRecords == nil {
  476. hdr.PAXRecords = map[string]string{}
  477. }
  478. hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability)
  479. }
  480. if err := cw.includeParents(hdr); err != nil {
  481. return err
  482. }
  483. if err := cw.tw.WriteHeader(hdr); err != nil {
  484. return errors.Wrap(err, "failed to write file header")
  485. }
  486. if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
  487. file, err := open(source)
  488. if err != nil {
  489. return errors.Wrapf(err, "failed to open path: %v", source)
  490. }
  491. defer file.Close()
  492. n, err := copyBuffered(context.TODO(), cw.tw, file)
  493. if err != nil {
  494. return errors.Wrap(err, "failed to copy")
  495. }
  496. if n != hdr.Size {
  497. return errors.New("short write copying file")
  498. }
  499. }
  500. if additionalLinks != nil {
  501. source = hdr.Name
  502. for _, extra := range additionalLinks {
  503. hdr.Name = extra
  504. hdr.Typeflag = tar.TypeLink
  505. hdr.Linkname = source
  506. hdr.Size = 0
  507. if err := cw.includeParents(hdr); err != nil {
  508. return err
  509. }
  510. if err := cw.tw.WriteHeader(hdr); err != nil {
  511. return errors.Wrap(err, "failed to write file header")
  512. }
  513. }
  514. }
  515. }
  516. return nil
  517. }
  518. func (cw *changeWriter) Close() error {
  519. if err := cw.tw.Close(); err != nil {
  520. return errors.Wrap(err, "failed to close tar writer")
  521. }
  522. return nil
  523. }
  524. func (cw *changeWriter) includeParents(hdr *tar.Header) error {
  525. name := strings.TrimRight(hdr.Name, "/")
  526. fname := filepath.Join(cw.source, name)
  527. parent := filepath.Dir(name)
  528. pname := filepath.Join(cw.source, parent)
  529. // Do not include root directory as parent
  530. if fname != cw.source && pname != cw.source {
  531. _, ok := cw.addedDirs[parent]
  532. if !ok {
  533. cw.addedDirs[parent] = struct{}{}
  534. fi, err := os.Stat(pname)
  535. if err != nil {
  536. return err
  537. }
  538. if err := cw.HandleChange(fs.ChangeKindModify, parent, fi, nil); err != nil {
  539. return err
  540. }
  541. }
  542. }
  543. if hdr.Typeflag == tar.TypeDir {
  544. cw.addedDirs[name] = struct{}{}
  545. }
  546. return nil
  547. }
  548. func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
  549. buf := bufPool.Get().(*[]byte)
  550. defer bufPool.Put(buf)
  551. for {
  552. select {
  553. case <-ctx.Done():
  554. err = ctx.Err()
  555. return
  556. default:
  557. }
  558. nr, er := src.Read(*buf)
  559. if nr > 0 {
  560. nw, ew := dst.Write((*buf)[0:nr])
  561. if nw > 0 {
  562. written += int64(nw)
  563. }
  564. if ew != nil {
  565. err = ew
  566. break
  567. }
  568. if nr != nw {
  569. err = io.ErrShortWrite
  570. break
  571. }
  572. }
  573. if er != nil {
  574. if er != io.EOF {
  575. err = er
  576. }
  577. break
  578. }
  579. }
  580. return written, err
  581. }
  582. // hardlinkRootPath returns target linkname, evaluating and bounding any
  583. // symlink to the parent directory.
  584. //
  585. // NOTE: Allow hardlink to the softlink, not the real one. For example,
  586. //
  587. // touch /tmp/zzz
  588. // ln -s /tmp/zzz /tmp/xxx
  589. // ln /tmp/xxx /tmp/yyy
  590. //
  591. // /tmp/yyy should be softlink which be same of /tmp/xxx, not /tmp/zzz.
  592. func hardlinkRootPath(root, linkname string) (string, error) {
  593. ppath, base := filepath.Split(linkname)
  594. ppath, err := fs.RootPath(root, ppath)
  595. if err != nil {
  596. return "", err
  597. }
  598. targetPath := filepath.Join(ppath, base)
  599. if !strings.HasPrefix(targetPath, root) {
  600. targetPath = root
  601. }
  602. return targetPath, nil
  603. }