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