github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/lib/filesystem/scanner/walk.go (about) 1 package scanner 2 3 import ( 4 "crypto/sha512" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "runtime" 11 "sort" 12 "syscall" 13 14 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 15 "github.com/Cloud-Foundations/Dominator/lib/filter" 16 "github.com/Cloud-Foundations/Dominator/lib/fsrateio" 17 "github.com/Cloud-Foundations/Dominator/lib/hash" 18 "github.com/Cloud-Foundations/Dominator/lib/wsyscall" 19 ) 20 21 var myCountGC int 22 23 func myGC() { 24 if myCountGC > 1000 { 25 runtime.GC() 26 myCountGC = 0 27 } 28 myCountGC++ 29 } 30 31 func makeRegularInode(stat *wsyscall.Stat_t) *filesystem.RegularInode { 32 var inode filesystem.RegularInode 33 inode.Mode = filesystem.FileMode(stat.Mode) 34 inode.Uid = stat.Uid 35 inode.Gid = stat.Gid 36 inode.MtimeSeconds = int64(stat.Mtim.Sec) 37 inode.MtimeNanoSeconds = int32(stat.Mtim.Nsec) 38 inode.Size = uint64(stat.Size) 39 return &inode 40 } 41 42 func makeSymlinkInode(stat *wsyscall.Stat_t) *filesystem.SymlinkInode { 43 var inode filesystem.SymlinkInode 44 inode.Uid = stat.Uid 45 inode.Gid = stat.Gid 46 return &inode 47 } 48 49 func makeSpecialInode(stat *wsyscall.Stat_t) *filesystem.SpecialInode { 50 var inode filesystem.SpecialInode 51 inode.Mode = filesystem.FileMode(stat.Mode) 52 inode.Uid = stat.Uid 53 inode.Gid = stat.Gid 54 inode.MtimeSeconds = int64(stat.Mtim.Sec) 55 inode.MtimeNanoSeconds = int32(stat.Mtim.Nsec) 56 inode.Rdev = stat.Rdev 57 return &inode 58 } 59 60 func scanFileSystem(rootDirectoryName string, 61 fsScanContext *fsrateio.ReaderContext, scanFilter *filter.Filter, 62 checkScanDisableRequest func() bool, hasher Hasher, oldFS *FileSystem) ( 63 *FileSystem, error) { 64 if checkScanDisableRequest != nil && checkScanDisableRequest() { 65 return nil, errors.New("DisableScan") 66 } 67 var fileSystem FileSystem 68 fileSystem.rootDirectoryName = rootDirectoryName 69 fileSystem.fsScanContext = fsScanContext 70 fileSystem.scanFilter = scanFilter 71 fileSystem.checkScanDisableRequest = checkScanDisableRequest 72 if hasher == nil { 73 fileSystem.hasher = GetSimpleHasher(false) 74 } else { 75 fileSystem.hasher = hasher 76 } 77 var stat wsyscall.Stat_t 78 if err := wsyscall.Lstat(rootDirectoryName, &stat); err != nil { 79 return nil, err 80 } 81 fileSystem.InodeTable = make(filesystem.InodeTable) 82 fileSystem.dev = stat.Dev 83 fileSystem.inodeNumber = stat.Ino 84 fileSystem.Mode = filesystem.FileMode(stat.Mode) 85 fileSystem.Uid = stat.Uid 86 fileSystem.Gid = stat.Gid 87 fileSystem.DirectoryCount++ 88 var tmpInode filesystem.RegularInode 89 if sha512.New().Size() != len(tmpInode.Hash) { 90 return nil, errors.New("incompatible hash size") 91 } 92 var oldDirectory *filesystem.DirectoryInode 93 if oldFS != nil && oldFS.InodeTable != nil { 94 oldDirectory = &oldFS.DirectoryInode 95 } 96 err, _ := scanDirectory(&fileSystem.FileSystem.DirectoryInode, oldDirectory, 97 &fileSystem, oldFS, "/") 98 oldFS = nil 99 if err != nil { 100 return nil, err 101 } 102 fileSystem.ComputeTotalDataBytes() 103 if err = fileSystem.RebuildInodePointers(); err != nil { 104 panic(err) 105 } 106 return &fileSystem, nil 107 } 108 109 func scanDirectory(directory, oldDirectory *filesystem.DirectoryInode, 110 fileSystem, oldFS *FileSystem, myPathName string) (error, bool) { 111 file, err := os.Open(path.Join(fileSystem.rootDirectoryName, myPathName)) 112 if err != nil { 113 return err, false 114 } 115 names, err := file.Readdirnames(-1) 116 file.Close() 117 if err != nil { 118 return err, false 119 } 120 sort.Strings(names) 121 entryList := make([]*filesystem.DirectoryEntry, 0, len(names)) 122 var copiedDirents int 123 for _, name := range names { 124 if directory == &fileSystem.DirectoryInode && name == ".subd" { 125 continue 126 } 127 filename := path.Join(myPathName, name) 128 if fileSystem.scanFilter != nil && 129 fileSystem.scanFilter.Match(filename) { 130 continue 131 } 132 var stat wsyscall.Stat_t 133 err := wsyscall.Lstat(path.Join(fileSystem.rootDirectoryName, filename), 134 &stat) 135 if err != nil { 136 if err == syscall.ENOENT { 137 continue 138 } 139 return err, false 140 } 141 if stat.Dev != fileSystem.dev { 142 continue 143 } 144 if fileSystem.checkScanDisableRequest != nil && 145 fileSystem.checkScanDisableRequest() { 146 return errors.New("DisableScan"), false 147 } 148 myGC() 149 dirent := new(filesystem.DirectoryEntry) 150 dirent.Name = name 151 dirent.InodeNumber = stat.Ino 152 var oldDirent *filesystem.DirectoryEntry 153 if oldDirectory != nil { 154 index := len(entryList) 155 if len(oldDirectory.EntryList) > index && 156 oldDirectory.EntryList[index].Name == name { 157 oldDirent = oldDirectory.EntryList[index] 158 } 159 } 160 if stat.Mode&syscall.S_IFMT == syscall.S_IFDIR { 161 err = addDirectory(dirent, oldDirent, fileSystem, oldFS, myPathName, 162 &stat) 163 } else if stat.Mode&syscall.S_IFMT == syscall.S_IFREG { 164 err = addRegularFile(dirent, fileSystem, oldFS, myPathName, &stat) 165 } else if stat.Mode&syscall.S_IFMT == syscall.S_IFLNK { 166 err = addSymlink(dirent, fileSystem, oldFS, myPathName, &stat) 167 } else if stat.Mode&syscall.S_IFMT == syscall.S_IFSOCK { 168 continue 169 } else { 170 err = addSpecialFile(dirent, fileSystem, oldFS, &stat) 171 } 172 if err != nil { 173 if err == syscall.ENOENT { 174 continue 175 } 176 return err, false 177 } 178 if oldDirent != nil && *dirent == *oldDirent { 179 dirent = oldDirent 180 copiedDirents++ 181 } 182 entryList = append(entryList, dirent) 183 } 184 if oldDirectory != nil && len(entryList) == copiedDirents && 185 len(entryList) == len(oldDirectory.EntryList) { 186 directory.EntryList = oldDirectory.EntryList 187 return nil, true 188 } else { 189 directory.EntryList = entryList 190 return nil, false 191 } 192 } 193 194 func addDirectory(dirent, oldDirent *filesystem.DirectoryEntry, 195 fileSystem, oldFS *FileSystem, 196 directoryPathName string, stat *wsyscall.Stat_t) error { 197 myPathName := path.Join(directoryPathName, dirent.Name) 198 if stat.Ino == fileSystem.inodeNumber { 199 return errors.New("recursive directory: " + myPathName) 200 } 201 if _, ok := fileSystem.InodeTable[stat.Ino]; ok { 202 return errors.New("hardlinked directory: " + myPathName) 203 } 204 inode := new(filesystem.DirectoryInode) 205 dirent.SetInode(inode) 206 fileSystem.InodeTable[stat.Ino] = inode 207 inode.Mode = filesystem.FileMode(stat.Mode) 208 inode.Uid = stat.Uid 209 inode.Gid = stat.Gid 210 var oldInode *filesystem.DirectoryInode 211 if oldDirent != nil { 212 if oi, ok := oldDirent.Inode().(*filesystem.DirectoryInode); ok { 213 oldInode = oi 214 } 215 } 216 err, copied := scanDirectory(inode, oldInode, fileSystem, oldFS, myPathName) 217 if err != nil { 218 return err 219 } 220 if copied && filesystem.CompareDirectoriesMetadata(inode, oldInode, nil) { 221 dirent.SetInode(oldInode) 222 fileSystem.InodeTable[stat.Ino] = oldInode 223 } 224 fileSystem.DirectoryCount++ 225 return nil 226 } 227 228 func addRegularFile(dirent *filesystem.DirectoryEntry, 229 fileSystem, oldFS *FileSystem, 230 directoryPathName string, stat *wsyscall.Stat_t) error { 231 if inode, ok := fileSystem.InodeTable[stat.Ino]; ok { 232 if inode, ok := inode.(*filesystem.RegularInode); ok { 233 dirent.SetInode(inode) 234 return nil 235 } 236 return errors.New("inode changed type: " + dirent.Name) 237 } 238 inode := makeRegularInode(stat) 239 if inode.Size > 0 { 240 err := scanRegularInode(inode, fileSystem, 241 path.Join(directoryPathName, dirent.Name)) 242 if err != nil { 243 return err 244 } 245 } 246 if oldFS != nil && oldFS.InodeTable != nil { 247 if oldInode, found := oldFS.InodeTable[stat.Ino]; found { 248 if oldInode, ok := oldInode.(*filesystem.RegularInode); ok { 249 if filesystem.CompareRegularInodes(inode, oldInode, nil) { 250 inode = oldInode 251 } 252 } 253 } 254 } 255 dirent.SetInode(inode) 256 fileSystem.InodeTable[stat.Ino] = inode 257 return nil 258 } 259 260 func addSymlink(dirent *filesystem.DirectoryEntry, 261 fileSystem, oldFS *FileSystem, 262 directoryPathName string, stat *wsyscall.Stat_t) error { 263 if inode, ok := fileSystem.InodeTable[stat.Ino]; ok { 264 if inode, ok := inode.(*filesystem.SymlinkInode); ok { 265 dirent.SetInode(inode) 266 return nil 267 } 268 return errors.New("inode changed type: " + dirent.Name) 269 } 270 inode := makeSymlinkInode(stat) 271 err := scanSymlinkInode(inode, fileSystem, 272 path.Join(directoryPathName, dirent.Name)) 273 if err != nil { 274 return err 275 } 276 if oldFS != nil && oldFS.InodeTable != nil { 277 if oldInode, found := oldFS.InodeTable[stat.Ino]; found { 278 if oldInode, ok := oldInode.(*filesystem.SymlinkInode); ok { 279 if filesystem.CompareSymlinkInodes(inode, oldInode, nil) { 280 inode = oldInode 281 } 282 } 283 } 284 } 285 dirent.SetInode(inode) 286 fileSystem.InodeTable[stat.Ino] = inode 287 return nil 288 } 289 290 func addSpecialFile(dirent *filesystem.DirectoryEntry, 291 fileSystem, oldFS *FileSystem, stat *wsyscall.Stat_t) error { 292 if inode, ok := fileSystem.InodeTable[stat.Ino]; ok { 293 if inode, ok := inode.(*filesystem.SpecialInode); ok { 294 dirent.SetInode(inode) 295 return nil 296 } 297 return errors.New("inode changed type: " + dirent.Name) 298 } 299 inode := makeSpecialInode(stat) 300 if oldFS != nil && oldFS.InodeTable != nil { 301 if oldInode, found := oldFS.InodeTable[stat.Ino]; found { 302 if oldInode, ok := oldInode.(*filesystem.SpecialInode); ok { 303 if filesystem.CompareSpecialInodes(inode, oldInode, nil) { 304 inode = oldInode 305 } 306 } 307 } 308 } 309 dirent.SetInode(inode) 310 fileSystem.InodeTable[stat.Ino] = inode 311 return nil 312 } 313 314 func (h simpleHasher) hash(reader io.Reader, length uint64) (hash.Hash, error) { 315 hasher := sha512.New() 316 var hashVal hash.Hash 317 nCopied, err := io.CopyN(hasher, reader, int64(length)) 318 if err != nil && err != io.EOF { 319 return hashVal, err 320 } 321 if nCopied != int64(length) { 322 if h { 323 // File changed length. Don't interrupt the scanning: return the 324 // zero hash and keep going. Hopefully next scan the file will be 325 // stable. 326 return hashVal, nil 327 } 328 return hashVal, fmt.Errorf("read: %d, expected: %d bytes", 329 nCopied, length) 330 } 331 copy(hashVal[:], hasher.Sum(nil)) 332 return hashVal, nil 333 } 334 335 func (h cpuLimitedHasher) hash(reader io.Reader, length uint64) ( 336 hash.Hash, error) { 337 h.limiter.Limit() 338 return h.hasher.Hash(reader, length) 339 } 340 341 func scanRegularInode(inode *filesystem.RegularInode, fileSystem *FileSystem, 342 myPathName string) error { 343 f, err := os.Open(path.Join(fileSystem.rootDirectoryName, myPathName)) 344 if err != nil { 345 return err 346 } 347 defer f.Close() 348 reader := io.Reader(f) 349 if fileSystem.fsScanContext != nil { 350 reader = fileSystem.fsScanContext.NewReader(f) 351 } 352 inode.Hash, err = fileSystem.hasher.Hash(reader, inode.Size) 353 if err != nil { 354 return fmt.Errorf("scanRegularInode(%s): %s", myPathName, err) 355 } 356 return nil 357 } 358 359 func scanSymlinkInode(inode *filesystem.SymlinkInode, fileSystem *FileSystem, 360 myPathName string) error { 361 target, err := os.Readlink(path.Join(fileSystem.rootDirectoryName, 362 myPathName)) 363 if err != nil { 364 return err 365 } 366 inode.Symlink = target 367 return nil 368 }