changes_linux.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. package archive
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "sort"
  8. "syscall"
  9. "unsafe"
  10. "github.com/docker/docker/pkg/system"
  11. )
  12. // walker is used to implement collectFileInfoForChanges on linux. Where this
  13. // method in general returns the entire contents of two directory trees, we
  14. // optimize some FS calls out on linux. In particular, we take advantage of the
  15. // fact that getdents(2) returns the inode of each file in the directory being
  16. // walked, which, when walking two trees in parallel to generate a list of
  17. // changes, can be used to prune subtrees without ever having to lstat(2) them
  18. // directly. Eliminating stat calls in this way can save up to seconds on large
  19. // images.
  20. type walker struct {
  21. dir1 string
  22. dir2 string
  23. root1 *FileInfo
  24. root2 *FileInfo
  25. }
  26. // collectFileInfoForChanges returns a complete representation of the trees
  27. // rooted at dir1 and dir2, with one important exception: any subtree or
  28. // leaf where the inode and device numbers are an exact match between dir1
  29. // and dir2 will be pruned from the results. This method is *only* to be used
  30. // to generating a list of changes between the two directories, as it does not
  31. // reflect the full contents.
  32. func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) {
  33. w := &walker{
  34. dir1: dir1,
  35. dir2: dir2,
  36. root1: newRootFileInfo(),
  37. root2: newRootFileInfo(),
  38. }
  39. i1, err := os.Lstat(w.dir1)
  40. if err != nil {
  41. return nil, nil, err
  42. }
  43. i2, err := os.Lstat(w.dir2)
  44. if err != nil {
  45. return nil, nil, err
  46. }
  47. if err := w.walk("/", i1, i2); err != nil {
  48. return nil, nil, err
  49. }
  50. return w.root1, w.root2, nil
  51. }
  52. // Given a FileInfo, its path info, and a reference to the root of the tree
  53. // being constructed, register this file with the tree.
  54. func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error {
  55. if fi == nil {
  56. return nil
  57. }
  58. parent := root.LookUp(filepath.Dir(path))
  59. if parent == nil {
  60. return fmt.Errorf("collectFileInfoForChanges: Unexpectedly no parent for %s", path)
  61. }
  62. info := &FileInfo{
  63. name: filepath.Base(path),
  64. children: make(map[string]*FileInfo),
  65. parent: parent,
  66. }
  67. cpath := filepath.Join(dir, path)
  68. stat, err := system.FromStatT(fi.Sys().(*syscall.Stat_t))
  69. if err != nil {
  70. return err
  71. }
  72. info.stat = stat
  73. info.capability, _ = system.Lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access
  74. parent.children[info.name] = info
  75. return nil
  76. }
  77. // Walk a subtree rooted at the same path in both trees being iterated. For
  78. // example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d
  79. func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) {
  80. // Register these nodes with the return trees, unless we're still at the
  81. // (already-created) roots:
  82. if path != "/" {
  83. if err := walkchunk(path, i1, w.dir1, w.root1); err != nil {
  84. return err
  85. }
  86. if err := walkchunk(path, i2, w.dir2, w.root2); err != nil {
  87. return err
  88. }
  89. }
  90. is1Dir := i1 != nil && i1.IsDir()
  91. is2Dir := i2 != nil && i2.IsDir()
  92. sameDevice := false
  93. if i1 != nil && i2 != nil {
  94. si1 := i1.Sys().(*syscall.Stat_t)
  95. si2 := i2.Sys().(*syscall.Stat_t)
  96. if si1.Dev == si2.Dev {
  97. sameDevice = true
  98. }
  99. }
  100. // If these files are both non-existent, or leaves (non-dirs), we are done.
  101. if !is1Dir && !is2Dir {
  102. return nil
  103. }
  104. // Fetch the names of all the files contained in both directories being walked:
  105. var names1, names2 []nameIno
  106. if is1Dir {
  107. names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access
  108. if err != nil {
  109. return err
  110. }
  111. }
  112. if is2Dir {
  113. names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access
  114. if err != nil {
  115. return err
  116. }
  117. }
  118. // We have lists of the files contained in both parallel directories, sorted
  119. // in the same order. Walk them in parallel, generating a unique merged list
  120. // of all items present in either or both directories.
  121. var names []string
  122. ix1 := 0
  123. ix2 := 0
  124. for {
  125. if ix1 >= len(names1) {
  126. break
  127. }
  128. if ix2 >= len(names2) {
  129. break
  130. }
  131. ni1 := names1[ix1]
  132. ni2 := names2[ix2]
  133. switch bytes.Compare([]byte(ni1.name), []byte(ni2.name)) {
  134. case -1: // ni1 < ni2 -- advance ni1
  135. // we will not encounter ni1 in names2
  136. names = append(names, ni1.name)
  137. ix1++
  138. case 0: // ni1 == ni2
  139. if ni1.ino != ni2.ino || !sameDevice {
  140. names = append(names, ni1.name)
  141. }
  142. ix1++
  143. ix2++
  144. case 1: // ni1 > ni2 -- advance ni2
  145. // we will not encounter ni2 in names1
  146. names = append(names, ni2.name)
  147. ix2++
  148. }
  149. }
  150. for ix1 < len(names1) {
  151. names = append(names, names1[ix1].name)
  152. ix1++
  153. }
  154. for ix2 < len(names2) {
  155. names = append(names, names2[ix2].name)
  156. ix2++
  157. }
  158. // For each of the names present in either or both of the directories being
  159. // iterated, stat the name under each root, and recurse the pair of them:
  160. for _, name := range names {
  161. fname := filepath.Join(path, name)
  162. var cInfo1, cInfo2 os.FileInfo
  163. if is1Dir {
  164. cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access
  165. if err != nil && !os.IsNotExist(err) {
  166. return err
  167. }
  168. }
  169. if is2Dir {
  170. cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access
  171. if err != nil && !os.IsNotExist(err) {
  172. return err
  173. }
  174. }
  175. if err = w.walk(fname, cInfo1, cInfo2); err != nil {
  176. return err
  177. }
  178. }
  179. return nil
  180. }
  181. // {name,inode} pairs used to support the early-pruning logic of the walker type
  182. type nameIno struct {
  183. name string
  184. ino uint64
  185. }
  186. type nameInoSlice []nameIno
  187. func (s nameInoSlice) Len() int { return len(s) }
  188. func (s nameInoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  189. func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name }
  190. // readdirnames is a hacked-apart version of the Go stdlib code, exposing inode
  191. // numbers further up the stack when reading directory contents. Unlike
  192. // os.Readdirnames, which returns a list of filenames, this function returns a
  193. // list of {filename,inode} pairs.
  194. func readdirnames(dirname string) (names []nameIno, err error) {
  195. var (
  196. size = 100
  197. buf = make([]byte, 4096)
  198. nbuf int
  199. bufp int
  200. nb int
  201. )
  202. f, err := os.Open(dirname)
  203. if err != nil {
  204. return nil, err
  205. }
  206. defer f.Close()
  207. names = make([]nameIno, 0, size) // Empty with room to grow.
  208. for {
  209. // Refill the buffer if necessary
  210. if bufp >= nbuf {
  211. bufp = 0
  212. nbuf, err = syscall.ReadDirent(int(f.Fd()), buf) // getdents on linux
  213. if nbuf < 0 {
  214. nbuf = 0
  215. }
  216. if err != nil {
  217. return nil, os.NewSyscallError("readdirent", err)
  218. }
  219. if nbuf <= 0 {
  220. break // EOF
  221. }
  222. }
  223. // Drain the buffer
  224. nb, names = parseDirent(buf[bufp:nbuf], names)
  225. bufp += nb
  226. }
  227. sl := nameInoSlice(names)
  228. sort.Sort(sl)
  229. return sl, nil
  230. }
  231. // parseDirent is a minor modification of syscall.ParseDirent (linux version)
  232. // which returns {name,inode} pairs instead of just names.
  233. func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) {
  234. origlen := len(buf)
  235. for len(buf) > 0 {
  236. dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
  237. buf = buf[dirent.Reclen:]
  238. if dirent.Ino == 0 { // File absent in directory.
  239. continue
  240. }
  241. bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
  242. var name = string(bytes[0:clen(bytes[:])])
  243. if name == "." || name == ".." { // Useless names
  244. continue
  245. }
  246. names = append(names, nameIno{name, dirent.Ino})
  247. }
  248. return origlen - len(buf), names
  249. }
  250. func clen(n []byte) int {
  251. for i := 0; i < len(n); i++ {
  252. if n[i] == 0 {
  253. return i
  254. }
  255. }
  256. return len(n)
  257. }
  258. // OverlayChanges walks the path rw and determines changes for the files in the path,
  259. // with respect to the parent layers
  260. func OverlayChanges(layers []string, rw string) ([]Change, error) {
  261. return changes(layers, rw, overlayDeletedFile, nil)
  262. }
  263. func overlayDeletedFile(root, path string, fi os.FileInfo) (string, error) {
  264. if fi.Mode()&os.ModeCharDevice != 0 {
  265. s := fi.Sys().(*syscall.Stat_t)
  266. if major(uint64(s.Rdev)) == 0 && minor(uint64(s.Rdev)) == 0 {
  267. return path, nil
  268. }
  269. }
  270. if fi.Mode()&os.ModeDir != 0 {
  271. opaque, err := system.Lgetxattr(filepath.Join(root, path), "trusted.overlay.opaque")
  272. if err != nil {
  273. return "", err
  274. }
  275. if len(opaque) == 1 && opaque[0] == 'y' {
  276. return path, nil
  277. }
  278. }
  279. return "", nil
  280. }