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