changes_linux.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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. // If these files are both non-existent, or leaves (non-dirs), we are done.
  93. if !is1Dir && !is2Dir {
  94. return nil
  95. }
  96. // Fetch the names of all the files contained in both directories being walked:
  97. var names1, names2 []nameIno
  98. if is1Dir {
  99. names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access
  100. if err != nil {
  101. return err
  102. }
  103. }
  104. if is2Dir {
  105. names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access
  106. if err != nil {
  107. return err
  108. }
  109. }
  110. // We have lists of the files contained in both parallel directories, sorted
  111. // in the same order. Walk them in parallel, generating a unique merged list
  112. // of all items present in either or both directories.
  113. var names []string
  114. ix1 := 0
  115. ix2 := 0
  116. for {
  117. if ix1 >= len(names1) {
  118. break
  119. }
  120. if ix2 >= len(names2) {
  121. break
  122. }
  123. ni1 := names1[ix1]
  124. ni2 := names2[ix2]
  125. switch bytes.Compare([]byte(ni1.name), []byte(ni2.name)) {
  126. case -1: // ni1 < ni2 -- advance ni1
  127. // we will not encounter ni1 in names2
  128. names = append(names, ni1.name)
  129. ix1++
  130. case 0: // ni1 == ni2
  131. if ni1.ino != ni2.ino {
  132. names = append(names, ni1.name)
  133. }
  134. ix1++
  135. ix2++
  136. case 1: // ni1 > ni2 -- advance ni2
  137. // we will not encounter ni2 in names1
  138. names = append(names, ni2.name)
  139. ix2++
  140. }
  141. }
  142. for ix1 < len(names1) {
  143. names = append(names, names1[ix1].name)
  144. ix1++
  145. }
  146. for ix2 < len(names2) {
  147. names = append(names, names2[ix2].name)
  148. ix2++
  149. }
  150. // For each of the names present in either or both of the directories being
  151. // iterated, stat the name under each root, and recurse the pair of them:
  152. for _, name := range names {
  153. fname := filepath.Join(path, name)
  154. var cInfo1, cInfo2 os.FileInfo
  155. if is1Dir {
  156. cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access
  157. if err != nil && !os.IsNotExist(err) {
  158. return err
  159. }
  160. }
  161. if is2Dir {
  162. cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access
  163. if err != nil && !os.IsNotExist(err) {
  164. return err
  165. }
  166. }
  167. if err = w.walk(fname, cInfo1, cInfo2); err != nil {
  168. return err
  169. }
  170. }
  171. return nil
  172. }
  173. // {name,inode} pairs used to support the early-pruning logic of the walker type
  174. type nameIno struct {
  175. name string
  176. ino uint64
  177. }
  178. type nameInoSlice []nameIno
  179. func (s nameInoSlice) Len() int { return len(s) }
  180. func (s nameInoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  181. func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name }
  182. // readdirnames is a hacked-apart version of the Go stdlib code, exposing inode
  183. // numbers further up the stack when reading directory contents. Unlike
  184. // os.Readdirnames, which returns a list of filenames, this function returns a
  185. // list of {filename,inode} pairs.
  186. func readdirnames(dirname string) (names []nameIno, err error) {
  187. var (
  188. size = 100
  189. buf = make([]byte, 4096)
  190. nbuf int
  191. bufp int
  192. nb int
  193. )
  194. f, err := os.Open(dirname)
  195. if err != nil {
  196. return nil, err
  197. }
  198. defer f.Close()
  199. names = make([]nameIno, 0, size) // Empty with room to grow.
  200. for {
  201. // Refill the buffer if necessary
  202. if bufp >= nbuf {
  203. bufp = 0
  204. nbuf, err = syscall.ReadDirent(int(f.Fd()), buf) // getdents on linux
  205. if nbuf < 0 {
  206. nbuf = 0
  207. }
  208. if err != nil {
  209. return nil, os.NewSyscallError("readdirent", err)
  210. }
  211. if nbuf <= 0 {
  212. break // EOF
  213. }
  214. }
  215. // Drain the buffer
  216. nb, names = parseDirent(buf[bufp:nbuf], names)
  217. bufp += nb
  218. }
  219. sl := nameInoSlice(names)
  220. sort.Sort(sl)
  221. return sl, nil
  222. }
  223. // parseDirent is a minor modification of syscall.ParseDirent (linux version)
  224. // which returns {name,inode} pairs instead of just names.
  225. func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) {
  226. origlen := len(buf)
  227. for len(buf) > 0 {
  228. dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
  229. buf = buf[dirent.Reclen:]
  230. if dirent.Ino == 0 { // File absent in directory.
  231. continue
  232. }
  233. bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
  234. var name = string(bytes[0:clen(bytes[:])])
  235. if name == "." || name == ".." { // Useless names
  236. continue
  237. }
  238. names = append(names, nameIno{name, dirent.Ino})
  239. }
  240. return origlen - len(buf), names
  241. }
  242. func clen(n []byte) int {
  243. for i := 0; i < len(n); i++ {
  244. if n[i] == 0 {
  245. return i
  246. }
  247. }
  248. return len(n)
  249. }