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  }