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 }