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  }