github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/synchronization/endpoint/local/endpoint.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"hash"
     8  	"io"
     9  	"path/filepath"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/mutagen-io/mutagen/pkg/encoding"
    14  	"github.com/mutagen-io/mutagen/pkg/filesystem"
    15  	"github.com/mutagen-io/mutagen/pkg/filesystem/behavior"
    16  	"github.com/mutagen-io/mutagen/pkg/filesystem/watching"
    17  	"github.com/mutagen-io/mutagen/pkg/logging"
    18  	"github.com/mutagen-io/mutagen/pkg/sidecar"
    19  	"github.com/mutagen-io/mutagen/pkg/state"
    20  	"github.com/mutagen-io/mutagen/pkg/synchronization"
    21  	"github.com/mutagen-io/mutagen/pkg/synchronization/core"
    22  	"github.com/mutagen-io/mutagen/pkg/synchronization/core/fastpath"
    23  	"github.com/mutagen-io/mutagen/pkg/synchronization/core/ignore"
    24  	dockerignore "github.com/mutagen-io/mutagen/pkg/synchronization/core/ignore/docker"
    25  	mutagenignore "github.com/mutagen-io/mutagen/pkg/synchronization/core/ignore/mutagen"
    26  	"github.com/mutagen-io/mutagen/pkg/synchronization/endpoint/local/staging"
    27  	"github.com/mutagen-io/mutagen/pkg/synchronization/rsync"
    28  	"github.com/mutagen-io/mutagen/pkg/timeutil"
    29  )
    30  
    31  const (
    32  	// pollSignalCoalescingWindow is the time interval over which triggering of
    33  	// the polling channel will be coalesced.
    34  	pollSignalCoalescingWindow = 20 * time.Millisecond
    35  	// minimumCacheSaveInterval is the minimum interval at which caches are
    36  	// written to disk asynchronously.
    37  	minimumCacheSaveInterval = 60 * time.Second
    38  	// watchPollScanSignalCoalescingWindow is the time interval over which
    39  	// triggering of scan operations by the non-recursive watch in watchPoll
    40  	// will be coalesced.
    41  	watchPollScanSignalCoalescingWindow = 10 * time.Millisecond
    42  )
    43  
    44  // reifiedWatchMode describes a fully reified watch mode based on the watch mode
    45  // specified for the endpoint and the availability of modes on the system.
    46  type reifiedWatchMode uint8
    47  
    48  const (
    49  	// reifiedWatchModeDisabled indicates that watching has been disabled.
    50  	reifiedWatchModeDisabled reifiedWatchMode = iota
    51  	// reifiedWatchModePoll indicates poll-based watching is in use.
    52  	reifiedWatchModePoll
    53  	// reifiedWatchModeRecursive indicates that recursive watching is in use.
    54  	reifiedWatchModeRecursive
    55  )
    56  
    57  // endpoint provides a local, in-memory implementation of
    58  // synchronization.Endpoint for local files.
    59  type endpoint struct {
    60  	// logger is the underlying logger. This field is static and thus safe for
    61  	// concurrent usage.
    62  	logger *logging.Logger
    63  	// root is the synchronization root. This field is static and thus safe for
    64  	// concurrent reads.
    65  	root string
    66  	// readOnly determines whether or not the endpoint should be operating in a
    67  	// read-only mode (i.e. it is the source of unidirectional synchronization).
    68  	// This field is static and thus safe for concurrent reads.
    69  	readOnly bool
    70  	// maximumEntryCount is the maximum number of entries that the endpoint will
    71  	// synchronize. This field is static and thus safe for concurrent reads.
    72  	maximumEntryCount uint64
    73  	// watchMode indicates the watch mode being used. This field is static and
    74  	// thus safe for concurrent reads.
    75  	watchMode reifiedWatchMode
    76  	// accelerationAllowed indicates whether or not scan acceleration is
    77  	// allowed. This field is static and thus safe for concurrent reads.
    78  	accelerationAllowed bool
    79  	// probeMode is the probe mode. This field is static and thus safe for
    80  	// concurrent reads.
    81  	probeMode behavior.ProbeMode
    82  	// symbolicLinkMode is the symbolic link mode. This field is static and thus
    83  	// safe for concurrent reads.
    84  	symbolicLinkMode core.SymbolicLinkMode
    85  	// permissionsMode is the permissions mode. This field is static and thus
    86  	// safe for concurrent reads.
    87  	permissionsMode core.PermissionsMode
    88  	// defaultFileMode is the default file permission mode to use in "portable"
    89  	// permission propagation. This field is static and thus safe for concurrent
    90  	// reads.
    91  	defaultFileMode filesystem.Mode
    92  	// defaultDirectoryMode is the default directory permission mode to use in
    93  	// "portable" permission propagation. This field is static and thus safe for
    94  	// concurrent reads.
    95  	defaultDirectoryMode filesystem.Mode
    96  	// defaultOwnership is the default ownership specification to use in
    97  	// "portable" permission propagation. This field is static and thus safe for
    98  	// concurrent reads.
    99  	defaultOwnership *filesystem.OwnershipSpecification
   100  	// workerCancel cancels any background worker Goroutines for the endpoint.
   101  	// This field is static and thus safe for concurrent invocation.
   102  	workerCancel context.CancelFunc
   103  	// saveCacheSignal is used to signal to the cache saving Goroutine that a
   104  	// cache save operation should occur. It is buffered with a capacity of 1
   105  	// and should be written to in a non-blocking fashion. It is never closed.
   106  	// This field is static and thus safe for concurrent usage.
   107  	saveCacheSignal chan<- struct{}
   108  	// saveCacheDone is closed when the cache saving Goroutine has completed. It
   109  	// will never have values written to it and will only be closed, so a
   110  	// receive that returns indicates closure. This field is static and thus
   111  	// safe for concurrent receive operations.
   112  	saveCacheDone <-chan struct{}
   113  	// watchDone is closed when the watching Goroutine has completed. It will
   114  	// never have values written to it and will only be closed, so a receive
   115  	// that returns indicates closure. This field is static and thus safe for
   116  	// concurrent receive operations.
   117  	watchDone <-chan struct{}
   118  	// pollSignal is the coalescer used to signal Poll callers. This field is
   119  	// static and thus safe for concurrent usage.
   120  	pollSignal *state.Coalescer
   121  	// recursiveWatchRetryEstablish is a channel used by Transition to signal to
   122  	// the recursive watching Goroutine (if any) that it should try to
   123  	// re-establish watching. It is a non-buffered channel, with reads only
   124  	// occurring when the recursive watching Goroutine is waiting to retry watch
   125  	// establishment and writes only occurring in a non-blocking fashion
   126  	// (meaning this is a best-effort signaling mechanism (with a fallback to a
   127  	// timer-based signal)). This field is static and never closed, and is thus
   128  	// safe for concurrent send operations.
   129  	recursiveWatchRetryEstablish chan struct{}
   130  	// scanLock serializes access to accelerate, recheckPaths, snapshot, hasher,
   131  	// cache, ignorer, ignoreCache, cacheWriteError, and lastScanEntryCount.
   132  	// This lock is not required by the Endpoint interface (which doesn't permit
   133  	// concurrent usage), but rather the endpoint's background worker Goroutines
   134  	// for cache saving and filesystem watching. This lock notably excludes
   135  	// coverage of scannedSinceLastStageCall, scannedSinceLastTransitionCall,
   136  	// lastReturnedScanCache, lastReturnedScanSnapshotDecomposesUnicode, which
   137  	// are only updated by Scan and read by Stage and Transition, thus making
   138  	// them safe under Endpoint's (non-concurrent) interface.
   139  	//
   140  	// Instead of being implemented as a mutex, this lock is implemented as a
   141  	// semaphore, allowing for preemption when waiting on its acquisition. This
   142  	// can be useful (for example) in Scan calls that might be blocked waiting
   143  	// for an initial accelerated watching scan to complete.
   144  	//
   145  	// Concretely, this lock is implemented as a channel with a single element
   146  	// that must be held for the holder to be considered in control of the lock.
   147  	// The lockScanLock and unlockScanLock methods should be used to manage
   148  	// control of the lock.
   149  	scanLock chan struct{}
   150  	// accelerate indicates that the Scan function should attempt to accelerate
   151  	// scanning by using data from a background watcher Goroutine.
   152  	accelerate bool
   153  	// recheckPaths is the set of re-check paths to use when accelerating scans
   154  	// in recursive watching mode. This map will be non-nil if and only if
   155  	// accelerate is true and recursive watching is being used.
   156  	recheckPaths map[string]bool
   157  	// snapshot is the snapshot from the last scan.
   158  	snapshot *core.Snapshot
   159  	// hasher is the hasher used for scans.
   160  	hasher hash.Hash
   161  	// cache is the cache from the last successful scan on the endpoint.
   162  	cache *core.Cache
   163  	// ignorer is the ignorer to use for scans.
   164  	ignorer ignore.Ignorer
   165  	// ignoreCache is the ignore cache from the last successful scan on the
   166  	// endpoint.
   167  	ignoreCache ignore.IgnoreCache
   168  	// cacheWriteError is the last error encountered when trying to write the
   169  	// cache to disk, if any.
   170  	cacheWriteError error
   171  	// lastScanEntryCount is the entry count at the time of the last scan.
   172  	lastScanEntryCount uint64
   173  	// scannedSinceLastStageCall tracks whether or not a scan operation has
   174  	// occurred since the last staging operation.
   175  	scannedSinceLastStageCall bool
   176  	// scannedSinceLastTransitionCall tracks whether or not a scan operation has
   177  	// occurred since the last transitioning operation.
   178  	scannedSinceLastTransitionCall bool
   179  	// lastReturnedScanCache is the cache corresponding to the last snapshot
   180  	// returned by Scan. This may be different than cache and is tracked
   181  	// separately because Transition (in order to function correctly) requires
   182  	// the cache corresponding to the snapshot that resulted in its operations.
   183  	lastReturnedScanCache *core.Cache
   184  	// lastReturnedScanSnapshotDecomposesUnicode is the value of
   185  	// DecomposesUnicode from the last snapshot returned by Scan. Despite very
   186  	// likely being the same as the value in the current snapshot, it needs to
   187  	// be tracked separately for the same reasons as lastReturnedScanCache.
   188  	lastReturnedScanSnapshotDecomposesUnicode bool
   189  	// stager is the staging coordinator. It is not safe for concurrent usage,
   190  	// but since Endpoint doesn't allow concurrent usage, we know that the
   191  	// stager will only be used in at most one of Stage or Transition methods at
   192  	// any given time.
   193  	stager stager
   194  }
   195  
   196  // NewEndpoint creates a new local endpoint instance using the specified session
   197  // metadata and options.
   198  func NewEndpoint(
   199  	logger *logging.Logger,
   200  	root string,
   201  	sessionIdentifier string,
   202  	version synchronization.Version,
   203  	configuration *synchronization.Configuration,
   204  	alpha bool,
   205  ) (synchronization.Endpoint, error) {
   206  	// Determine if the endpoint is running in a read-only mode.
   207  	synchronizationMode := configuration.SynchronizationMode
   208  	if synchronizationMode.IsDefault() {
   209  		synchronizationMode = version.DefaultSynchronizationMode()
   210  	}
   211  	unidirectional := synchronizationMode == core.SynchronizationMode_SynchronizationModeOneWaySafe ||
   212  		synchronizationMode == core.SynchronizationMode_SynchronizationModeOneWayReplica
   213  	readOnly := alpha && unidirectional
   214  
   215  	// Compute the effective hashing algorithm and create the hasher factory.
   216  	hashingAlgorithm := configuration.HashingAlgorithm
   217  	if hashingAlgorithm.IsDefault() {
   218  		hashingAlgorithm = version.DefaultHashingAlgorithm()
   219  	}
   220  	hasherFactory := hashingAlgorithm.Factory()
   221  
   222  	// Determine the maximum entry count.
   223  	maximumEntryCount := configuration.MaximumEntryCount
   224  	if maximumEntryCount == 0 {
   225  		maximumEntryCount = version.DefaultMaximumEntryCount()
   226  	}
   227  
   228  	// Determine the maximum staging file size.
   229  	maximumStagingFileSize := configuration.MaximumStagingFileSize
   230  	if maximumStagingFileSize == 0 {
   231  		maximumStagingFileSize = version.DefaultMaximumStagingFileSize()
   232  	}
   233  
   234  	// Compute the effective watch mode.
   235  	watchMode := configuration.WatchMode
   236  	if watchMode.IsDefault() {
   237  		watchMode = version.DefaultWatchMode()
   238  	}
   239  
   240  	// Compute the actual (reified) watch mode.
   241  	var actualWatchMode reifiedWatchMode
   242  	var nonRecursiveWatchingAllowed bool
   243  	if watchMode == synchronization.WatchMode_WatchModePortable {
   244  		if watching.RecursiveWatchingSupported {
   245  			actualWatchMode = reifiedWatchModeRecursive
   246  		} else {
   247  			actualWatchMode = reifiedWatchModePoll
   248  			nonRecursiveWatchingAllowed = true
   249  		}
   250  	} else if watchMode == synchronization.WatchMode_WatchModeForcePoll {
   251  		actualWatchMode = reifiedWatchModePoll
   252  	} else if watchMode == synchronization.WatchMode_WatchModeNoWatch {
   253  		actualWatchMode = reifiedWatchModeDisabled
   254  	} else {
   255  		panic("unhandled watch mode")
   256  	}
   257  
   258  	// Compute the effective scan mode and determine whether or not scan
   259  	// acceleration is allowed.
   260  	scanMode := configuration.ScanMode
   261  	if scanMode.IsDefault() {
   262  		scanMode = version.DefaultScanMode()
   263  	}
   264  	accelerationAllowed := scanMode == synchronization.ScanMode_ScanModeAccelerated
   265  
   266  	// Compute the effective probe mode.
   267  	probeMode := configuration.ProbeMode
   268  	if probeMode.IsDefault() {
   269  		probeMode = version.DefaultProbeMode()
   270  	}
   271  
   272  	// Compute the effective symbolic link mode.
   273  	symbolicLinkMode := configuration.SymbolicLinkMode
   274  	if symbolicLinkMode.IsDefault() {
   275  		symbolicLinkMode = version.DefaultSymbolicLinkMode()
   276  	}
   277  
   278  	// Compute the effective ignore syntax.
   279  	ignoreSyntax := configuration.IgnoreSyntax
   280  	if ignoreSyntax.IsDefault() {
   281  		ignoreSyntax = version.DefaultIgnoreSyntax()
   282  	}
   283  
   284  	// Compute a combined ignore list and create the ignorer.
   285  	var ignores []string
   286  	ignores = append(ignores, configuration.DefaultIgnores...)
   287  	ignores = append(ignores, configuration.Ignores...)
   288  	var ignorer ignore.Ignorer
   289  	if ignoreSyntax == ignore.Syntax_SyntaxMutagen {
   290  		if i, err := mutagenignore.NewIgnorer(ignores); err != nil {
   291  			return nil, fmt.Errorf("unable to create Mutagen-style ignorer: %w", err)
   292  		} else {
   293  			ignorer = i
   294  		}
   295  	} else if ignoreSyntax == ignore.Syntax_SyntaxDocker {
   296  		if i, err := dockerignore.NewIgnorer(ignores); err != nil {
   297  			return nil, fmt.Errorf("unable to create Docker-style ignorer: %w", err)
   298  		} else {
   299  			ignorer = i
   300  		}
   301  	} else {
   302  		panic("unhandled ignore syntax")
   303  	}
   304  
   305  	// Compute the effective VCS ignore mode and add VCS ignores if necessary.
   306  	ignoreVCSMode := configuration.IgnoreVCSMode
   307  	if ignoreVCSMode.IsDefault() {
   308  		ignoreVCSMode = version.DefaultIgnoreVCSMode()
   309  	}
   310  	if ignoreVCSMode == ignore.IgnoreVCSMode_IgnoreVCSModeIgnore {
   311  		ignorer = ignore.IgnoreVCS(ignorer)
   312  	}
   313  
   314  	// Track whether or not any non-default ownership or directory permissions
   315  	// are set. We don't care about non-default file permissions since we're
   316  	// only tracking this to set volume root ownership and permissions in
   317  	// sidecar containers.
   318  	var nonDefaultOwnershipOrDirectoryPermissionsSet bool
   319  
   320  	// Compute the effective permissions mode.
   321  	permissionsMode := configuration.PermissionsMode
   322  	if permissionsMode.IsDefault() {
   323  		permissionsMode = version.DefaultPermissionsMode()
   324  	}
   325  
   326  	// Compute the effective default file mode.
   327  	defaultFileMode := filesystem.Mode(configuration.DefaultFileMode)
   328  	if defaultFileMode == 0 {
   329  		defaultFileMode = version.DefaultFileMode()
   330  	}
   331  
   332  	// Compute the effective default directory mode.
   333  	defaultDirectoryMode := filesystem.Mode(configuration.DefaultDirectoryMode)
   334  	if defaultDirectoryMode == 0 {
   335  		defaultDirectoryMode = version.DefaultDirectoryMode()
   336  	} else {
   337  		nonDefaultOwnershipOrDirectoryPermissionsSet = true
   338  	}
   339  
   340  	// Compute the effective owner specification.
   341  	defaultOwnerSpecification := configuration.DefaultOwner
   342  	if defaultOwnerSpecification == "" {
   343  		defaultOwnerSpecification = version.DefaultOwnerSpecification()
   344  	} else {
   345  		nonDefaultOwnershipOrDirectoryPermissionsSet = true
   346  	}
   347  
   348  	// Compute the effective owner group specification.
   349  	defaultGroupSpecification := configuration.DefaultGroup
   350  	if defaultGroupSpecification == "" {
   351  		defaultGroupSpecification = version.DefaultGroupSpecification()
   352  	} else {
   353  		nonDefaultOwnershipOrDirectoryPermissionsSet = true
   354  	}
   355  
   356  	// Compute the effective ownership specification.
   357  	defaultOwnership, err := filesystem.NewOwnershipSpecification(
   358  		defaultOwnerSpecification,
   359  		defaultGroupSpecification,
   360  	)
   361  	if err != nil {
   362  		return nil, fmt.Errorf("unable to create ownership specification: %w", err)
   363  	}
   364  
   365  	// Compute the cache path if this isn't an ephemeral endpoint.
   366  	cachePath, err := pathForCache(sessionIdentifier, alpha)
   367  	if err != nil {
   368  		return nil, fmt.Errorf("unable to compute/create cache path: %w", err)
   369  	}
   370  
   371  	// Load any existing cache. If it fails to load or validate, just replace it
   372  	// with an empty one.
   373  	// TODO: Should we let validation errors bubble up? They may be indicative
   374  	// of something bad.
   375  	cache := &core.Cache{}
   376  	if encoding.LoadAndUnmarshalProtobuf(cachePath, cache) != nil {
   377  		cache = &core.Cache{}
   378  	} else if cache.EnsureValid() != nil {
   379  		cache = &core.Cache{}
   380  	}
   381  
   382  	// Check if this endpoint is running inside a sidecar container and, if so,
   383  	// whether or not the root exists beneath a volume mount point (which it
   384  	// almost certainly does, but that's not guaranteed). We track the latter
   385  	// condition by whether or not sidecarVolumeMountPoint is non-empty.
   386  	var sidecarVolumeMountPoint string
   387  	if sidecar.EnvironmentIsSidecar() {
   388  		sidecarVolumeMountPoint = sidecar.VolumeMountPointForPath(root)
   389  	}
   390  
   391  	// Compute the effective staging mode. If no mode has been explicitly set
   392  	// and the synchronization root is a volume mount point in a Mutagen sidecar
   393  	// container, then use internal staging for better performance. Otherwise,
   394  	// use either the explicitly specified staging mode or the default staging
   395  	// mode.
   396  	stageMode := configuration.StageMode
   397  	var useSidecarVolumeMountPointAsInternalStagingRoot bool
   398  	if stageMode.IsDefault() {
   399  		if sidecarVolumeMountPoint != "" {
   400  			stageMode = synchronization.StageMode_StageModeInternal
   401  			useSidecarVolumeMountPointAsInternalStagingRoot = true
   402  		} else {
   403  			stageMode = version.DefaultStageMode()
   404  		}
   405  	}
   406  
   407  	// Compute the staging root path and whether or not it should be hidden.
   408  	var stagingRoot string
   409  	var hideStagingRoot bool
   410  	if stageMode == synchronization.StageMode_StageModeMutagen {
   411  		stagingRoot, err = pathForMutagenStagingRoot(sessionIdentifier, alpha)
   412  	} else if stageMode == synchronization.StageMode_StageModeNeighboring {
   413  		stagingRoot, err = pathForNeighboringStagingRoot(root, sessionIdentifier, alpha)
   414  		hideStagingRoot = true
   415  	} else if stageMode == synchronization.StageMode_StageModeInternal {
   416  		if useSidecarVolumeMountPointAsInternalStagingRoot {
   417  			stagingRoot, err = pathForInternalStagingRoot(sidecarVolumeMountPoint, sessionIdentifier, alpha)
   418  		} else {
   419  			stagingRoot, err = pathForInternalStagingRoot(root, sessionIdentifier, alpha)
   420  		}
   421  		hideStagingRoot = true
   422  	} else {
   423  		panic("unhandled staging mode")
   424  	}
   425  	if err != nil {
   426  		return nil, fmt.Errorf("unable to compute staging root: %w", err)
   427  	}
   428  
   429  	// HACK: If non-default ownership or permissions have been set and the
   430  	// synchronization root is a volume mount point in a Mutagen sidecar
   431  	// container with no pre-existing content, then set the ownership and
   432  	// permissions of the synchronization root to match those of the session.
   433  	// This is a heuristic to work around the fact that Docker volumes don't
   434  	// allow ownership specification at creation time, either via the command
   435  	// line or Compose.
   436  	// TODO: Should this be restricted to Linux containers?
   437  	if nonDefaultOwnershipOrDirectoryPermissionsSet && root == sidecarVolumeMountPoint {
   438  		if err := sidecar.SetVolumeOwnershipAndPermissionsIfEmpty(
   439  			filepath.Base(sidecarVolumeMountPoint),
   440  			defaultOwnership,
   441  			defaultDirectoryMode,
   442  		); err != nil {
   443  			return nil, fmt.Errorf("unable to set ownership and permissions for sidecar volume: %w", err)
   444  		}
   445  	}
   446  
   447  	// Create a cancellable context in which the endpoint's background worker
   448  	// Goroutines will operate.
   449  	workerCtx, workerCancel := context.WithCancel(context.Background())
   450  
   451  	// Create channels to signal and track the cache saving Goroutine.
   452  	saveCacheSignal := make(chan struct{}, 1)
   453  	saveCacheDone := make(chan struct{})
   454  
   455  	// Create a channel to track the watch Goroutine.
   456  	watchDone := make(chan struct{})
   457  
   458  	// Create the scan lock.
   459  	scanLock := make(chan struct{}, 1)
   460  	scanLock <- struct{}{}
   461  
   462  	// Create the endpoint.
   463  	endpoint := &endpoint{
   464  		logger:                       logger,
   465  		root:                         root,
   466  		readOnly:                     readOnly,
   467  		maximumEntryCount:            maximumEntryCount,
   468  		watchMode:                    actualWatchMode,
   469  		accelerationAllowed:          accelerationAllowed,
   470  		probeMode:                    probeMode,
   471  		symbolicLinkMode:             symbolicLinkMode,
   472  		permissionsMode:              permissionsMode,
   473  		defaultFileMode:              defaultFileMode,
   474  		defaultDirectoryMode:         defaultDirectoryMode,
   475  		defaultOwnership:             defaultOwnership,
   476  		workerCancel:                 workerCancel,
   477  		saveCacheSignal:              saveCacheSignal,
   478  		saveCacheDone:                saveCacheDone,
   479  		watchDone:                    watchDone,
   480  		pollSignal:                   state.NewCoalescer(pollSignalCoalescingWindow),
   481  		recursiveWatchRetryEstablish: make(chan struct{}),
   482  		scanLock:                     scanLock,
   483  		hasher:                       hasherFactory(),
   484  		cache:                        cache,
   485  		ignorer:                      ignorer,
   486  		stager: staging.NewStager(
   487  			stagingRoot,
   488  			hideStagingRoot,
   489  			maximumStagingFileSize,
   490  			hasherFactory,
   491  		),
   492  	}
   493  
   494  	// Start the cache saving Goroutine.
   495  	go func() {
   496  		endpoint.saveCache(workerCtx, cachePath, saveCacheSignal)
   497  		close(saveCacheDone)
   498  	}()
   499  
   500  	// Compute the effective watch polling interval.
   501  	watchPollingInterval := configuration.WatchPollingInterval
   502  	if watchPollingInterval == 0 {
   503  		watchPollingInterval = version.DefaultWatchPollingInterval()
   504  	}
   505  
   506  	// Start the watching Goroutine.
   507  	go func() {
   508  		if actualWatchMode == reifiedWatchModePoll {
   509  			endpoint.watchPoll(workerCtx, watchPollingInterval, nonRecursiveWatchingAllowed)
   510  		} else if actualWatchMode == reifiedWatchModeRecursive {
   511  			endpoint.watchRecursive(workerCtx, watchPollingInterval)
   512  		}
   513  		close(watchDone)
   514  	}()
   515  
   516  	// Success.
   517  	return endpoint, nil
   518  }
   519  
   520  // lockScanLock acquires the scan lock in a preemptable fashion. To disable
   521  // preemption, pass context.Background(). This method returns true if the lock
   522  // is acquired and false otherwise. It will only return false if preemption
   523  // occurred via the provided context.
   524  func (e *endpoint) lockScanLock(ctx context.Context) bool {
   525  	select {
   526  	case <-ctx.Done():
   527  		return false
   528  	case <-e.scanLock:
   529  		return true
   530  	}
   531  }
   532  
   533  // unlockScanLock releases the scan lock. It should only be called by the
   534  // current holder of the scan lock.
   535  func (e *endpoint) unlockScanLock() {
   536  	e.scanLock <- struct{}{}
   537  }
   538  
   539  // saveCache serializes the cache and writes the result to disk at regular
   540  // intervals. It runs as a background Goroutine for all endpoints.
   541  func (e *endpoint) saveCache(ctx context.Context, cachePath string, signal <-chan struct{}) {
   542  	// Track the last saved cache. If it hasn't changed, there's no point in
   543  	// rewriting it. It's safe to keep a reference to the cache since caches are
   544  	// treated as immutable. The only cost is (possibly) keeping an old cache
   545  	// around until the next write cycle, but that's a relatively small price to
   546  	// pay to avoid unnecessary disk writes, and in the common case of
   547  	// accelerated scanning with no re-check paths, a new cache won't be
   548  	// generated anyway, so we won't be carrying anything extra around.
   549  	var lastSavedCache *core.Cache
   550  
   551  	// Track the last cache save time.
   552  	var lastSaveTime time.Time
   553  
   554  	for {
   555  		select {
   556  		case <-ctx.Done():
   557  			return
   558  		case <-signal:
   559  			// If it's been less than our minimum cache save interval, then skip
   560  			// this save request.
   561  			now := time.Now()
   562  			if now.Sub(lastSaveTime) < minimumCacheSaveInterval {
   563  				continue
   564  			}
   565  
   566  			// Grab the scan lock.
   567  			e.lockScanLock(context.Background())
   568  
   569  			// If the cache hasn't changed since the last write, then skip this
   570  			// save request.
   571  			if e.cache == lastSavedCache {
   572  				e.unlockScanLock()
   573  				continue
   574  			}
   575  
   576  			// Save the cache.
   577  			e.logger.Debug("Saving cache to disk")
   578  			if err := encoding.MarshalAndSaveProtobuf(cachePath, e.cache); err != nil {
   579  				e.logger.Error("Cache save failed:", err)
   580  				e.cacheWriteError = err
   581  				e.unlockScanLock()
   582  				return
   583  			}
   584  
   585  			// Update our state.
   586  			lastSavedCache = e.cache
   587  			lastSaveTime = now
   588  
   589  			// Release the cache lock.
   590  			e.unlockScanLock()
   591  		}
   592  	}
   593  }
   594  
   595  // watchPoll is the watch loop for poll-based watching, with optional support
   596  // for using native non-recursive watching facilities to reduce notification
   597  // latency on frequently updated contents.
   598  func (e *endpoint) watchPoll(ctx context.Context, pollingInterval uint32, nonRecursiveWatchingAllowed bool) {
   599  	// Create a sublogger.
   600  	logger := e.logger.Sublogger("polling")
   601  
   602  	// Create a ticker to regulate polling and defer its shutdown.
   603  	ticker := time.NewTicker(time.Duration(pollingInterval) * time.Second)
   604  	defer ticker.Stop()
   605  
   606  	// Track whether or not it's our first iteration in the polling loop. We
   607  	// adjust some behaviors in that case.
   608  	first := true
   609  
   610  	// Track the previous snapshot.
   611  	previous := &core.Snapshot{}
   612  
   613  	// If non-recursive watching is available, then set up a non-recursive
   614  	// watcher (and ensure its termination). Since non-recursive watching is a
   615  	// best-effort basis to reduce latency, we don't try to re-establish this
   616  	// watcher if it fails.
   617  	var watcher watching.NonRecursiveWatcher
   618  	var watchEvents <-chan string
   619  	var watchErrors <-chan error
   620  	if nonRecursiveWatchingAllowed && watching.NonRecursiveWatchingSupported {
   621  		logger.Debug("Creating non-recursive watcher")
   622  		if w, err := watching.NewNonRecursiveWatcher(); err != nil {
   623  			logger.Debug("Unable to create non-recursive watcher:", err)
   624  		} else {
   625  			logger.Debug("Successfully created non-recursive watcher")
   626  			watcher = w
   627  			watchEvents = watcher.Events()
   628  			watchErrors = watcher.Errors()
   629  			defer func() {
   630  				if watcher != nil {
   631  					watcher.Terminate()
   632  				}
   633  			}()
   634  		}
   635  	}
   636  
   637  	// Create (and defer termination of) a coalescer that we can use to drive
   638  	// polling when using non-recursive watching. This is only required if a
   639  	// non-recursive watcher is established, but tracking an event channel and
   640  	// strobe method conditionally would make this code even uglier.
   641  	performScanSignal := state.NewCoalescer(watchPollScanSignalCoalescingWindow)
   642  	defer performScanSignal.Terminate()
   643  
   644  	// Loop until cancellation, performing polling at the specified interval.
   645  	for {
   646  		// Set behaviors based on whether or not this is our first time in the
   647  		// loop. If this is our first time in the loop, then we skip waiting,
   648  		// because our ticker won't fire its first event until after the polling
   649  		// duration has elapsed, and we'd like a baseline scan before that. The
   650  		// reason we want a baseline scan before that is that we'll ignore
   651  		// modifications on our first successful scan. The reason for ignoring
   652  		// these modifications is that we'll be comparing against zero-valued
   653  		// variables and are thus certain to see modifications if there is
   654  		// existing content on disk. Since the controller already skips polling
   655  		// (if watching is enabled) on its first synchronization cycle, there's
   656  		// no point for us to also send a notification, because if both
   657  		// endpoints did this, you'd see up to three scans on session startup.
   658  		// Of course, if our scan fails on the first try, then we'll allow a
   659  		// notification (due to these "artificial" modifications) to be sent
   660  		// after the first successful scan, but that will at least occur after
   661  		// the initial polling duration.
   662  		var skipWaiting, ignoreModifications bool
   663  		if first {
   664  			skipWaiting = true
   665  			ignoreModifications = true
   666  			first = false
   667  		}
   668  
   669  		// Unless we're skipping waiting, wait for cancellation, a tick event,
   670  		// a notification from our non-recursive watches, or a coalesced event.
   671  		if !skipWaiting {
   672  			select {
   673  			case <-ctx.Done():
   674  				// Log termination.
   675  				logger.Debug("Polling terminated")
   676  
   677  				// Ensure that accelerated watching is disabled, if necessary.
   678  				if e.accelerationAllowed {
   679  					e.lockScanLock(context.Background())
   680  					e.accelerate = false
   681  					e.unlockScanLock()
   682  				}
   683  
   684  				// Terminate polling.
   685  				return
   686  			case <-ticker.C:
   687  				logger.Debug("Received timer-based polling signal")
   688  			case <-performScanSignal.Signals():
   689  				logger.Debug("Received event-driven polling signal")
   690  			case err := <-watchErrors:
   691  				// Log the error.
   692  				logger.Debug("Non-recursive watching error:", err)
   693  
   694  				// Terminate the watcher and nil it out. We don't bother trying
   695  				// to re-establish it. Also nil out the errors channel in case
   696  				// the watcher pumps any additional errors into it (in which
   697  				// case we don't want to trigger this code again on a nil
   698  				// watcher). We'll allow event channels to continue since they
   699  				// may contain residual events.
   700  				watcher.Terminate()
   701  				watcher = nil
   702  				watchErrors = nil
   703  
   704  				// Strobe the re-scan signal an continue polling.
   705  				performScanSignal.Strobe()
   706  				continue
   707  			case path := <-watchEvents:
   708  				// Filter temporary files and log the event. Non-recursive
   709  				// watchers return absolute paths, and thus we can't use our
   710  				// fast-path base name calculation for leaf name calculations.
   711  				// Fortunately, we don't need to perform the same total path
   712  				// prefix check as recursive watching since we know that
   713  				// temporary directories will never be added to the watcher
   714  				// (since they'll never be included in the scan), and thus we'll
   715  				// never see changes to their contents that would need to be
   716  				// filtered out.
   717  				ignore := strings.HasPrefix(filepath.Base(path), filesystem.TemporaryNamePrefix)
   718  				if ignore {
   719  					logger.Tracef("Ignoring event path: \"%s\"", path)
   720  					continue
   721  				} else {
   722  					logger.Tracef("Processing event path: \"%s\"", path)
   723  				}
   724  
   725  				// Strobe the re-scan signal and continue polling.
   726  				performScanSignal.Strobe()
   727  				continue
   728  			}
   729  		}
   730  
   731  		// Grab the scan lock.
   732  		e.lockScanLock(context.Background())
   733  
   734  		// Disable the use of the existing scan results.
   735  		e.accelerate = false
   736  
   737  		// Perform a scan. If there's an error, then assume it's due to
   738  		// concurrent modification. In that case, release the scan lock and
   739  		// strobe the poll events channel. The controller can then perform a
   740  		// full scan.
   741  		logger.Debug("Performing filesystem scan")
   742  		if err := e.scan(ctx, nil, nil); err != nil {
   743  			// Log the error.
   744  			logger.Debug("Scan failed:", err)
   745  
   746  			// Release the scan lock.
   747  			e.unlockScanLock()
   748  
   749  			// Strobe the poll signal and continue polling.
   750  			e.pollSignal.Strobe()
   751  			continue
   752  		}
   753  
   754  		// If our scan was successful, then we know that the scan results
   755  		// will be okay to return for the next Scan call, though we only
   756  		// indicate that acceleration should be used if the endpoint allows it.
   757  		e.accelerate = e.accelerationAllowed
   758  		if e.accelerate {
   759  			logger.Debug("Accelerated scanning now available")
   760  		}
   761  
   762  		// Extract scan parameters so that we can release the scan lock.
   763  		snapshot := e.snapshot
   764  
   765  		// Release the scan lock.
   766  		e.unlockScanLock()
   767  
   768  		// Check for modifications.
   769  		modified := !snapshot.Equal(previous)
   770  
   771  		// If we have a working non-recursive watcher, or we're performing trace
   772  		// logging, then perform a full diff to determine what's changed. This
   773  		// will let us determine the most recently updated paths that we should
   774  		// watch, as well as establish those watches. Any watch establishment
   775  		// errors will be reported on the watch errors channel.
   776  		if watcher != nil || logger.Level() >= logging.LevelTrace {
   777  			changes := core.Diff(previous.Content, snapshot.Content)
   778  			for _, change := range changes {
   779  				logger.Tracef("Observed change at \"%s\"", change.Path)
   780  				if watcher != nil && change.New != nil &&
   781  					(change.New.Kind == core.EntryKind_Directory ||
   782  						change.New.Kind == core.EntryKind_File) {
   783  					watcher.Watch(filepath.Join(e.root, change.Path))
   784  				}
   785  			}
   786  		}
   787  
   788  		// Update our tracking parameters.
   789  		previous = snapshot
   790  
   791  		// If we've seen modifications, and we're not ignoring them, then strobe
   792  		// the poll events channel.
   793  		if modified && !ignoreModifications {
   794  			// Log the modifications.
   795  			logger.Debug("Modifications detected")
   796  
   797  			// Strobe the poll signal.
   798  			e.pollSignal.Strobe()
   799  		} else {
   800  			// Log the lack of modifications.
   801  			logger.Debug("No unignored modifications detected")
   802  		}
   803  	}
   804  }
   805  
   806  // watchRecursive is the watch loop for platforms where native recursive
   807  // watching facilities are available.
   808  func (e *endpoint) watchRecursive(ctx context.Context, pollingInterval uint32) {
   809  	// Create a sublogger.
   810  	logger := e.logger.Sublogger("watching")
   811  
   812  	// Convert the polling interval to a duration.
   813  	pollingDuration := time.Duration(pollingInterval) * time.Second
   814  
   815  	// Track our recursive watcher and ensure that it's stopped when we return.
   816  	var watcher watching.RecursiveWatcher
   817  	defer func() {
   818  		if watcher != nil {
   819  			watcher.Terminate()
   820  		}
   821  	}()
   822  
   823  	// Create a timer, initially stopped and drained, that we can use to
   824  	// regulate waiting periods. Also, ensure that it's stopped when we return.
   825  	timer := time.NewTimer(0)
   826  	timeutil.StopAndDrainTimer(timer)
   827  	defer timer.Stop()
   828  
   829  	// Loop until cancellation.
   830  	var err error
   831  WatchEstablishment:
   832  	for {
   833  		// Attempt to establish the watch.
   834  		logger.Debug("Attempting to establish recursive watch")
   835  		watcher, err = watching.NewRecursiveWatcher(e.root)
   836  		if err != nil {
   837  			// Log the failure.
   838  			logger.Debug("Unable to establish recursive watch:", err)
   839  
   840  			// Strobe the poll signal (since nothing else will be driving
   841  			// synchronization from this endpoint at this point in time).
   842  			e.pollSignal.Strobe()
   843  
   844  			// Wait to retry watch establishment.
   845  			timer.Reset(pollingDuration)
   846  			select {
   847  			case <-ctx.Done():
   848  				logger.Debug("Watching terminated while waiting for establishment")
   849  				return
   850  			case <-timer.C:
   851  				continue
   852  			case <-e.recursiveWatchRetryEstablish:
   853  				logger.Debug("Received recursive watch establishment suggestion")
   854  				timeutil.StopAndDrainTimer(timer)
   855  				continue
   856  			}
   857  		}
   858  		logger.Debug("Watch successfully established")
   859  
   860  		// If accelerated scanning is allowed, then reset the timer (which won't
   861  		// be running) to fire immediately in the event loop in order to try
   862  		// enabling acceleration. The handler for the timer will take care of
   863  		// strobing the poll signal once the scan is done (that way there's not
   864  		// immediate contention for the scan lock). If accelerated scanning
   865  		// isn't allowed, then just strobe the poll signal here since
   866  		// establishment of the watch is worth signaling (and necessary on the
   867  		// first pass through the loop).
   868  		if e.accelerationAllowed {
   869  			timer.Reset(0)
   870  		} else {
   871  			e.pollSignal.Strobe()
   872  		}
   873  
   874  		// Loop and process events.
   875  		for {
   876  			select {
   877  			case <-ctx.Done():
   878  				// Log termination.
   879  				logger.Debug("Watching terminated")
   880  
   881  				// Ensure that accelerated watching is disabled, if necessary.
   882  				if e.accelerationAllowed {
   883  					e.lockScanLock(context.Background())
   884  					e.accelerate = false
   885  					e.recheckPaths = nil
   886  					e.unlockScanLock()
   887  				}
   888  
   889  				// Terminate watching.
   890  				return
   891  			case <-timer.C:
   892  				// Log the acceleration attempt.
   893  				logger.Debug("Attempting to enable accelerated scanning")
   894  
   895  				// Attempt to perform a baseline scan to enable acceleration.
   896  				e.lockScanLock(context.Background())
   897  				if err := e.scan(ctx, nil, nil); err != nil {
   898  					logger.Debug("Unable to perform baseline scan:", err)
   899  					timer.Reset(pollingDuration)
   900  				} else {
   901  					logger.Debug("Accelerated scanning now available")
   902  					e.accelerate = true
   903  					e.recheckPaths = make(map[string]bool)
   904  				}
   905  				e.unlockScanLock()
   906  
   907  				// Strobe the poll signal, regardless of outcome. The likely
   908  				// outcome is that we succeeded in enabling acceleration, but
   909  				// even if not, we'll still want to drive a synchronization
   910  				// cycle if this is the first pass through the loop.
   911  				e.pollSignal.Strobe()
   912  			case err := <-watcher.Errors():
   913  				// Log the error.
   914  				logger.Debug("Recursive watching error:", err)
   915  
   916  				// If acceleration is allowed on the endpoint, then disable scan
   917  				// acceleration and clear out the re-check paths.
   918  				if e.accelerationAllowed {
   919  					e.lockScanLock(context.Background())
   920  					e.accelerate = false
   921  					e.recheckPaths = nil
   922  					e.unlockScanLock()
   923  				}
   924  
   925  				// Stop and drain the timer, which may be running.
   926  				timeutil.StopAndDrainTimer(timer)
   927  
   928  				// Strobe the poll signal since something has occurred that's
   929  				// killed our watch.
   930  				e.pollSignal.Strobe()
   931  
   932  				// Terminate the watcher.
   933  				watcher.Terminate()
   934  				watcher = nil
   935  
   936  				// If the watcher failed due to an internal event overflow, then
   937  				// events are likely happening on disk faster than we can
   938  				// process them. In that case, wait one polling interval before
   939  				// attempting to re-establish the watch.
   940  				if err == watching.ErrWatchInternalOverflow {
   941  					logger.Debug("Waiting before watch re-establishment")
   942  					timer.Reset(pollingDuration)
   943  					select {
   944  					case <-ctx.Done():
   945  						return
   946  					case <-timer.C:
   947  					}
   948  				}
   949  
   950  				// Retry watch establishment.
   951  				continue WatchEstablishment
   952  			case path := <-watcher.Events():
   953  				// Filter temporary files and log the event. Recursive watchers
   954  				// return watch-root-relative paths, so we can use our fast-path
   955  				// base name calculation for leaf name calculations. We also
   956  				// check the entire path for a temporary prefix to identify
   957  				// temporary directories (whose contents may have non-temporary
   958  				// names, such as in the case of internal staging directories).
   959  				ignore := strings.HasPrefix(path, filesystem.TemporaryNamePrefix) ||
   960  					strings.HasPrefix(fastpath.Base(path), filesystem.TemporaryNamePrefix)
   961  				if ignore {
   962  					logger.Tracef("Ignoring event path: \"%s\"", path)
   963  					continue
   964  				} else {
   965  					logger.Tracef("Processing event path: \"%s\"", path)
   966  				}
   967  
   968  				// If acceleration is allowed (and currently available) on the
   969  				// endpoint, then register the path as a re-check path. We only
   970  				// need to do this if acceleration is already available,
   971  				// otherwise we're still in a pre-baseline scan state and don't
   972  				// need to record these events.
   973  				if e.accelerationAllowed {
   974  					e.lockScanLock(context.Background())
   975  					if e.accelerate {
   976  						e.recheckPaths[path] = true
   977  					}
   978  					e.unlockScanLock()
   979  				}
   980  
   981  				// Strobe the poll signal to signal the event.
   982  				e.pollSignal.Strobe()
   983  			}
   984  		}
   985  	}
   986  }
   987  
   988  // Poll implements the Poll method for local endpoints.
   989  func (e *endpoint) Poll(ctx context.Context) error {
   990  	// Wait for either cancellation or an event.
   991  	select {
   992  	case <-ctx.Done():
   993  	case <-e.pollSignal.Signals():
   994  	}
   995  
   996  	// Done.
   997  	return nil
   998  }
   999  
  1000  // scan is the internal function which performs a scan operation on the root and
  1001  // updates the endpoint scan parameters. The caller must hold the scan lock.
  1002  func (e *endpoint) scan(ctx context.Context, baseline *core.Snapshot, recheckPaths map[string]bool) error {
  1003  	// Perform a full (warm) scan, watching for errors.
  1004  	snapshot, newCache, newIgnoreCache, err := core.Scan(
  1005  		ctx,
  1006  		e.root,
  1007  		baseline, recheckPaths,
  1008  		e.hasher, e.cache,
  1009  		e.ignorer, e.ignoreCache,
  1010  		e.probeMode,
  1011  		e.symbolicLinkMode,
  1012  		e.permissionsMode,
  1013  	)
  1014  	if err != nil {
  1015  		return err
  1016  	}
  1017  
  1018  	// Update the snapshot.
  1019  	e.snapshot = snapshot
  1020  
  1021  	// Update caches.
  1022  	e.cache = newCache
  1023  	e.ignoreCache = newIgnoreCache
  1024  
  1025  	// Update the last scan entry count.
  1026  	e.lastScanEntryCount = snapshot.Content.Count()
  1027  
  1028  	// Trigger an asynchronous cache save operation.
  1029  	select {
  1030  	case e.saveCacheSignal <- struct{}{}:
  1031  	default:
  1032  	}
  1033  
  1034  	// Success.
  1035  	return nil
  1036  }
  1037  
  1038  // Scan implements the Scan method for local endpoints.
  1039  func (e *endpoint) Scan(ctx context.Context, _ *core.Entry, full bool) (*core.Snapshot, error, bool) {
  1040  	// Grab the scan lock and defer its release. If lock acquisition is
  1041  	// preempted, then the controller has cancelled the request.
  1042  	if !e.lockScanLock(ctx) {
  1043  		return nil, fmt.Errorf("scan cancelled: %w", context.Canceled), false
  1044  	}
  1045  	defer e.unlockScanLock()
  1046  
  1047  	// Before attempting to perform a scan, check for any cache write errors
  1048  	// that may have occurred during background cache writes. If we see any
  1049  	// error, then we skip scanning and report them here.
  1050  	if e.cacheWriteError != nil {
  1051  		return nil, fmt.Errorf("unable to save cache to disk: %w", e.cacheWriteError), false
  1052  	}
  1053  
  1054  	// Perform a scan.
  1055  	//
  1056  	// We check to see if we can accelerate the scanning process by using
  1057  	// information from a background watching Goroutine. For recursive watching,
  1058  	// this means performing a re-scan using a baseline and a set of re-check
  1059  	// paths. For poll-based watching, this just means re-using the last scan,
  1060  	// so no action is needed here. If acceleration isn't available (due to the
  1061  	// state of the watcher or because it's disallowed on the endpoint), then we
  1062  	// just perform a full (warm) scan. We also avoid acceleration in the event
  1063  	// that a full scan has been explicitly requested, but we don't make any
  1064  	// change to the state of acceleration availability, because performing a
  1065  	// full warm scan will only improve the accuracy of the baseline (most
  1066  	// recent) snapshot, so acceleration will still work.
  1067  	//
  1068  	// If we see any error while scanning, we just have to assume that it's due
  1069  	// to concurrent modifications and suggest a retry. In the case of
  1070  	// accelerated scanning with recursive watching, there's no need to disable
  1071  	// acceleration on failure so long as the watch is still established (and if
  1072  	// it's not, that will handled elsewhere).
  1073  	if e.accelerate && !full {
  1074  		if e.watchMode == reifiedWatchModeRecursive {
  1075  			e.logger.Debug("Performing accelerated scan with", len(e.recheckPaths), "recheck paths")
  1076  			if err := e.scan(ctx, e.snapshot, e.recheckPaths); err != nil {
  1077  				return nil, err, !errors.Is(err, core.ErrScanCancelled)
  1078  			} else {
  1079  				e.recheckPaths = make(map[string]bool)
  1080  			}
  1081  		} else {
  1082  			e.logger.Debug("Performing accelerated scan with existing snapshot")
  1083  		}
  1084  	} else {
  1085  		e.logger.Debug("Performing full scan")
  1086  		if err := e.scan(ctx, nil, nil); err != nil {
  1087  			return nil, err, true
  1088  		}
  1089  	}
  1090  
  1091  	// Verify that we haven't exceeded the maximum entry count.
  1092  	// TODO: Do we actually want to enforce this count in the scan operation so
  1093  	// that we don't hold those entries in memory? Right now this is mostly
  1094  	// concerned with avoiding transmission of the entries over the wire.
  1095  	if e.lastScanEntryCount > e.maximumEntryCount {
  1096  		e.logger.Debugf("Scan count (%d) exceeded maximum allowed entry count (%d)",
  1097  			e.lastScanEntryCount, e.maximumEntryCount,
  1098  		)
  1099  		return nil, errors.New("exceeded allowed entry count"), true
  1100  	}
  1101  
  1102  	// Update call states.
  1103  	e.scannedSinceLastStageCall = true
  1104  	e.scannedSinceLastTransitionCall = true
  1105  
  1106  	// Store the values corresponding to the snapshot that we'll return.
  1107  	e.lastReturnedScanCache = e.cache
  1108  	e.lastReturnedScanSnapshotDecomposesUnicode = e.snapshot.DecomposesUnicode
  1109  
  1110  	// Success.
  1111  	return e.snapshot, nil, false
  1112  }
  1113  
  1114  // stageFromRoot attempts to perform staging from local files by using a reverse
  1115  // lookup map.
  1116  func (e *endpoint) stageFromRoot(
  1117  	path string,
  1118  	digest []byte,
  1119  	reverseLookupMap *core.ReverseLookupMap,
  1120  	opener *filesystem.Opener,
  1121  ) bool {
  1122  	// See if we can find a path within the root that has a matching digest.
  1123  	sourcePath, sourcePathOk := reverseLookupMap.Lookup(digest)
  1124  	if !sourcePathOk {
  1125  		return false
  1126  	}
  1127  
  1128  	// Open the source file and defer its closure.
  1129  	source, _, err := opener.OpenFile(sourcePath)
  1130  	if err != nil {
  1131  		return false
  1132  	}
  1133  	defer source.Close()
  1134  
  1135  	// Create a staging sink. We explicitly manage its closure below.
  1136  	sink, err := e.stager.Sink(path)
  1137  	if err != nil {
  1138  		return false
  1139  	}
  1140  
  1141  	// Copy data to the sink and close it, then check for copy errors.
  1142  	_, err = io.Copy(sink, source)
  1143  	sink.Close()
  1144  	if err != nil {
  1145  		return false
  1146  	}
  1147  
  1148  	// Verify that everything staged correctly, ensuring that the source file
  1149  	// wasn't modified during the copy operation.
  1150  	success, _ := e.stager.Contains(path, digest)
  1151  	return success
  1152  }
  1153  
  1154  // Stage implements the Stage method for local endpoints.
  1155  func (e *endpoint) Stage(paths []string, digests [][]byte) ([]string, []*rsync.Signature, rsync.Receiver, error) {
  1156  	// If we're in a read-only mode, we shouldn't be staging files.
  1157  	if e.readOnly {
  1158  		return nil, nil, nil, errors.New("endpoint is in read-only mode")
  1159  	}
  1160  
  1161  	// Validate argument lengths and bail if there's nothing to stage.
  1162  	if len(paths) != len(digests) {
  1163  		return nil, nil, nil, errors.New("path count does not match digest count")
  1164  	} else if len(paths) == 0 {
  1165  		return nil, nil, nil, nil
  1166  	}
  1167  
  1168  	// Grab the scan lock. We'll need this to verify the last scan entry count
  1169  	// and to generate the reverse lookup map.
  1170  	e.lockScanLock(context.Background())
  1171  
  1172  	// Verify that we've performed a scan since the last staging operation, that
  1173  	// way our count check is valid. If we haven't, then the controller is
  1174  	// either malfunctioning or malicious.
  1175  	if !e.scannedSinceLastStageCall {
  1176  		e.unlockScanLock()
  1177  		return nil, nil, nil, errors.New("multiple staging operations performed without scan")
  1178  	}
  1179  	e.scannedSinceLastStageCall = false
  1180  
  1181  	// Verify that the number of paths provided isn't going to put us over the
  1182  	// maximum number of allowed entries.
  1183  	if e.maximumEntryCount != 0 && (e.maximumEntryCount-e.lastScanEntryCount) < uint64(len(paths)) {
  1184  		e.unlockScanLock()
  1185  		return nil, nil, nil, errors.New("staging would exceeded allowed entry count")
  1186  	}
  1187  
  1188  	// Generate a reverse lookup map from the cache, which we'll use shortly to
  1189  	// detect renames and copies.
  1190  	reverseLookupMap, err := e.cache.GenerateReverseLookupMap()
  1191  	if err != nil {
  1192  		e.unlockScanLock()
  1193  		return nil, nil, nil, fmt.Errorf("unable to generate reverse lookup map: %w", err)
  1194  	}
  1195  
  1196  	// Release the scan lock.
  1197  	e.unlockScanLock()
  1198  
  1199  	// Inform the stager that we're about to begin staging and transition
  1200  	// operations.
  1201  	if err := e.stager.Initialize(); err != nil {
  1202  		return nil, nil, nil, fmt.Errorf("unable to initialize stager: %w", err)
  1203  	}
  1204  
  1205  	// Create an opener that we can use file opening and defer its closure. We
  1206  	// can't cache this across synchronization cycles since its path references
  1207  	// may become invalidated or may prevent modifications.
  1208  	opener := filesystem.NewOpener(e.root)
  1209  	defer opener.Close()
  1210  
  1211  	// Filter the path list by looking for files that we can source locally.
  1212  	//
  1213  	// First, check if the content can be provided from the stager, which
  1214  	// indicates that a previous staging operation was interrupted.
  1215  	//
  1216  	// Second, use a reverse lookup map (generated from the cache) and see if we
  1217  	// can find (and stage) any files locally, which indicates that a file has
  1218  	// been copied or renamed.
  1219  	//
  1220  	// If we manage to handle all files, then we can abort staging.
  1221  	filteredPaths := paths[:0]
  1222  	for p, path := range paths {
  1223  		digest := digests[p]
  1224  		if available, err := e.stager.Contains(path, digest); err != nil {
  1225  			return nil, nil, nil, fmt.Errorf("unable to query file staging status: %w", err)
  1226  		} else if available {
  1227  			continue
  1228  		} else if e.stageFromRoot(path, digest, reverseLookupMap, opener) {
  1229  			continue
  1230  		} else {
  1231  			filteredPaths = append(filteredPaths, path)
  1232  		}
  1233  	}
  1234  	if len(filteredPaths) == 0 {
  1235  		return nil, nil, nil, nil
  1236  	}
  1237  
  1238  	// Create an rsync engine.
  1239  	engine := rsync.NewEngine()
  1240  
  1241  	// Compute signatures for each of the unstaged paths. For paths that don't
  1242  	// exist or that can't be read, just use an empty signature, which means to
  1243  	// expect/use an empty base when deltifying/patching.
  1244  	//
  1245  	// If the root doesn't exist or doesn't contain any files, then we can just
  1246  	// use an empty signature straight away.
  1247  	rootExistsAndHasFileContents := reverseLookupMap.Length() > 0
  1248  	emptySignature := &rsync.Signature{}
  1249  	signatures := make([]*rsync.Signature, len(filteredPaths))
  1250  	for p, path := range filteredPaths {
  1251  		if !rootExistsAndHasFileContents {
  1252  			signatures[p] = emptySignature
  1253  		} else if base, _, err := opener.OpenFile(path); err != nil {
  1254  			signatures[p] = emptySignature
  1255  		} else if signature, err := engine.Signature(base, 0); err != nil {
  1256  			base.Close()
  1257  			signatures[p] = emptySignature
  1258  		} else {
  1259  			base.Close()
  1260  			signatures[p] = signature
  1261  		}
  1262  	}
  1263  
  1264  	// Create a receiver.
  1265  	receiver, err := rsync.NewReceiver(e.root, filteredPaths, signatures, e.stager)
  1266  	if err != nil {
  1267  		return nil, nil, nil, fmt.Errorf("unable to create rsync receiver: %w", err)
  1268  	}
  1269  
  1270  	// Done.
  1271  	return filteredPaths, signatures, receiver, nil
  1272  }
  1273  
  1274  // Supply implements the supply method for local endpoints.
  1275  func (e *endpoint) Supply(paths []string, signatures []*rsync.Signature, receiver rsync.Receiver) error {
  1276  	return rsync.Transmit(e.root, paths, signatures, receiver)
  1277  }
  1278  
  1279  // Transition implements the Transition method for local endpoints.
  1280  func (e *endpoint) Transition(ctx context.Context, transitions []*core.Change) ([]*core.Entry, []*core.Problem, bool, error) {
  1281  	// If we're in a read-only mode, we shouldn't be performing transitions.
  1282  	if e.readOnly {
  1283  		return nil, nil, false, errors.New("endpoint is in read-only mode")
  1284  	}
  1285  
  1286  	// Grab the scan lock and defer its release.
  1287  	e.lockScanLock(context.Background())
  1288  	defer e.unlockScanLock()
  1289  
  1290  	// Verify that we've performed a scan since the last transition operation,
  1291  	// that way our count check is valid. If we haven't, then the controller is
  1292  	// either malfunctioning or malicious.
  1293  	if !e.scannedSinceLastTransitionCall {
  1294  		return nil, nil, false, errors.New("multiple transition operations performed without scan")
  1295  	}
  1296  	e.scannedSinceLastTransitionCall = false
  1297  
  1298  	// Verify that the number of entries we'll be creating won't put us over the
  1299  	// maximum number of allowed entries. Again, we don't worry too much about
  1300  	// overflow here for the same reasons as in Entry.Count.
  1301  	if e.maximumEntryCount != 0 {
  1302  		// Compute the resulting entry count. If we dip below zero in this
  1303  		// counting process, then the controller is malfunctioning.
  1304  		resultingEntryCount := e.lastScanEntryCount
  1305  		for _, transition := range transitions {
  1306  			if removed := transition.Old.Count(); removed > resultingEntryCount {
  1307  				return nil, nil, false, errors.New("transition requires removing more entries than exist")
  1308  			} else {
  1309  				resultingEntryCount -= removed
  1310  			}
  1311  			resultingEntryCount += transition.New.Count()
  1312  		}
  1313  
  1314  		// If the resulting entry count would be too high, then abort the
  1315  		// transitioning operation, but return the error as a problem, not an
  1316  		// error, since nobody is malfunctioning here.
  1317  		if e.maximumEntryCount < resultingEntryCount {
  1318  			results := make([]*core.Entry, len(transitions))
  1319  			for t, transition := range transitions {
  1320  				results[t] = transition.Old
  1321  			}
  1322  			problems := []*core.Problem{{Error: "transitioning would exceeded allowed entry count"}}
  1323  			return results, problems, false, nil
  1324  		}
  1325  	}
  1326  
  1327  	// Perform the transition. We release the scan lock around this operation
  1328  	// because we want watching Goroutines to be able to pick up events, or at
  1329  	// least be able to handle them. If we held scan lock, there's a good chance
  1330  	// that the underlying watchers would overflow while they waited for event
  1331  	// paths to be handled. Note that we don't need to hold the scan lock to
  1332  	// read lastReturnedScanCache and lastReturnedScanSnapshotDecomposesUnicode
  1333  	// because these aren't updated concurrently and thus don't fall under the
  1334  	// scope of the scan lock.
  1335  	e.unlockScanLock()
  1336  	results, problems, stagerMissingFiles := core.Transition(
  1337  		ctx,
  1338  		e.root,
  1339  		transitions,
  1340  		e.lastReturnedScanCache,
  1341  		e.symbolicLinkMode,
  1342  		e.defaultFileMode,
  1343  		e.defaultDirectoryMode,
  1344  		e.defaultOwnership,
  1345  		e.lastReturnedScanSnapshotDecomposesUnicode,
  1346  		e.stager,
  1347  	)
  1348  	e.lockScanLock(context.Background())
  1349  
  1350  	// Determine whether or not the transition made any changes on disk.
  1351  	var transitionMadeChanges bool
  1352  	for r, result := range results {
  1353  		if !result.Equal(transitions[r].Old, true) {
  1354  			transitionMadeChanges = true
  1355  			break
  1356  		}
  1357  	}
  1358  
  1359  	// If we're using recursive watching and we made any changes to disk, then
  1360  	// send a signal to trigger watch establishment (if needed), because if no
  1361  	// watch is currently established due to the synchronization root not having
  1362  	// existed, then there's a high likelihood that we just created it.
  1363  	if e.watchMode == reifiedWatchModeRecursive && transitionMadeChanges {
  1364  		select {
  1365  		case e.recursiveWatchRetryEstablish <- struct{}{}:
  1366  		default:
  1367  		}
  1368  	}
  1369  
  1370  	// Ensure that accelerated scanning doesn't return a stale (pre-transition)
  1371  	// snapshot. This is critical, especially in the case of poll-based watching
  1372  	// (where it has a high chance of occurring), because it leads to the
  1373  	// pathologically bad case of a pre-transition snapshot being returned by
  1374  	// the next call to Scan, which will cause the controller will perform an
  1375  	// inversion (on the opposite endpoint) of the transitions that were just
  1376  	// applied here. In the case of recursive watching, we just need to ensure
  1377  	// that any modified paths get put into the re-check path list, because
  1378  	// there could be a delay in the OS reporting the modifications, or a delay
  1379  	// in the watching Goroutine detecting and handling failure, and thus Scan
  1380  	// could acquire the scan lock before recheck paths are appropriately
  1381  	// updated or acceleration is disabled due to failure. In the case of
  1382  	// poll-based watching, we just need to disable accelerated scanning, which
  1383  	// will be automatically re-enabled on the next polling operation. If
  1384  	// filesystem watching is disabled, then so is acceleration, and thus
  1385  	// there's no way that a stale scan could be returned. Note that, in the
  1386  	// recurisve watching case, we only need to include transition roots because
  1387  	// transition operations will always change the root type and thus scanning
  1388  	// will see any potential content changes. Also, we don't need to worry
  1389  	// about delayed watcher failure reporting due to external (non-transition)
  1390  	// changes because those won't be exact inversions of the operations that
  1391  	// we're applying here. That type of failure is unavoidable anyway, but
  1392  	// still guarded against by Transition's just-in-time modification checks.
  1393  	if e.accelerate && transitionMadeChanges {
  1394  		if e.watchMode == reifiedWatchModePoll {
  1395  			e.accelerate = false
  1396  		} else if e.watchMode == reifiedWatchModeRecursive {
  1397  			for _, transition := range transitions {
  1398  				e.recheckPaths[transition.Path] = true
  1399  			}
  1400  		}
  1401  	}
  1402  
  1403  	// If we're using poll-based watching, then strobe the poll signal if
  1404  	// Transition made any changes on disk. This is necessary to work around
  1405  	// cases where some other mechanism rapidly (and fully) inverts changes, in
  1406  	// which case the pre-Transition and post-Transition scans will look the
  1407  	// same to the poll-based watching Goroutine and the inversion operation
  1408  	// (which should be reported back to the controller) won't be caught. This
  1409  	// is unrelated to the stale scan inversion issue mentioned above - in this
  1410  	// case the problem is that the changes are seen, but no polling event is
  1411  	// ever generated because the polling Goroutine doesn't know what the
  1412  	// controller expects the disk to look like - it just knows that nothing has
  1413  	// changed between now and some previous point in time.
  1414  	//
  1415  	// An example of this is when a new file is propagated but then removed by
  1416  	// the user before the next poll-based scan. In this case, the polling scan
  1417  	// looks the same before and after Transition, and no polling event will be
  1418  	// generated if we don't do it here. It's important that we only do this if
  1419  	// on-disk changes were actually applied, otherwise we'll drive a feedback
  1420  	// loop when problems are encountered for changes that can never be fully
  1421  	// applied.
  1422  	if e.watchMode == reifiedWatchModePoll && transitionMadeChanges {
  1423  		e.pollSignal.Strobe()
  1424  	}
  1425  
  1426  	// Finalize the stager, which will also wipe the staging directory. We don't
  1427  	// monitor for errors here, because we need to return the results and
  1428  	// problems no matter what, but if there's something weird going on with the
  1429  	// filesystem, we'll see it the next time we scan or stage.
  1430  	//
  1431  	// TODO: If we see a large number of problems, should we avoid wiping the
  1432  	// staging directory? It could be due to an easily correctable error, at
  1433  	// which point you wouldn't want to restage if you're talking about lots of
  1434  	// files.
  1435  	e.stager.Finalize()
  1436  
  1437  	// Done.
  1438  	return results, problems, stagerMissingFiles, nil
  1439  }
  1440  
  1441  // Shutdown implements the Shutdown method for local endpoints.
  1442  func (e *endpoint) Shutdown() error {
  1443  	// Signal background worker Goroutines to terminate.
  1444  	e.workerCancel()
  1445  
  1446  	// Wait for background worker Goroutines to terminate.
  1447  	<-e.saveCacheDone
  1448  	<-e.watchDone
  1449  
  1450  	// Terminate the polling coalescer.
  1451  	e.pollSignal.Terminate()
  1452  
  1453  	// Done.
  1454  	return nil
  1455  }