2015-09-27 09:50:54 +01:00
// Copyright (C) 2014 The Syncthing Authors.
2014-09-29 21:43:32 +02:00
//
2015-03-07 21:36:35 +01:00
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
2017-02-09 07:52:18 +01:00
// You can obtain one at https://mozilla.org/MPL/2.0/.
2014-06-01 22:50:14 +02:00
2014-05-15 00:26:55 -03:00
package model
2014-03-28 14:36:57 +01:00
import (
2018-01-14 14:30:11 +00:00
"bytes"
2023-08-04 19:57:30 +02:00
"context"
2024-08-10 13:58:20 +02:00
"crypto/sha256"
2022-08-16 10:01:49 +02:00
"errors"
2014-08-25 17:45:13 +02:00
"fmt"
2020-04-27 00:13:18 +02:00
"io"
2025-08-07 11:19:36 +02:00
"log/slog"
2014-03-28 14:36:57 +01:00
"path/filepath"
2025-05-26 20:37:49 +02:00
"slices"
2022-07-26 08:24:58 +02:00
"strconv"
2016-01-03 21:15:02 +01:00
"strings"
2025-08-07 11:19:36 +02:00
"sync"
2014-03-28 14:36:57 +01:00
"time"
2014-06-20 00:27:54 +02:00
2025-03-29 12:50:08 +00:00
"github.com/syncthing/syncthing/internal/itererr"
2025-08-07 11:19:36 +02:00
"github.com/syncthing/syncthing/internal/slogutil"
2022-07-28 19:36:39 +02:00
"github.com/syncthing/syncthing/lib/build"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
2016-08-05 17:45:45 +00:00
"github.com/syncthing/syncthing/lib/fs"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/osutil"
2015-09-22 19:38:46 +02:00
"github.com/syncthing/syncthing/lib/protocol"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/scanner"
2023-08-21 19:44:33 +02:00
"github.com/syncthing/syncthing/lib/semaphore"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/versioner"
2014-03-28 14:36:57 +01:00
)
2017-11-09 21:16:29 +00:00
var (
blockStats = make ( map [ string ] int )
2025-08-07 11:19:36 +02:00
blockStatsMut sync . Mutex
2017-11-09 21:16:29 +00:00
)
2016-05-04 10:47:33 +00:00
func init () {
2016-12-16 22:23:35 +00:00
folderFactories [ config . FolderTypeSendReceive ] = newSendReceiveFolder
2016-05-04 10:47:33 +00:00
}
2014-09-27 14:44:15 +02:00
// A pullBlockState is passed to the puller routine for each block that needs
// to be fetched.
type pullBlockState struct {
* sharedPullerState
2025-10-21 22:00:44 +02:00
2014-09-27 14:44:15 +02:00
block protocol . BlockInfo
2014-03-28 14:36:57 +01:00
}
2014-09-27 14:44:15 +02:00
// A copyBlocksState is passed to copy routine if the file has blocks to be
2014-10-08 23:41:23 +01:00
// copied.
2014-09-27 14:44:15 +02:00
type copyBlocksState struct {
* sharedPullerState
2025-10-21 22:00:44 +02:00
2014-09-27 14:44:15 +02:00
blocks [] protocol . BlockInfo
2017-01-04 21:04:13 +00:00
have int
2014-03-28 14:36:57 +01:00
}
2015-07-03 10:25:35 +01:00
// Which filemode bits to preserve
2017-08-19 14:36:56 +00:00
const retainBits = fs . ModeSetgid | fs . ModeSetuid | fs . ModeSticky
2015-07-03 10:25:35 +01:00
2014-09-27 14:44:15 +02:00
var (
2020-08-20 10:56:29 +02:00
activity = newDeviceActivity ()
errNoDevice = errors . New ( "peers who had this file went away, or the file has changed while syncing. will retry later" )
errDirPrefix = "directory has been deleted on a remote device but "
errDirHasToBeScanned = errors . New ( errDirPrefix + "contains changed files, scheduling scan" )
errDirHasIgnored = errors . New ( errDirPrefix + "contains ignored files (see ignore documentation for (?d) prefix)" )
errDirNotEmpty = errors . New ( errDirPrefix + "is not empty; the contents are probably ignored on that remote device, but not locally" )
errNotAvailable = errors . New ( "no connected device has the required version of this file" )
errModified = errors . New ( "file modified but not rescanned; will try again later" )
errUnexpectedDirOnFileDel = errors . New ( "encountered directory when trying to remove file/symlink" )
errIncompatibleSymlink = errors . New ( "incompatible symlink entry; rescan with newer Syncthing on source" )
contextRemovingOldItem = "removing item to be replaced"
2014-09-27 14:44:15 +02:00
)
2020-06-16 15:20:08 +02:00
type dbUpdateType int
func ( d dbUpdateType ) String () string {
switch d {
case dbUpdateHandleDir :
return "dbUpdateHandleDir"
case dbUpdateDeleteDir :
return "dbUpdateDeleteDir"
case dbUpdateHandleFile :
return "dbUpdateHandleFile"
case dbUpdateDeleteFile :
return "dbUpdateDeleteFile"
case dbUpdateShortcutFile :
2024-02-01 15:11:09 +01:00
return "dbUpdateShortcutFile"
2020-06-16 15:20:08 +02:00
case dbUpdateHandleSymlink :
return "dbUpdateHandleSymlink"
case dbUpdateInvalidate :
return "dbUpdateHandleInvalidate"
}
panic ( fmt . Sprintf ( "unknown dbUpdateType %d" , d ))
}
2015-06-16 12:12:34 +01:00
const (
2020-06-16 15:20:08 +02:00
dbUpdateHandleDir dbUpdateType = iota
2015-06-16 12:12:34 +01:00
dbUpdateDeleteDir
dbUpdateHandleFile
dbUpdateDeleteFile
dbUpdateShortcutFile
2016-12-09 18:02:18 +00:00
dbUpdateHandleSymlink
2017-11-11 19:18:17 +00:00
dbUpdateInvalidate
2015-06-16 12:12:34 +01:00
)
2015-08-14 09:37:04 +02:00
const (
2018-02-25 10:14:02 +01:00
defaultCopiers = 2
defaultPullerPause = 60 * time . Second
2018-05-01 23:50:23 +02:00
defaultPullerPendingKiB = 2 * protocol . MaxBlockSize / 1024
2018-02-25 10:14:02 +01:00
2017-11-07 06:59:35 +00:00
maxPullerIterations = 3
2015-08-14 09:37:04 +02:00
)
2015-06-16 12:12:34 +01:00
type dbUpdateJob struct {
file protocol . FileInfo
2020-06-16 15:20:08 +02:00
jobType dbUpdateType
2015-06-16 12:12:34 +01:00
}
2016-12-16 22:23:35 +00:00
type sendReceiveFolder struct {
2025-08-07 11:19:36 +02:00
* folder
2015-03-16 21:14:19 +01:00
2020-05-11 22:44:04 +01:00
queue * jobQueue
blockPullReorderer blockPullReorderer
2023-08-21 19:44:33 +02:00
writeLimiter * semaphore . Semaphore
2015-06-26 13:31:30 +02:00
2020-07-28 11:13:15 +02:00
tempPullErrors map [ string ] string // pull errors that might be just transient
2015-03-16 21:14:19 +01:00
}
2025-03-29 12:50:08 +00:00
func newSendReceiveFolder ( model * model , ignores * ignore . Matcher , cfg config . FolderConfiguration , ver versioner . Versioner , evLogger events . Logger , ioLimiter * semaphore . Semaphore ) service {
2016-12-16 22:23:35 +00:00
f := & sendReceiveFolder {
2025-03-29 12:50:08 +00:00
folder : newFolder ( model , ignores , cfg , evLogger , ioLimiter , ver ),
2020-05-11 22:44:04 +01:00
queue : newJobQueue (),
blockPullReorderer : newBlockPullReorderer ( cfg . BlockPullOrder , model . id , cfg . DeviceIDs ()),
2023-08-21 19:44:33 +02:00
writeLimiter : semaphore . New ( cfg . MaxConcurrentWrites ),
2015-03-16 21:14:19 +01:00
}
2025-06-06 11:54:40 +02:00
f . puller = f
2015-08-14 09:37:04 +02:00
2016-12-21 11:23:20 +00:00
if f . Copiers == 0 {
f . Copiers = defaultCopiers
2015-08-14 09:37:04 +02:00
}
2018-02-25 10:14:02 +01:00
// If the configured max amount of pending data is zero, we use the
// default. If it's configured to something non-zero but less than the
// protocol block size we adjust it upwards accordingly.
if f . PullerMaxPendingKiB == 0 {
f . PullerMaxPendingKiB = defaultPullerPendingKiB
}
2018-04-16 20:08:50 +02:00
if blockSizeKiB := protocol . MaxBlockSize / 1024 ; f . PullerMaxPendingKiB < blockSizeKiB {
2018-02-25 10:14:02 +01:00
f . PullerMaxPendingKiB = blockSizeKiB
2015-08-14 09:37:04 +02:00
}
2018-02-25 10:14:02 +01:00
return f
2014-03-28 14:36:57 +01:00
}
2019-01-01 10:17:14 +01:00
// pull returns true if it manages to get all needed items from peers, i.e. get
// the device in sync with the global state.
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) pull ( ctx context . Context ) ( bool , error ) {
f . sl . DebugContext ( ctx , "Pulling" )
2017-11-07 06:59:35 +00:00
2017-12-07 08:42:03 +00:00
scanChan := make ( chan string )
2025-11-27 21:34:35 +01:00
go f . pullScannerRoutine ( ctx , scanChan )
2023-08-08 07:24:02 +02:00
defer func () {
close ( scanChan )
f . setState ( FolderIdle )
}()
2017-12-07 08:42:03 +00:00
2023-08-04 19:57:30 +02:00
metricFolderPulls . WithLabelValues ( f . ID ). Inc ()
2025-11-27 21:34:35 +01:00
pullCtx , cancel := context . WithCancel ( ctx )
2023-08-04 19:57:30 +02:00
defer cancel ()
2025-11-27 21:34:35 +01:00
go addTimeUntilCancelled ( pullCtx , metricFolderPullSeconds . WithLabelValues ( f . ID ))
2018-07-04 09:07:33 +02:00
2019-11-11 15:50:28 +01:00
changed := 0
2020-11-06 14:22:20 +01:00
f . errorsMut . Lock ()
2020-07-28 11:13:15 +02:00
f . pullErrors = nil
2020-11-06 14:22:20 +01:00
f . errorsMut . Unlock ()
2020-07-28 11:13:15 +02:00
2021-03-07 13:43:22 +01:00
var err error
2025-05-20 15:04:33 +02:00
for tries := range maxPullerIterations {
2018-07-04 09:07:33 +02:00
select {
2025-11-27 21:34:35 +01:00
case <- ctx . Done ():
return false , ctx . Err ()
2018-07-04 09:07:33 +02:00
default :
}
2019-10-16 09:08:54 +02:00
// Needs to be set on every loop, as the puller might have set
// it to FolderSyncing during the last iteration.
f . setState ( FolderSyncPreparing )
2025-11-27 21:34:35 +01:00
changed , err = f . pullerIteration ( ctx , scanChan )
2021-03-07 13:43:22 +01:00
if err != nil {
return false , err
}
2019-01-14 08:30:52 +01:00
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Pull iteration completed" , "changed" , changed , "try" , tries + 1 )
2017-11-07 06:59:35 +00:00
if changed == 0 {
2025-12-23 08:26:58 +01:00
// No files were changed by the puller, so we are in sync, or we
// are unable to make further progress for the moment.
2019-11-11 15:50:28 +01:00
break
2017-11-07 06:59:35 +00:00
}
2019-01-14 08:30:52 +01:00
}
2017-11-07 06:59:35 +00:00
2020-11-06 14:22:20 +01:00
f . errorsMut . Lock ()
pullErrNum := len ( f . tempPullErrors )
if pullErrNum > 0 {
f . pullErrors = make ([] FileError , 0 , len ( f . tempPullErrors ))
for path , err := range f . tempPullErrors {
2025-11-27 21:34:35 +01:00
f . sl . WarnContext ( ctx , "Failed to sync" , slogutil . FilePath ( path ), slogutil . Error ( err ))
2020-11-06 14:22:20 +01:00
f . pullErrors = append ( f . pullErrors , FileError {
Err : err ,
Path : path ,
})
}
f . tempPullErrors = nil
2020-07-28 11:13:15 +02:00
}
2020-11-06 14:22:20 +01:00
f . errorsMut . Unlock ()
2020-07-28 11:13:15 +02:00
2019-11-19 09:56:53 +01:00
if pullErrNum > 0 {
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . FolderErrors , map [ string ] interface {}{
2019-01-14 08:30:52 +01:00
"folder" : f . folderID ,
2019-11-11 15:50:28 +01:00
"errors" : f . Errors (),
2019-01-14 08:30:52 +01:00
})
2017-11-07 06:59:35 +00:00
}
2025-12-23 08:26:58 +01:00
// We're done if we didn't change anything and didn't fail to change
// anything
return changed == 0 && pullErrNum == 0 , nil
2017-11-07 06:59:35 +00:00
}
2014-09-28 12:00:38 +01:00
// pullerIteration runs a single puller iteration for the given folder and
2014-09-27 14:44:15 +02:00
// returns the number items that should have been synced (even those that
// might have failed). One puller iteration handles all files currently
2014-11-23 00:02:09 +00:00
// flagged as needed in the folder.
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) pullerIteration ( ctx context . Context , scanChan chan <- string ) ( int , error ) {
2020-11-06 14:22:20 +01:00
f . errorsMut . Lock ()
2020-07-28 11:13:15 +02:00
f . tempPullErrors = make ( map [ string ] string )
2020-11-06 14:22:20 +01:00
f . errorsMut . Unlock ()
2019-11-19 09:56:53 +01:00
2014-09-27 14:44:15 +02:00
pullChan := make ( chan pullBlockState )
copyChan := make ( chan copyBlocksState )
finisherChan := make ( chan * sharedPullerState )
2017-12-07 08:42:03 +00:00
dbUpdateChan := make ( chan dbUpdateJob )
2014-09-27 14:44:15 +02:00
2025-08-07 11:19:36 +02:00
var pullWg sync . WaitGroup
var copyWg sync . WaitGroup
var doneWg sync . WaitGroup
var updateWg sync . WaitGroup
2014-09-27 14:44:15 +02:00
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Starting puller iteration" , "copiers" , f . Copiers , "pullerPendingKiB" , f . PullerMaxPendingKiB )
2014-11-23 18:43:49 +00:00
2025-12-23 08:26:58 +01:00
var changed int // only read after updateWg closes
2026-02-11 10:41:38 +00:00
updateWg . Go ( func () {
2017-12-07 08:42:03 +00:00
// dbUpdaterRoutine finishes when dbUpdateChan is closed
2025-12-23 08:26:58 +01:00
changed = f . dbUpdaterRoutine ( dbUpdateChan )
2026-02-11 10:41:38 +00:00
})
2015-04-05 15:34:29 +02:00
2025-05-20 15:04:33 +02:00
for range f . Copiers {
2026-02-11 10:41:38 +00:00
copyWg . Go ( func () {
2014-09-27 14:44:15 +02:00
// copierRoutine finishes when copyChan is closed
2025-11-27 21:34:35 +01:00
f . copierRoutine ( ctx , copyChan , pullChan , finisherChan )
2026-02-11 10:41:38 +00:00
})
2014-09-27 14:44:15 +02:00
}
2026-02-11 10:41:38 +00:00
pullWg . Go ( func () {
2018-02-25 10:14:02 +01:00
// pullerRoutine finishes when pullChan is closed
2025-11-27 21:34:35 +01:00
f . pullerRoutine ( ctx , pullChan , finisherChan )
2026-02-11 10:41:38 +00:00
})
2014-09-27 14:44:15 +02:00
2014-12-24 23:12:12 +00:00
// finisherRoutine finishes when finisherChan is closed
2026-02-11 10:41:38 +00:00
doneWg . Go ( func () {
2025-11-27 21:34:35 +01:00
f . finisherRoutine ( ctx , finisherChan , dbUpdateChan , scanChan )
2026-02-11 10:41:38 +00:00
})
2014-09-27 14:44:15 +02:00
2025-12-23 08:26:58 +01:00
fileDeletions , dirDeletions , err := f . processNeeded ( ctx , dbUpdateChan , copyChan , scanChan )
2018-07-04 09:07:33 +02:00
// Signal copy and puller routines that we are done with the in data for
// this iteration. Wait for them to finish.
close ( copyChan )
copyWg . Wait ()
close ( pullChan )
pullWg . Wait ()
// Signal the finisher chan that there will be no more input and wait
// for it to finish.
close ( finisherChan )
doneWg . Wait ()
if err == nil {
2025-11-27 21:34:35 +01:00
f . processDeletions ( ctx , fileDeletions , dirDeletions , dbUpdateChan , scanChan )
2018-07-04 09:07:33 +02:00
}
// Wait for db updates and scan scheduling to complete
close ( dbUpdateChan )
updateWg . Wait ()
2020-01-08 12:21:22 +01:00
f . queue . Reset ()
2021-03-12 12:21:28 +01:00
return changed , err
2018-07-04 09:07:33 +02:00
}
2025-12-23 08:26:58 +01:00
func ( f * sendReceiveFolder ) processNeeded ( ctx context . Context , dbUpdateChan chan <- dbUpdateJob , copyChan chan <- copyBlocksState , scanChan chan <- string ) ( map [ string ] protocol . FileInfo , [] protocol . FileInfo , error ) {
2018-07-10 17:40:06 +02:00
var dirDeletions [] protocol . FileInfo
fileDeletions := map [ string ] protocol . FileInfo {}
buckets := map [ string ][] protocol . FileInfo {}
2016-12-13 10:24:10 +00:00
// Iterate the list of items that we need and sort them into piles.
// Regular files to pull goes into the file queue, everything else
// (directories, symlinks and deletes) goes into the "process directly"
// pile.
2025-03-29 12:50:08 +00:00
loop :
2025-04-06 05:30:43 -07:00
for file , err := range itererr . Zip ( f . model . sdb . AllNeededGlobalFiles ( f . folderID , protocol . LocalDeviceID , f . Order , 0 , 0 )) {
if err != nil {
2025-12-23 08:26:58 +01:00
return nil , nil , err
2025-04-06 05:30:43 -07:00
}
2018-07-04 09:07:33 +02:00
select {
2025-11-27 21:34:35 +01:00
case <- ctx . Done ():
2025-03-29 12:50:08 +00:00
break loop
2018-07-04 09:07:33 +02:00
default :
}
2024-12-01 16:50:17 +01:00
if f . IgnoreDelete && file . IsDeleted () {
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Ignoring file deletion per config" , slogutil . FilePath ( file . FileName ()))
2025-03-29 12:50:08 +00:00
continue
2016-12-13 10:24:10 +00:00
}
switch {
2024-01-13 18:58:23 +01:00
case f . ignores . Match ( file . Name ). IsIgnored ():
2021-02-08 15:30:39 +01:00
file . SetIgnored ()
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Handling ignored file" , file . LogAttr ())
2017-12-07 08:42:03 +00:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateInvalidate }
2017-11-11 19:18:17 +00:00
2022-07-28 19:36:39 +02:00
case build . IsWindows && fs . WindowsInvalidFilename ( file . Name ) != nil :
2019-06-14 08:48:14 +02:00
if file . IsDeleted () {
// Just pretend we deleted it, no reason to create an error
// about a deleted file that we can't have anyway.
// Reason we need it in the first place is, that it was
// ignored at some point.
dbUpdateChan <- dbUpdateJob { file , dbUpdateDeleteFile }
} else {
2020-09-28 10:22:50 +02:00
// We can't pull an invalid file. Grab the error again since
// we couldn't assign it directly in the case clause.
f . newPullError ( file . Name , fs . WindowsInvalidFilename ( file . Name ))
2019-06-14 08:48:14 +02:00
}
2017-12-25 17:54:34 +00:00
2016-12-13 10:24:10 +00:00
case file . IsDeleted ():
2025-06-06 11:54:40 +02:00
switch {
case file . IsDirectory ():
2018-07-10 17:40:06 +02:00
// Perform directory deletions at the end, as we may have
// files to delete inside them before we get to that point.
dirDeletions = append ( dirDeletions , file )
2025-06-06 11:54:40 +02:00
case file . IsSymlink ():
2025-03-29 12:50:08 +00:00
f . deleteFile ( file , dbUpdateChan , scanChan )
2025-06-06 11:54:40 +02:00
default :
2025-03-29 12:50:08 +00:00
df , ok , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
if err != nil {
2025-12-23 08:26:58 +01:00
return nil , nil , err
2025-03-29 12:50:08 +00:00
}
2018-07-10 17:40:06 +02:00
// Local file can be already deleted, but with a lower version
// number, hence the deletion coming in again as part of
// WithNeed, furthermore, the file can simply be of the wrong
// type if we haven't yet managed to pull it.
if ok && ! df . IsDeleted () && ! df . IsSymlink () && ! df . IsDirectory () && ! df . IsInvalid () {
2019-05-17 18:29:54 +02:00
fileDeletions [ file . Name ] = file
2018-07-10 17:40:06 +02:00
// Put files into buckets per first hash
2020-05-11 19:15:11 +01:00
key := string ( df . BlocksHash )
2018-07-10 17:40:06 +02:00
buckets [ key ] = append ( buckets [ key ], df )
2019-05-17 18:29:54 +02:00
} else {
f . deleteFileWithCurrent ( file , df , ok , dbUpdateChan , scanChan )
2018-07-10 17:40:06 +02:00
}
}
2016-12-13 10:24:10 +00:00
case file . Type == protocol . FileInfoTypeFile :
2025-03-29 12:50:08 +00:00
curFile , hasCurFile , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
if err != nil {
2025-12-23 08:26:58 +01:00
return nil , nil , err
2025-03-29 12:50:08 +00:00
}
2020-03-10 14:46:49 +01:00
if hasCurFile && file . BlocksEqual ( curFile ) {
2019-03-27 09:36:58 +01:00
// We are supposed to copy the entire file, and then fetch nothing. We
// are only updating metadata, so we don't actually *need* to make the
// copy.
2021-09-01 22:03:06 +02:00
f . shortcutFile ( file , dbUpdateChan )
2019-03-27 09:36:58 +01:00
} else {
// Queue files for processing after directories and symlinks.
f . queue . Push ( file . Name , file . Size , file . ModTime ())
}
2017-11-11 19:18:17 +00:00
2024-10-26 14:59:38 +05:30
case ( build . IsWindows || build . IsAndroid ) && file . IsSymlink ():
2025-03-29 12:50:08 +00:00
if err := f . handleSymlinkCheckExisting ( file , scanChan ); err != nil {
2020-09-10 10:52:38 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "handling unsupported symlink: %w" , err ))
break
}
2021-02-08 15:30:39 +01:00
file . SetUnsupported ()
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Invalidating unsupported symlink" , slogutil . FilePath ( file . Name ))
2017-12-07 08:42:03 +00:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateInvalidate }
2016-12-13 10:24:10 +00:00
2019-03-27 09:36:58 +01:00
case file . IsDirectory () && ! file . IsSymlink ():
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Handling directory" , slogutil . FilePath ( file . Name ))
2019-03-27 09:36:58 +01:00
if f . checkParent ( file . Name , scanChan ) {
2025-03-29 12:50:08 +00:00
f . handleDir ( file , dbUpdateChan , scanChan )
2019-03-27 09:36:58 +01:00
}
case file . IsSymlink ():
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Handling symlink" , slogutil . FilePath ( file . Name ))
2019-03-27 09:36:58 +01:00
if f . checkParent ( file . Name , scanChan ) {
2025-03-29 12:50:08 +00:00
f . handleSymlink ( file , dbUpdateChan , scanChan )
2019-03-27 09:36:58 +01:00
}
default :
panic ( "unhandleable item type, can't happen" )
2016-12-13 10:24:10 +00:00
}
2025-03-29 12:50:08 +00:00
}
2016-12-13 10:24:10 +00:00
2018-07-04 09:07:33 +02:00
select {
2025-11-27 21:34:35 +01:00
case <- ctx . Done ():
2025-12-23 08:26:58 +01:00
return nil , nil , ctx . Err ()
2018-07-04 09:07:33 +02:00
default :
}
2016-12-13 10:24:10 +00:00
// Process the file queue.
2015-04-25 14:13:53 +09:00
2014-12-19 23:12:12 +00:00
nextFile :
2014-12-01 19:23:06 +00:00
for {
2016-01-16 21:42:32 +01:00
select {
2025-11-27 21:34:35 +01:00
case <- ctx . Done ():
2025-12-23 08:26:58 +01:00
return fileDeletions , dirDeletions , ctx . Err ()
2016-01-16 21:42:32 +01:00
default :
}
2016-04-26 14:01:46 +00:00
fileName , ok := f . queue . Pop ()
2014-12-30 09:31:34 +01:00
if ! ok {
2014-12-01 19:23:06 +00:00
break
}
2014-12-19 23:12:12 +00:00
2025-03-29 12:50:08 +00:00
fi , ok , err := f . model . sdb . GetGlobalFile ( f . folderID , fileName )
if err != nil {
2025-12-23 08:26:58 +01:00
return nil , nil , err
2025-03-29 12:50:08 +00:00
}
2014-12-19 23:12:12 +00:00
if ! ok {
2015-01-06 22:12:45 +01:00
// File is no longer in the index. Mark it as done and drop it.
2016-04-26 14:01:46 +00:00
f . queue . Done ( fileName )
2014-12-19 23:12:12 +00:00
continue
}
2018-10-26 20:13:35 +02:00
if fi . IsDeleted () || fi . IsInvalid () || fi . Type != protocol . FileInfoTypeFile {
2016-12-13 10:24:10 +00:00
// The item has changed type or status in the index while we
// were processing directories above.
f . queue . Done ( fileName )
continue
}
2018-07-10 17:40:06 +02:00
if ! f . checkParent ( fi . Name , scanChan ) {
2018-08-19 23:34:26 +02:00
f . queue . Done ( fileName )
2016-01-16 17:18:37 +00:00
continue
}
2016-12-09 18:02:18 +00:00
// Check our list of files to be removed for a match, in which case
// we can just do a rename instead.
2020-05-11 19:15:11 +01:00
key := string ( fi . BlocksHash )
2020-05-17 09:48:35 +02:00
for candidate , ok := popCandidate ( buckets , key ); ok ; candidate , ok = popCandidate ( buckets , key ) {
2020-05-11 19:15:11 +01:00
// candidate is our current state of the file, where as the
// desired state with the delete bit set is in the deletion
// map.
desired := fileDeletions [ candidate . Name ]
2025-03-29 12:50:08 +00:00
if err := f . renameFile ( candidate , desired , fi , dbUpdateChan , scanChan ); err != nil {
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Rename shortcut failed" , slogutil . FilePath ( fi . Name ), slogutil . Error ( err ))
2020-05-11 19:15:11 +01:00
// Failed to rename, try next one.
continue
}
2016-12-09 18:02:18 +00:00
2020-05-11 19:15:11 +01:00
// Remove the pending deletion (as we performed it by renaming)
delete ( fileDeletions , candidate . Name )
2016-12-09 18:02:18 +00:00
2020-05-11 19:15:11 +01:00
f . queue . Done ( fileName )
continue nextFile
2015-01-06 22:12:45 +01:00
}
2014-12-19 23:12:12 +00:00
2025-09-04 16:53:30 +00:00
// Verify there is some availability for the file before we start
// processing it
2025-03-29 12:50:08 +00:00
devices := f . model . fileAvailability ( f . FolderConfiguration , fi )
2025-09-04 16:53:30 +00:00
if len ( devices ) == 0 {
f . newPullError ( fileName , errNotAvailable )
f . queue . Done ( fileName )
continue
}
// Verify we have space to handle the file before we start
// creating temp files etc.
if err := f . CheckAvailableSpace ( uint64 ( fi . Size )); err != nil { //nolint:gosec
f . newPullError ( fileName , err )
f . queue . Done ( fileName )
2024-11-12 07:51:52 +00:00
continue
2018-08-19 20:03:20 +02:00
}
2025-09-04 16:53:30 +00:00
2025-11-27 21:34:35 +01:00
if err := f . handleFile ( ctx , fi , copyChan ); err != nil {
2025-09-04 16:53:30 +00:00
f . newPullError ( fileName , err )
}
2014-12-01 19:23:06 +00:00
}
2025-12-23 08:26:58 +01:00
return fileDeletions , dirDeletions , nil
2018-07-04 09:07:33 +02:00
}
2014-05-19 22:31:28 +02:00
2020-05-17 09:48:35 +02:00
func popCandidate ( buckets map [ string ][] protocol . FileInfo , key string ) ( protocol . FileInfo , bool ) {
cands := buckets [ key ]
if len ( cands ) == 0 {
return protocol . FileInfo {}, false
}
buckets [ key ] = cands [ 1 :]
return cands [ 0 ], true
}
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) processDeletions ( ctx context . Context , fileDeletions map [ string ] protocol . FileInfo , dirDeletions [] protocol . FileInfo , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) {
2014-12-19 23:12:12 +00:00
for _ , file := range fileDeletions {
2018-07-04 09:07:33 +02:00
select {
2025-11-27 21:34:35 +01:00
case <- ctx . Done ():
2018-07-04 09:07:33 +02:00
return
default :
}
2025-03-29 12:50:08 +00:00
f . deleteFile ( file , dbUpdateChan , scanChan )
2014-12-19 23:12:12 +00:00
}
2019-05-06 20:55:26 +02:00
// Process in reverse order to delete depth first
for i := range dirDeletions {
2018-07-04 09:07:33 +02:00
select {
2025-11-27 21:34:35 +01:00
case <- ctx . Done ():
2018-07-04 09:07:33 +02:00
return
default :
}
2019-05-06 20:55:26 +02:00
dir := dirDeletions [ len ( dirDeletions ) - i - 1 ]
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Deleting directory" , slogutil . FilePath ( dir . Name ))
2025-03-29 12:50:08 +00:00
f . deleteDir ( dir , dbUpdateChan , scanChan )
2014-10-12 22:01:57 +01:00
}
2014-09-27 14:44:15 +02:00
}
2014-04-01 23:18:32 +02:00
2014-09-27 14:44:15 +02:00
// handleDir creates or updates the given directory
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) handleDir ( file protocol . FileInfo , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) {
2016-12-09 18:02:18 +00:00
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
2015-02-01 17:31:19 +00:00
var err error
2016-12-09 18:02:18 +00:00
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemStarted , map [ string ] string {
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-04-14 20:59:06 +09:00
"item" : file . Name ,
"type" : "dir" ,
"action" : "update" ,
2015-02-01 17:31:19 +00:00
})
2015-04-14 20:59:06 +09:00
2015-02-01 17:31:19 +00:00
defer func () {
2025-08-07 11:19:36 +02:00
slog . Info ( "Created or updated directory" , f . LogAttr (), file . LogAttr ())
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemFinished , map [ string ] interface {}{
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-02-01 17:31:19 +00:00
"item" : file . Name ,
2015-05-27 11:14:39 +02:00
"error" : events . Error ( err ),
2015-04-14 20:59:06 +09:00
"type" : "dir" ,
"action" : "update" ,
2015-02-01 17:31:19 +00:00
})
}()
2023-08-04 19:57:30 +02:00
mode := fs . FileMode ( file . Permissions & 0 o777 )
2018-02-25 09:39:00 +01:00
if f . IgnorePerms || file . NoPermissions {
2023-08-04 19:57:30 +02:00
mode = 0 o777
2014-10-10 00:34:32 +02:00
}
2014-05-19 22:31:28 +02:00
2025-08-07 11:19:36 +02:00
f . sl . Debug ( "Need dir" , "file" , file , "cur" , slogutil . Expensive ( func () any {
2025-03-29 12:50:08 +00:00
curFile , _ , _ := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
2025-08-07 11:19:36 +02:00
return curFile
}))
2020-12-27 22:26:25 +01:00
info , err := f . mtimefs . Lstat ( file . Name )
2014-11-13 22:59:40 +00:00
switch {
2019-03-07 15:15:14 +01:00
// There is already something under that name, we need to handle that.
// Unless it already is a directory, as we only track permissions,
// that don't result in a conflict.
case err == nil && ! info . IsDir ():
// Check that it is what we have in the database.
2025-03-29 12:50:08 +00:00
curFile , hasCurFile , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
if err != nil {
f . newPullError ( file . Name , fmt . Errorf ( "handling dir: %w" , err ))
return
}
2024-03-02 09:55:18 -05:00
if err := f . scanIfItemChanged ( file . Name , info , curFile , hasCurFile , false , scanChan ); err != nil {
2022-09-14 09:50:55 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "handling dir: %w" , err ))
2019-03-07 15:15:14 +01:00
return
}
// Remove it to replace with the dir.
2025-09-13 14:16:28 +00:00
if ! curFile . IsSymlink () && file . InConflictWith ( curFile ) {
2019-03-07 15:15:14 +01:00
// The new file has been changed in conflict with the existing one. We
// should file it away as a conflict instead of just removing or
2020-10-19 08:53:19 +02:00
// archiving.
2019-03-07 15:15:14 +01:00
// Symlinks aren't checked for conflicts.
2019-07-31 10:53:35 +02:00
err = f . inWritableDir ( func ( name string ) error {
2019-03-07 15:15:14 +01:00
return f . moveForConflict ( name , file . ModifiedBy . String (), scanChan )
2019-07-31 10:53:35 +02:00
}, curFile . Name )
2019-03-07 15:15:14 +01:00
} else {
2025-03-29 12:50:08 +00:00
err = f . deleteItemOnDisk ( curFile , scanChan )
2019-03-07 15:15:14 +01:00
}
2014-11-13 22:59:40 +00:00
if err != nil {
2019-03-07 15:15:14 +01:00
f . newPullError ( file . Name , err )
2014-09-28 01:54:25 +02:00
return
}
2014-11-13 22:59:40 +00:00
fallthrough
// The directory doesn't exist, so we create it with the right
// mode bits from the start.
2017-08-19 14:36:56 +00:00
case err != nil && fs . IsNotExist ( err ):
2014-11-13 22:59:40 +00:00
// We declare a function that acts on only the path name, so
// we can pass it to InWritableDir. We use a regular Mkdir and
// not MkdirAll because the parent should already exist.
mkdir := func ( path string ) error {
2020-12-27 22:26:25 +01:00
err = f . mtimefs . Mkdir ( path , mode )
2023-05-17 02:06:50 -05:00
if err != nil {
2015-03-23 21:31:53 +09:00
return err
}
2015-07-03 10:25:35 +01:00
2022-11-09 06:54:04 +01:00
// Set the platform data (ownership, xattrs, etc).
if err := f . setPlatformData ( & file , path ); err != nil {
2019-01-25 09:52:21 +01:00
return err
}
2023-05-17 02:06:50 -05:00
if f . IgnorePerms || file . NoPermissions {
2023-05-02 04:11:39 -05:00
return nil
}
2015-07-03 10:25:35 +01:00
// Stat the directory so we can check its permissions.
2020-12-27 22:26:25 +01:00
info , err := f . mtimefs . Lstat ( path )
2015-07-03 10:25:35 +01:00
if err != nil {
return err
}
// Mask for the bits we want to preserve and add them in to the
// directories permissions.
2020-12-27 22:26:25 +01:00
return f . mtimefs . Chmod ( path , mode |( info . Mode () & retainBits ))
2014-11-13 22:59:40 +00:00
}
2014-09-28 01:54:25 +02:00
2019-07-31 10:53:35 +02:00
if err = f . inWritableDir ( mkdir , file . Name ); err == nil {
2017-12-07 08:42:03 +00:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateHandleDir }
2014-11-13 22:59:40 +00:00
} else {
2022-08-16 10:01:49 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "creating directory: %w" , err ))
2014-11-13 22:59:40 +00:00
}
2014-09-28 01:54:25 +02:00
return
2014-11-13 22:59:40 +00:00
// Weird error when stat()'ing the dir. Probably won't work to do
// anything else with it if we can't even stat() it.
case err != nil :
2022-08-16 10:01:49 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "checking file to be replaced: %w" , err ))
2014-03-28 14:36:57 +01:00
return
}
2022-11-09 06:54:04 +01:00
// The directory already exists, so we just correct the metadata. (We
2014-09-28 01:54:25 +02:00
// don't handle modification times on directories, because that sucks...)
// It's OK to change mode bits on stuff within non-writable directories.
2019-03-04 14:01:52 +01:00
if ! f . IgnorePerms && ! file . NoPermissions {
2020-12-27 22:26:25 +01:00
if err := f . mtimefs . Chmod ( file . Name , mode |( info . Mode () & retainBits )); err != nil {
2022-09-14 09:50:55 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "handling dir (setting permissions): %w" , err ))
2019-03-04 14:01:52 +01:00
return
}
2022-11-09 06:54:04 +01:00
if err := f . setPlatformData ( & file , file . Name ); err != nil {
f . newPullError ( file . Name , fmt . Errorf ( "handling dir (setting metadata): %w" , err ))
return
}
2014-07-15 13:04:37 +02:00
}
2019-03-04 14:01:52 +01:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateHandleDir }
2014-09-27 14:44:15 +02:00
}
2014-03-28 14:36:57 +01:00
2018-07-10 17:40:06 +02:00
// checkParent verifies that the thing we are handling lives inside a directory,
// and not a symlink or regular file. It also resurrects missing parent dirs.
func ( f * sendReceiveFolder ) checkParent ( file string , scanChan chan <- string ) bool {
parent := filepath . Dir ( file )
2020-12-27 22:26:25 +01:00
if err := osutil . TraversesSymlink ( f . mtimefs , parent ); err != nil {
2022-08-16 10:01:49 +02:00
f . newPullError ( file , fmt . Errorf ( "checking parent dirs: %w" , err ))
2018-07-10 17:40:06 +02:00
return false
}
// issues #114 and #4475: This works around a race condition
// between two devices, when one device removes a directory and the
// other creates a file in it. However that happens, we end up with
// a directory for "foo" with the delete bit, but a file "foo/bar"
// that we want to sync. We never create the directory, and hence
// fail to create the file and end up looping forever on it. This
// breaks that by creating the directory and scheduling a scan,
// where it will be found and the delete bit on it removed. The
// user can then clean up as they like...
// This can also occur if an entire tree structure was deleted, but only
// a leave has been scanned.
2020-11-09 15:33:32 +01:00
//
// And if this is an encrypted folder:
// Encrypted files have made-up filenames with two synthetic parent
// directories which don't have any meaning. Create those if necessary.
2020-12-27 22:26:25 +01:00
if _ , err := f . mtimefs . Lstat ( parent ); ! fs . IsNotExist ( err ) {
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Parent directory exists" , slogutil . FilePath ( file ))
2018-07-10 17:40:06 +02:00
return true
}
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Creating parent directory" , slogutil . FilePath ( file ))
2023-08-04 19:57:30 +02:00
if err := f . mtimefs . MkdirAll ( parent , 0 o755 ); err != nil {
2022-08-16 10:01:49 +02:00
f . newPullError ( file , fmt . Errorf ( "creating parent dir: %w" , err ))
2018-07-10 17:40:06 +02:00
return false
}
2020-11-09 15:33:32 +01:00
if f . Type != config . FolderTypeReceiveEncrypted {
scanChan <- parent
}
2018-07-10 17:40:06 +02:00
return true
}
2016-12-09 18:02:18 +00:00
// handleSymlink creates or updates the given symlink
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) handleSymlink ( file protocol . FileInfo , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) {
2016-12-09 18:02:18 +00:00
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemStarted , map [ string ] string {
2016-12-09 18:02:18 +00:00
"folder" : f . folderID ,
"item" : file . Name ,
"type" : "symlink" ,
"action" : "update" ,
})
defer func () {
2025-08-07 11:19:36 +02:00
if err != nil {
slog . Warn ( "Failed to handle symlink" , f . LogAttr (), file . LogAttr (), slogutil . Error ( err ))
} else {
slog . Info ( "Created or updated symlink" , f . LogAttr (), file . LogAttr ())
}
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemFinished , map [ string ] interface {}{
2016-12-09 18:02:18 +00:00
"folder" : f . folderID ,
"item" : file . Name ,
"error" : events . Error ( err ),
"type" : "symlink" ,
"action" : "update" ,
})
}()
2025-08-07 11:19:36 +02:00
f . sl . Debug ( "Need symlink" , slogutil . FilePath ( file . Name ), slog . Any ( "cur" , slogutil . Expensive ( func () any {
curFile , _ , _ := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
return curFile
})))
2016-12-09 18:02:18 +00:00
2025-01-11 17:38:29 +01:00
if len ( file . SymlinkTarget ) == 0 {
2016-12-09 18:02:18 +00:00
// Index entry from a Syncthing predating the support for including
// the link target in the index entry. We log this as an error.
2019-03-04 14:01:52 +01:00
f . newPullError ( file . Name , errIncompatibleSymlink )
2016-12-09 18:02:18 +00:00
return
}
2025-03-29 12:50:08 +00:00
if err = f . handleSymlinkCheckExisting ( file , scanChan ); err != nil {
2020-09-10 10:52:38 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "handling symlink: %w" , err ))
2020-07-28 11:13:15 +02:00
return
2016-12-09 18:02:18 +00:00
}
// We declare a function that acts on only the path name, so
// we can pass it to InWritableDir.
createLink := func ( path string ) error {
2025-01-11 17:38:29 +01:00
if err := f . mtimefs . CreateSymlink ( string ( file . SymlinkTarget ), path ); err != nil {
2019-01-25 09:52:21 +01:00
return err
}
2022-11-09 06:54:04 +01:00
return f . setPlatformData ( & file , path )
2016-12-09 18:02:18 +00:00
}
2019-07-31 10:53:35 +02:00
if err = f . inWritableDir ( createLink , file . Name ); err == nil {
2017-12-07 08:42:03 +00:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateHandleSymlink }
2016-12-09 18:02:18 +00:00
} else {
2022-08-16 10:01:49 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "symlink create: %w" , err ))
2016-12-09 18:02:18 +00:00
}
}
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) handleSymlinkCheckExisting ( file protocol . FileInfo , scanChan chan <- string ) error {
2020-09-10 10:52:38 +02:00
// If there is already something under that name, we need to handle that.
2020-12-27 22:26:25 +01:00
info , err := f . mtimefs . Lstat ( file . Name )
2020-09-10 10:52:38 +02:00
if err != nil {
if fs . IsNotExist ( err ) {
return nil
}
return err
}
// Check that it is what we have in the database.
2025-03-29 12:50:08 +00:00
curFile , hasCurFile , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
if err != nil {
return err
}
2024-03-02 09:55:18 -05:00
if err := f . scanIfItemChanged ( file . Name , info , curFile , hasCurFile , false , scanChan ); err != nil {
2020-09-10 10:52:38 +02:00
return err
}
// Remove it to replace with the symlink. This also handles the
// "change symlink type" path.
2025-09-13 14:16:28 +00:00
if ! curFile . IsDirectory () && ! curFile . IsSymlink () && file . InConflictWith ( curFile ) {
2020-09-10 10:52:38 +02:00
// The new file has been changed in conflict with the existing one. We
// should file it away as a conflict instead of just removing or
2020-10-19 08:53:19 +02:00
// archiving.
2020-09-10 10:52:38 +02:00
// Directories and symlinks aren't checked for conflicts.
return f . inWritableDir ( func ( name string ) error {
return f . moveForConflict ( name , file . ModifiedBy . String (), scanChan )
}, curFile . Name )
} else {
2025-03-29 12:50:08 +00:00
return f . deleteItemOnDisk ( curFile , scanChan )
2020-09-10 10:52:38 +02:00
}
}
2019-03-07 15:15:14 +01:00
// deleteDir attempts to remove a directory that was deleted on a remote
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) deleteDir ( file protocol . FileInfo , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) {
2016-12-09 18:02:18 +00:00
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
2015-02-01 17:31:19 +00:00
var err error
2016-12-09 18:02:18 +00:00
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemStarted , map [ string ] string {
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-04-14 20:59:06 +09:00
"item" : file . Name ,
"type" : "dir" ,
"action" : "delete" ,
2015-02-01 17:31:19 +00:00
})
2016-12-09 18:02:18 +00:00
2015-02-01 17:31:19 +00:00
defer func () {
2020-06-16 15:20:08 +02:00
if err != nil {
2022-08-16 10:01:49 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "delete dir: %w" , err ))
2025-08-07 11:19:36 +02:00
slog . Info ( "Failed to delete directory" , f . LogAttr (), file . LogAttr (), slogutil . Error ( err ))
} else {
slog . Info ( "Deleted directory" , f . LogAttr (), file . LogAttr ())
2020-06-16 15:20:08 +02:00
}
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemFinished , map [ string ] interface {}{
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-02-01 17:31:19 +00:00
"item" : file . Name ,
2015-05-27 11:14:39 +02:00
"error" : events . Error ( err ),
2015-04-14 20:59:06 +09:00
"type" : "dir" ,
"action" : "delete" ,
2015-02-01 17:31:19 +00:00
})
}()
2025-03-29 12:50:08 +00:00
cur , hasCur , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
if err != nil {
return
}
2020-06-16 15:20:08 +02:00
2020-09-12 07:45:50 +02:00
if err = f . checkToBeDeleted ( file , cur , hasCur , scanChan ); err != nil {
if fs . IsNotExist ( err ) || fs . IsErrCaseConflict ( err ) {
2020-06-16 15:20:08 +02:00
err = nil
2020-09-12 07:45:50 +02:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateDeleteDir }
2020-06-16 15:20:08 +02:00
}
return
}
2025-03-29 12:50:08 +00:00
if err = f . deleteDirOnDisk ( file . Name , scanChan ); err != nil {
2017-12-07 08:42:03 +00:00
return
2014-03-28 14:36:57 +01:00
}
2017-12-07 08:42:03 +00:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateDeleteDir }
2014-03-28 14:36:57 +01:00
}
2014-09-27 14:44:15 +02:00
// deleteFile attempts to delete the given file
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) deleteFile ( file protocol . FileInfo , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) {
cur , hasCur , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
if err != nil {
f . newPullError ( file . Name , fmt . Errorf ( "delete file: %w" , err ))
return
}
2019-05-17 18:29:54 +02:00
f . deleteFileWithCurrent ( file , cur , hasCur , dbUpdateChan , scanChan )
}
func ( f * sendReceiveFolder ) deleteFileWithCurrent ( file , cur protocol . FileInfo , hasCur bool , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) {
2016-12-09 18:02:18 +00:00
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
2015-02-01 17:31:19 +00:00
var err error
2016-12-09 18:02:18 +00:00
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Deleting file or symlink" , slogutil . FilePath ( file . Name ))
2019-05-17 18:29:54 +02:00
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemStarted , map [ string ] string {
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-04-14 20:59:06 +09:00
"item" : file . Name ,
"type" : "file" ,
"action" : "delete" ,
2015-02-01 17:31:19 +00:00
})
2016-12-09 18:02:18 +00:00
2015-02-01 17:31:19 +00:00
defer func () {
2025-08-07 11:19:36 +02:00
kind := "file"
if file . IsSymlink () {
kind = "symlink"
}
2019-05-17 18:29:54 +02:00
if err != nil {
2022-08-16 10:01:49 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "delete file: %w" , err ))
2025-08-07 11:19:36 +02:00
slog . Info ( "Failed to delete " + kind , f . LogAttr (), file . LogAttr (), slogutil . Error ( err ))
} else {
slog . Info ( "Deleted " + kind , f . LogAttr (), file . LogAttr ())
2019-05-17 18:29:54 +02:00
}
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemFinished , map [ string ] interface {}{
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-02-01 17:31:19 +00:00
"item" : file . Name ,
2015-05-27 11:14:39 +02:00
"error" : events . Error ( err ),
2015-04-14 20:59:06 +09:00
"type" : "file" ,
"action" : "delete" ,
2015-02-01 17:31:19 +00:00
})
}()
2020-09-12 07:45:50 +02:00
if err = f . checkToBeDeleted ( file , cur , hasCur , scanChan ); err != nil {
if fs . IsNotExist ( err ) || fs . IsErrCaseConflict ( err ) {
2020-06-16 15:20:08 +02:00
err = nil
2020-09-12 07:45:50 +02:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateDeleteFile }
2020-06-16 15:20:08 +02:00
}
2019-05-17 18:29:54 +02:00
return
2018-09-16 09:48:14 +02:00
}
2019-03-07 15:15:14 +01:00
// We are asked to delete a file, but what we have on disk and in db
// is a directory. Something is wrong here, should probably not happen.
if cur . IsDirectory () {
2019-05-17 18:29:54 +02:00
err = errUnexpectedDirOnFileDel
return
2019-03-07 15:15:14 +01:00
}
2025-07-06 15:22:03 +02:00
switch {
2025-09-13 14:16:28 +00:00
case file . InConflictWith ( cur ) && ! cur . IsSymlink ():
2025-07-06 15:22:03 +02:00
// If the delete constitutes winning a conflict, we move the file to
// a conflict copy instead of doing the delete
err = f . inWritableDir ( func ( name string ) error {
return f . moveForConflict ( name , file . ModifiedBy . String (), scanChan )
}, cur . Name )
2019-03-07 15:15:14 +01:00
2025-07-06 15:22:03 +02:00
case f . versioner != nil && ! cur . IsSymlink ():
// If we have a versioner, use that to move the file away
2019-07-31 10:53:35 +02:00
err = f . inWritableDir ( f . versioner . Archive , file . Name )
2025-07-06 15:22:03 +02:00
default :
// Delete the file
2020-12-27 22:26:25 +01:00
err = f . inWritableDir ( f . mtimefs . Remove , file . Name )
2014-09-27 14:44:15 +02:00
}
2014-07-13 21:07:24 +02:00
2017-08-19 14:36:56 +00:00
if err == nil || fs . IsNotExist ( err ) {
2015-05-23 23:55:50 +02:00
// It was removed or it doesn't exist to start with
2019-05-17 18:29:54 +02:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateDeleteFile }
return
2018-07-12 11:15:57 +03:00
}
2020-12-27 22:26:25 +01:00
if _ , serr := f . mtimefs . Lstat ( file . Name ); serr != nil && ! fs . IsPermission ( serr ) {
2015-05-23 23:55:50 +02:00
// We get an error just looking at the file, and it's not a permission
// problem. Lets assume the error is in fact some variant of "file
// does not exist" (possibly expressed as some parent being a file and
// not a directory etc) and that the delete is handled.
2018-09-16 09:48:14 +02:00
err = nil
2019-05-17 18:29:54 +02:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateDeleteFile }
2014-05-28 11:45:45 +02:00
}
2025-08-07 11:19:36 +02:00
slog . Info ( "Deleted file" , f . LogAttr (), file . LogAttr ())
2014-09-27 14:44:15 +02:00
}
2014-05-28 11:45:45 +02:00
2014-12-19 23:12:12 +00:00
// renameFile attempts to rename an existing file to a destination
// and set the right attributes on it.
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) renameFile ( cur , source , target protocol . FileInfo , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) error {
2016-12-09 18:02:18 +00:00
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
2015-02-01 17:31:19 +00:00
var err error
2016-12-09 18:02:18 +00:00
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemStarted , map [ string ] string {
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-04-14 20:59:06 +09:00
"item" : source . Name ,
"type" : "file" ,
"action" : "delete" ,
2015-02-01 17:31:19 +00:00
})
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemStarted , map [ string ] string {
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-04-14 20:59:06 +09:00
"item" : target . Name ,
"type" : "file" ,
"action" : "update" ,
2015-02-01 17:31:19 +00:00
})
2016-12-09 18:02:18 +00:00
2015-02-01 17:31:19 +00:00
defer func () {
2025-08-07 11:19:36 +02:00
if err != nil {
slog . Info ( "Failed to rename file" , f . LogAttr (), target . LogAttr (), slog . String ( "from" , source . Name ), slogutil . Error ( err ))
} else {
slog . Info ( "Renamed file" , f . LogAttr (), target . LogAttr (), slog . String ( "from" , source . Name ))
}
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemFinished , map [ string ] interface {}{
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-02-01 17:31:19 +00:00
"item" : source . Name ,
2015-05-27 11:14:39 +02:00
"error" : events . Error ( err ),
2015-04-14 20:59:06 +09:00
"type" : "file" ,
"action" : "delete" ,
2015-02-01 17:31:19 +00:00
})
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemFinished , map [ string ] interface {}{
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-02-01 17:31:19 +00:00
"item" : target . Name ,
2015-05-27 11:14:39 +02:00
"error" : events . Error ( err ),
2015-04-14 20:59:06 +09:00
"type" : "file" ,
"action" : "update" ,
2015-02-01 17:31:19 +00:00
})
}()
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Taking rename shortcut" , "from" , source . Name , "to" , target . Name )
2014-12-19 23:12:12 +00:00
2018-09-16 09:48:14 +02:00
// Check that source is compatible with what we have in the DB
2020-09-12 07:45:50 +02:00
if err = f . checkToBeDeleted ( source , cur , true , scanChan ); err != nil {
2018-10-10 11:37:20 +02:00
return err
2018-09-16 09:48:14 +02:00
}
// Check that the target corresponds to what we have in the DB
2025-03-29 12:50:08 +00:00
curTarget , ok , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , target . Name )
if err != nil {
return err
}
2020-12-27 22:26:25 +01:00
switch stat , serr := f . mtimefs . Lstat ( target . Name ); {
2018-09-16 09:48:14 +02:00
case serr != nil :
2025-06-06 12:03:28 +02:00
var caseErr * fs . CaseConflictError
2020-09-10 08:23:54 +02:00
switch {
case errors . As ( serr , & caseErr ):
if caseErr . Real != source . Name {
err = serr
break
}
fallthrough // This is a case only rename
case fs . IsNotExist ( serr ):
if ! ok || curTarget . IsDeleted () {
break
}
scanChan <- target . Name
err = errModified
default :
// We can't check whether the file changed as compared to the db,
// do not delete.
err = serr
}
2018-09-16 09:48:14 +02:00
case ! ok :
// Target appeared from nowhere
scanChan <- target . Name
err = errModified
default :
2019-02-02 10:11:42 +01:00
var fi protocol . FileInfo
2022-09-14 09:50:55 +02:00
if fi , err = scanner . CreateFileInfo ( stat , target . Name , f . mtimefs , f . SyncOwnership , f . SyncXattrs , f . XattrFilter ); err == nil {
2022-07-26 08:24:58 +02:00
if ! fi . IsEquivalentOptional ( curTarget , protocol . FileInfoComparison {
ModTimeWindow : f . modTimeWindow ,
IgnorePerms : f . IgnorePerms ,
IgnoreBlocks : true ,
IgnoreFlags : protocol . LocalAllFlags ,
IgnoreOwnership : ! f . SyncOwnership ,
2022-09-14 09:50:55 +02:00
IgnoreXattrs : ! f . SyncXattrs ,
2022-07-26 08:24:58 +02:00
}) {
2018-09-16 09:48:14 +02:00
// Target changed
scanChan <- target . Name
err = errModified
}
}
}
if err != nil {
2018-10-10 11:37:20 +02:00
return err
2018-09-16 09:48:14 +02:00
}
tempName := fs . TempName ( target . Name )
2016-04-26 14:01:46 +00:00
if f . versioner != nil {
2025-06-06 11:54:40 +02:00
err = f . CheckAvailableSpace ( uint64 ( source . Size )) //nolint:gosec
2014-12-19 23:12:12 +00:00
if err == nil {
2024-12-01 16:50:17 +01:00
err = osutil . Copy ( f . CopyRangeMethod . ToFS (), f . mtimefs , f . mtimefs , source . Name , tempName )
2018-08-25 10:16:38 +02:00
if err == nil {
2019-07-31 10:53:35 +02:00
err = f . inWritableDir ( f . versioner . Archive , source . Name )
2018-08-25 10:16:38 +02:00
}
2014-12-19 23:12:12 +00:00
}
} else {
2024-12-01 16:50:17 +01:00
err = osutil . RenameOrCopy ( f . CopyRangeMethod . ToFS (), f . mtimefs , f . mtimefs , source . Name , tempName )
2014-12-19 23:12:12 +00:00
}
2018-10-10 11:37:20 +02:00
if err != nil {
return err
}
2014-12-19 23:12:12 +00:00
2018-10-10 11:37:20 +02:00
blockStatsMut . Lock ()
2019-10-04 12:03:13 +01:00
minBlocksPerBlock := target . BlockSize () / protocol . MinBlockSize
blockStats [ "total" ] += len ( target . Blocks ) * minBlocksPerBlock
blockStats [ "renamed" ] += len ( target . Blocks ) * minBlocksPerBlock
2018-10-10 11:37:20 +02:00
blockStatsMut . Unlock ()
2014-12-19 23:12:12 +00:00
2018-10-10 11:37:20 +02:00
// The file was renamed, so we have handled both the necessary delete
// of the source and the creation of the target temp file. Fix-up the metadata,
// update the local index of the target file and rename from temp to real name.
2015-03-01 10:46:28 +01:00
2025-03-29 12:50:08 +00:00
if err = f . performFinish ( target , curTarget , true , tempName , dbUpdateChan , scanChan ); err != nil {
2018-10-10 11:37:20 +02:00
return err
}
2014-12-19 23:12:12 +00:00
2018-10-10 11:37:20 +02:00
dbUpdateChan <- dbUpdateJob { source , dbUpdateDeleteFile }
2015-03-01 10:46:28 +01:00
2018-10-10 11:37:20 +02:00
return nil
2014-12-19 23:12:12 +00:00
}
2015-05-27 11:14:39 +02:00
// This is the flow of data and events here, I think...
//
// +-----------------------+
// | | - - - - > ItemStarted
// | handleFile | - - - - > ItemFinished (on shortcuts)
// | |
// +-----------------------+
// |
// | copyChan (copyBlocksState; unless shortcut taken)
// |
// | +-----------------------+
// | | +-----------------------+
// +--->| | |
// | | copierRoutine |
// +-| |
// +-----------------------+
// |
// | pullChan (sharedPullerState)
// |
// | +-----------------------+
// | | +-----------------------+
// +-->| | |
// | | pullerRoutine |
// +-| |
// +-----------------------+
// |
// | finisherChan (sharedPullerState)
// |
// | +-----------------------+
// | | |
// +-->| finisherRoutine | - - - - > ItemFinished
// | |
// +-----------------------+
2014-09-27 14:44:15 +02:00
// handleFile queues the copies and pulls as necessary for a single new or
// changed file.
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) handleFile ( ctx context . Context , file protocol . FileInfo , copyChan chan <- copyBlocksState ) error {
2025-03-29 12:50:08 +00:00
curFile , hasCurFile , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , file . Name )
if err != nil {
return err
}
2014-03-28 14:36:57 +01:00
2019-03-27 09:36:58 +01:00
have , _ := blockDiff ( curFile . Blocks , file . Blocks )
2014-03-28 14:36:57 +01:00
2017-09-02 05:52:38 +00:00
tempName := fs . TempName ( file . Name )
2015-10-29 09:08:03 +01:00
2018-01-14 14:30:11 +00:00
populateOffsets ( file . Blocks )
2014-10-17 23:16:29 +01:00
2021-08-04 11:10:10 +02:00
blocks := append ([] protocol . BlockInfo {}, file . Blocks ... )
2020-10-02 07:07:05 +01:00
reused := make ([] int , 0 , len ( file . Blocks ))
2014-10-03 23:15:54 +01:00
2021-08-04 11:10:10 +02:00
if f . Type != config . FolderTypeReceiveEncrypted {
2025-11-27 21:34:35 +01:00
blocks , reused = f . reuseBlocks ( ctx , blocks , reused , file , tempName )
2020-09-09 11:47:14 +02:00
}
2014-10-03 23:15:54 +01:00
2021-08-04 11:10:10 +02:00
// The sharedpullerstate will know which flags to use when opening the
// temp file depending if we are reusing any blocks or not.
if len ( reused ) == 0 {
// Otherwise, discard the file ourselves in order for the
// sharedpuller not to panic when it fails to exclusively create a
// file which already exists
f . inWritableDir ( f . mtimefs . Remove , tempName )
2015-12-21 13:29:18 -05:00
}
2020-05-11 22:44:04 +01:00
// Reorder blocks
blocks = f . blockPullReorderer . Reorder ( blocks )
2016-04-15 10:59:41 +00:00
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemStarted , map [ string ] string {
2016-04-26 14:01:46 +00:00
"folder" : f . folderID ,
2015-12-21 13:29:18 -05:00
"item" : file . Name ,
"type" : "file" ,
"action" : "update" ,
})
2020-12-27 22:26:25 +01:00
s := newSharedPullerState ( file , f . mtimefs , f . folderID , tempName , blocks , reused , f . IgnorePerms || file . NoPermissions , hasCurFile , curFile , ! f . DisableSparseFiles , ! f . DisableFsync )
2014-03-28 14:36:57 +01:00
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Handling file" , slogutil . FilePath ( file . Name ), "blocksToCopy" , len ( blocks ), "reused" , len ( reused ))
2014-03-28 14:36:57 +01:00
2014-10-08 23:41:23 +01:00
cs := copyBlocksState {
2020-04-21 19:55:14 +02:00
sharedPullerState : s ,
2014-10-08 23:41:23 +01:00
blocks : blocks ,
2017-01-04 21:04:13 +00:00
have : len ( have ),
2014-10-06 10:14:36 +02:00
}
2026-04-26 11:58:09 +02:00
2014-10-08 23:41:23 +01:00
copyChan <- cs
2025-03-29 12:50:08 +00:00
return nil
2014-03-28 14:36:57 +01:00
}
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) reuseBlocks ( ctx context . Context , blocks [] protocol . BlockInfo , reused [] int , file protocol . FileInfo , tempName string ) ([] protocol . BlockInfo , [] int ) {
2021-08-04 11:10:10 +02:00
// Check for an old temporary file which might have some blocks we could
// reuse.
2025-11-27 21:34:35 +01:00
tempBlocks , err := scanner . HashFile ( ctx , f . ID , f . mtimefs , tempName , file . BlockSize (), nil )
2021-08-04 11:10:10 +02:00
if err != nil {
2025-06-06 12:03:28 +02:00
var caseErr * fs . CaseConflictError
2021-08-04 11:10:10 +02:00
if errors . As ( err , & caseErr ) {
if rerr := f . mtimefs . Rename ( caseErr . Real , tempName ); rerr == nil {
2025-11-27 21:34:35 +01:00
tempBlocks , err = scanner . HashFile ( ctx , f . ID , f . mtimefs , tempName , file . BlockSize (), nil )
2021-08-04 11:10:10 +02:00
}
}
}
if err != nil {
return blocks , reused
}
// Check for any reusable blocks in the temp file
tempCopyBlocks , _ := blockDiff ( tempBlocks , file . Blocks )
// block.String() returns a string unique to the block
existingBlocks := make ( map [ string ] struct {}, len ( tempCopyBlocks ))
for _ , block := range tempCopyBlocks {
existingBlocks [ block . String ()] = struct {}{}
}
// Since the blocks are already there, we don't need to get them.
blocks = blocks [: 0 ]
for i , block := range file . Blocks {
_ , ok := existingBlocks [ block . String ()]
if ! ok {
blocks = append ( blocks , block )
} else {
reused = append ( reused , i )
}
}
return blocks , reused
}
2018-01-14 14:30:11 +00:00
// blockDiff returns lists of common and missing (to transform src into tgt)
// blocks. Both block lists must have been created with the same block size.
2018-01-14 21:52:41 +00:00
func blockDiff ( src , tgt [] protocol . BlockInfo ) ([] protocol . BlockInfo , [] protocol . BlockInfo ) {
if len ( tgt ) == 0 {
2018-01-14 14:30:11 +00:00
return nil , nil
}
2018-01-14 21:52:41 +00:00
if len ( src ) == 0 {
2018-01-14 14:30:11 +00:00
// Copy the entire file
return nil , tgt
}
2018-01-14 21:52:41 +00:00
have := make ([] protocol . BlockInfo , 0 , len ( src ))
need := make ([] protocol . BlockInfo , 0 , len ( tgt ))
2018-01-14 14:30:11 +00:00
for i := range tgt {
2018-01-14 21:52:41 +00:00
if i >= len ( src ) {
return have , append ( need , tgt [ i :] ... )
}
if ! bytes . Equal ( tgt [ i ]. Hash , src [ i ]. Hash ) {
2018-01-14 14:30:11 +00:00
// Copy differing block
need = append ( need , tgt [ i ])
} else {
have = append ( have , tgt [ i ])
}
}
return have , need
}
// populateOffsets sets the Offset field on each block
func populateOffsets ( blocks [] protocol . BlockInfo ) {
var offset int64
for i := range blocks {
blocks [ i ]. Offset = offset
offset += int64 ( blocks [ i ]. Size )
}
}
2022-09-14 09:50:55 +02:00
// shortcutFile sets file metadata, when that's the only thing that has
// changed.
2021-09-01 22:03:06 +02:00
func ( f * sendReceiveFolder ) shortcutFile ( file protocol . FileInfo , dbUpdateChan chan <- dbUpdateJob ) {
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Taking metadata shortcut" , slogutil . FilePath ( file . Name ))
2018-09-16 11:29:06 +02:00
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemStarted , map [ string ] string {
2018-09-16 11:29:06 +02:00
"folder" : f . folderID ,
"item" : file . Name ,
"type" : "file" ,
"action" : "metadata" ,
})
var err error
2025-08-07 11:19:36 +02:00
defer func () {
if err != nil {
slog . Info ( "Failed to update file metadata" , f . LogAttr (), file . LogAttr (), slogutil . Error ( err ))
} else {
slog . Info ( "Updated file metadata" , f . LogAttr (), file . LogAttr ())
}
f . evLogger . Log ( events . ItemFinished , map [ string ] interface {}{
"folder" : f . folderID ,
"item" : file . Name ,
"error" : events . Error ( err ),
"type" : "file" ,
"action" : "metadata" ,
})
}()
2018-09-16 11:29:06 +02:00
f . queue . Done ( file . Name )
2018-02-25 09:39:00 +01:00
if ! f . IgnorePerms && ! file . NoPermissions {
2023-08-04 19:57:30 +02:00
if err = f . mtimefs . Chmod ( file . Name , fs . FileMode ( file . Permissions & 0 o777 )); err != nil {
2022-09-14 09:50:55 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "shortcut file (setting permissions): %w" , err ))
return
}
}
2022-11-09 06:54:04 +01:00
if err := f . setPlatformData ( & file , file . Name ); err != nil {
f . newPullError ( file . Name , fmt . Errorf ( "shortcut file (setting metadata): %w" , err ))
2022-07-26 08:24:58 +02:00
return
}
2021-10-05 11:23:27 +02:00
// Still need to re-write the trailer with the new encrypted fileinfo.
if f . Type == config . FolderTypeReceiveEncrypted {
err = inWritableDir ( func ( path string ) error {
2023-08-04 19:57:30 +02:00
fd , err := f . mtimefs . OpenFile ( path , fs . OptReadWrite , 0 o666 )
2021-10-05 11:23:27 +02:00
if err != nil {
return err
}
defer fd . Close ()
trailerSize , err := writeEncryptionTrailer ( file , fd )
if err != nil {
return err
}
2023-03-28 22:02:59 +02:00
file . EncryptionTrailerSize = int ( trailerSize )
file . Size += trailerSize
return fd . Truncate ( file . Size )
2021-10-05 11:23:27 +02:00
}, f . mtimefs , file . Name , true )
if err != nil {
2022-09-14 09:50:55 +02:00
f . newPullError ( file . Name , fmt . Errorf ( "writing encrypted file trailer: %w" , err ))
2021-10-05 11:23:27 +02:00
return
}
}
2021-10-23 13:01:59 +02:00
f . mtimefs . Chtimes ( file . Name , file . ModTime (), file . ModTime ()) // never fails
2018-09-16 11:29:06 +02:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateShortcutFile }
2014-03-28 14:36:57 +01:00
}
2014-10-08 23:41:23 +01:00
// copierRoutine reads copierStates until the in channel closes and performs
// the relevant copies when possible, or passes it to the puller routine.
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) copierRoutine ( ctx context . Context , in <- chan copyBlocksState , pullChan chan <- pullBlockState , out chan <- * sharedPullerState ) {
2025-05-04 09:23:57 +02:00
otherFolderFilesystems := make ( map [ string ] fs . Filesystem )
2020-05-11 19:15:11 +01:00
for folder , cfg := range f . model . cfg . Folders () {
2026-04-26 11:58:09 +02:00
if folder == f . ID || ! cfg . BlockIndexing {
2025-05-04 09:23:57 +02:00
continue
2020-05-11 19:15:11 +01:00
}
2025-05-04 09:23:57 +02:00
otherFolderFilesystems [ folder ] = cfg . Filesystem ()
2020-05-11 19:15:11 +01:00
}
2014-09-27 14:44:15 +02:00
for state := range in {
2020-11-09 15:33:32 +01:00
if f . Type != config . FolderTypeReceiveEncrypted {
f . model . progressEmitter . Register ( state . sharedPullerState )
2018-05-05 09:24:44 +01:00
}
2016-12-14 23:30:29 +00:00
2025-08-05 11:47:44 +02:00
f . setState ( FolderSyncing ) // Does nothing if already FolderSyncing
2018-07-04 09:07:33 +02:00
blocks :
2014-09-27 14:44:15 +02:00
for _ , block := range state . blocks {
2018-07-04 09:07:33 +02:00
select {
2025-11-27 21:34:35 +01:00
case <- ctx . Done ():
state . fail ( fmt . Errorf ( "folder stopped: %w" , ctx . Err ()))
2018-07-04 09:07:33 +02:00
break blocks
default :
}
2016-12-21 11:23:20 +00:00
if ! f . DisableSparseFiles && state . reused == 0 && block . IsEmpty () {
2015-11-21 16:30:53 +01:00
// The block is a block of all zeroes, and we are not reusing
// a temp file, so there is no need to do anything with it.
// If we were reusing a temp file and had this block to copy,
// it would be because the block in the temp file was *not* a
// block of all zeroes, so then we should not skip it.
// Pretend we copied it.
2023-08-04 19:57:30 +02:00
state . skippedSparseBlock ( block . Size )
2018-01-11 10:36:35 +00:00
state . copyDone ( block )
2015-11-21 16:30:53 +01:00
continue
}
2025-11-27 21:34:35 +01:00
if f . copyBlock ( ctx , block , state , otherFolderFilesystems ) {
2025-05-04 09:23:57 +02:00
state . copyDone ( block )
continue
2025-03-29 12:50:08 +00:00
}
2014-10-08 23:41:23 +01:00
if state . failed () != nil {
break
2014-08-27 07:00:15 +02:00
}
2014-09-27 14:44:15 +02:00
2025-05-04 09:23:57 +02:00
state . pullStarted ()
ps := pullBlockState {
sharedPullerState : state . sharedPullerState ,
block : block ,
}
pullChan <- ps
}
// If there are no blocks to pull/copy, we still need the temporary file in place.
if len ( state . blocks ) == 0 {
_ , err := state . tempFile ()
if err != nil {
state . fail ( err )
2014-05-25 20:49:08 +02:00
}
2014-05-19 23:42:08 +02:00
}
2017-08-19 14:36:56 +00:00
2014-09-27 14:44:15 +02:00
out <- state . sharedPullerState
2014-03-28 14:36:57 +01:00
}
}
2025-05-04 09:23:57 +02:00
// Returns true when the block was successfully copied.
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) copyBlock ( ctx context . Context , block protocol . BlockInfo , state copyBlocksState , otherFolderFilesystems map [ string ] fs . Filesystem ) bool {
2025-05-20 15:04:33 +02:00
buf := protocol . BufferPool . Get ( block . Size )
2025-05-16 22:50:13 +02:00
defer protocol . BufferPool . Put ( buf )
2025-05-04 09:23:57 +02:00
2026-04-26 11:58:09 +02:00
// Check for the block in the current version of the file
if idx , ok := state . curFileBlocks [ string ( block . Hash )]; ok {
if f . copyBlockFromFile ( ctx , state . curFile . Name , state . curFile . Blocks [ idx ]. Offset , state , f . mtimefs , block , buf ) {
state . copiedFromOrigin ( block . Size )
return true
}
if state . failed () != nil {
return false
}
2025-05-04 09:23:57 +02:00
}
2026-04-26 11:58:09 +02:00
if f . folder . BlockIndexing {
// Hope that it's usually in the same folder, so start with that
// one. Also possibly more efficient copy (same filesystem).
if f . copyBlockFromFolder ( ctx , f . ID , block , state , f . mtimefs , buf ) {
return true
}
if state . failed () != nil {
return false
}
2025-05-04 09:23:57 +02:00
}
for folderID , ffs := range otherFolderFilesystems {
2025-11-27 21:34:35 +01:00
if f . copyBlockFromFolder ( ctx , folderID , block , state , ffs , buf ) {
2025-05-04 09:23:57 +02:00
return true
}
if state . failed () != nil {
return false
}
}
return false
}
// Returns true when the block was successfully copied.
// The passed buffer must be large enough to accommodate the block.
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) copyBlockFromFolder ( ctx context . Context , folderID string , block protocol . BlockInfo , state copyBlocksState , ffs fs . Filesystem , buf [] byte ) bool {
2025-05-04 09:23:57 +02:00
for e , err := range itererr . Zip ( f . model . sdb . AllLocalBlocksWithHash ( folderID , block . Hash )) {
if err != nil {
// We just ignore this and continue pulling instead (though
// there's a good chance that will fail too, if the DB is
// unhealthy).
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Failed to get block information from database" , "blockHash" , block . Hash , slogutil . FilePath ( state . file . Name ), slogutil . Error ( err ))
2025-05-04 09:23:57 +02:00
return false
}
2025-11-27 21:34:35 +01:00
if ! f . copyBlockFromFile ( ctx , e . FileName , e . Offset , state , ffs , block , buf ) {
2025-05-04 09:23:57 +02:00
if state . failed () != nil {
return false
}
continue
}
if e . FileName == state . file . Name {
state . copiedFromOrigin ( block . Size )
} else {
state . copiedFromElsewhere ( block . Size )
}
return true
}
return false
}
// Returns true when the block was successfully copied.
// The passed buffer must be large enough to accommodate the block.
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) copyBlockFromFile ( ctx context . Context , srcName string , srcOffset int64 , state copyBlocksState , ffs fs . Filesystem , block protocol . BlockInfo , buf [] byte ) bool {
2025-05-01 17:15:02 +02:00
fd , err := ffs . Open ( srcName )
if err != nil {
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Failed to open source file for block copy" , slogutil . FilePath ( srcName ), "blockHash" , block . Hash , slogutil . Error ( err ))
2025-05-04 09:23:57 +02:00
return false
2025-05-01 17:15:02 +02:00
}
defer fd . Close ()
_ , err = fd . ReadAt ( buf , srcOffset )
if err != nil {
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Failed to read block from file" , slogutil . FilePath ( srcName ), "blockHash" , block . Hash , slogutil . Error ( err ))
2025-05-04 09:23:57 +02:00
return false
2025-05-01 17:15:02 +02:00
}
// Hash is not SHA256 as it's an encrypted hash token. In that
// case we can't verify the block integrity so we'll take it on
// trust. (The other side can and will verify.)
if f . Type != config . FolderTypeReceiveEncrypted {
if err := f . verifyBuffer ( buf , block ); err != nil {
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Failed to verify block buffer" , slogutil . Error ( err ))
2025-05-04 09:23:57 +02:00
return false
2025-05-01 17:15:02 +02:00
}
}
2025-05-04 09:23:57 +02:00
dstFd , err := state . tempFile ()
if err != nil {
// State is already marked as failed when an error is returned here.
return false
}
2025-05-01 17:15:02 +02:00
if f . CopyRangeMethod != config . CopyRangeMethodStandard {
2025-11-27 21:34:35 +01:00
err = f . withLimiter ( ctx , func () error {
2025-05-01 17:15:02 +02:00
dstFd . mut . Lock ()
defer dstFd . mut . Unlock ()
return fs . CopyRange ( f . CopyRangeMethod . ToFS (), fd , dstFd . fd , srcOffset , block . Offset , int64 ( block . Size ))
})
2025-05-04 09:23:57 +02:00
} else {
2025-11-27 21:34:35 +01:00
err = f . limitedWriteAt ( ctx , dstFd , buf , block . Offset )
2025-05-04 09:23:57 +02:00
}
if err != nil {
state . fail ( fmt . Errorf ( "dst write: %w" , err ))
return false
2025-05-01 17:15:02 +02:00
}
2025-05-04 09:23:57 +02:00
return true
2025-05-01 17:15:02 +02:00
}
2022-07-28 17:32:45 +02:00
func ( * sendReceiveFolder ) verifyBuffer ( buf [] byte , block protocol . BlockInfo ) error {
2025-06-06 11:54:40 +02:00
if len ( buf ) != block . Size {
2018-05-05 09:24:44 +01:00
return fmt . Errorf ( "length mismatch %d != %d" , len ( buf ), block . Size )
2018-01-14 14:30:11 +00:00
}
2020-03-31 14:32:24 +02:00
hash := sha256 . Sum256 ( buf )
if ! bytes . Equal ( hash [:], block . Hash ) {
2018-05-05 09:24:44 +01:00
return fmt . Errorf ( "hash mismatch %x != %x" , hash , block . Hash )
2018-01-14 14:30:11 +00:00
}
2018-05-05 09:24:44 +01:00
return nil
2018-01-14 14:30:11 +00:00
}
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) pullerRoutine ( ctx context . Context , in <- chan pullBlockState , out chan <- * sharedPullerState ) {
2023-08-21 19:44:33 +02:00
requestLimiter := semaphore . New ( f . PullerMaxPendingKiB * 1024 )
2025-08-07 11:19:36 +02:00
var wg sync . WaitGroup
2018-02-25 10:14:02 +01:00
2014-09-27 14:44:15 +02:00
for state := range in {
if state . failed () != nil {
2015-05-27 11:14:39 +02:00
out <- state . sharedPullerState
2014-12-28 23:11:32 +00:00
continue
2014-09-27 14:44:15 +02:00
}
2014-07-24 09:38:16 +02:00
2019-10-16 09:08:54 +02:00
f . setState ( FolderSyncing ) // Does nothing if already FolderSyncing
2018-02-25 10:14:02 +01:00
// The requestLimiter limits how many pending block requests we have
// ongoing at any given time, based on the size of the blocks
// themselves.
2014-08-05 09:46:11 +02:00
2025-06-06 11:54:40 +02:00
bytes := state . block . Size
2015-11-21 16:30:53 +01:00
2025-11-27 21:34:35 +01:00
if err := requestLimiter . TakeWithContext ( ctx , bytes ); err != nil {
2020-04-21 19:55:14 +02:00
state . fail ( err )
out <- state . sharedPullerState
continue
2020-02-24 21:57:15 +01:00
}
2026-02-11 10:41:38 +00:00
wg . Go ( func () {
2021-06-25 11:38:04 +02:00
defer requestLimiter . Give ( bytes )
2025-11-27 21:34:35 +01:00
f . pullBlock ( ctx , state , out )
2026-02-11 10:41:38 +00:00
})
2018-02-25 10:14:02 +01:00
}
wg . Wait ()
}
2014-12-28 23:11:32 +00:00
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) pullBlock ( ctx context . Context , state pullBlockState , out chan <- * sharedPullerState ) {
2018-02-25 10:14:02 +01:00
// Get an fd to the temporary file. Technically we don't need it until
// after fetching the block, but if we run into an error here there is
// no point in issuing the request to the network.
fd , err := state . tempFile ()
if err != nil {
out <- state . sharedPullerState
return
}
2014-12-28 23:11:32 +00:00
2026-05-09 12:15:18 +02:00
if state . block . IsEmpty () {
2018-02-25 10:14:02 +01:00
// There is no need to request a block of all zeroes. Pretend we
// requested it and handled it correctly.
2026-05-09 12:15:18 +02:00
if state . reused != 0 || f . DisableSparseFiles {
// We are reusing a file (contents apparently weren't all-zeroes
// previously), or sparse files are disabled, so we need to
// actually write the block.
zeroes := make ([] byte , state . block . Size )
err = f . limitedWriteAt ( ctx , fd , zeroes , state . block . Offset )
}
if err != nil {
state . fail ( fmt . Errorf ( "save: %w" , err ))
} else {
state . pullDone ( state . block )
}
2018-02-25 10:14:02 +01:00
out <- state . sharedPullerState
return
}
var lastError error
2025-03-29 12:50:08 +00:00
candidates := f . model . blockAvailability ( f . FolderConfiguration , state . file , state . block )
2021-03-17 21:34:52 +01:00
loop :
2018-02-25 10:14:02 +01:00
for {
2018-07-04 09:07:33 +02:00
select {
2025-11-27 21:34:35 +01:00
case <- ctx . Done ():
state . fail ( fmt . Errorf ( "folder stopped: %w" , ctx . Err ()))
2021-03-17 21:34:52 +01:00
break loop
2018-07-04 09:07:33 +02:00
default :
}
2018-02-25 10:14:02 +01:00
// Select the least busy device to pull the block from. If we found no
// feasible device at all, fail the block (and in the long run, the
// file).
2021-11-10 19:56:29 +01:00
found := activity . leastBusy ( candidates )
if found == - 1 {
2018-02-25 10:14:02 +01:00
if lastError != nil {
2022-08-16 10:01:49 +02:00
state . fail ( fmt . Errorf ( "pull: %w" , lastError ))
2014-12-28 23:11:32 +00:00
} else {
2022-08-16 10:01:49 +02:00
state . fail ( fmt . Errorf ( "pull: %w" , errNoDevice ))
2014-12-28 23:11:32 +00:00
}
break
}
2018-02-25 10:14:02 +01:00
2021-11-10 19:56:29 +01:00
selected := candidates [ found ]
candidates [ found ] = candidates [ len ( candidates ) - 1 ]
candidates = candidates [: len ( candidates ) - 1 ]
2018-02-25 10:14:02 +01:00
// Fetch the block, while marking the selected device as in use so that
// leastBusy can select another device when someone else asks.
activity . using ( selected )
2019-01-24 08:18:55 +01:00
var buf [] byte
2020-11-09 15:33:32 +01:00
blockNo := int ( state . block . Offset / int64 ( state . file . BlockSize ()))
2025-11-27 21:34:35 +01:00
buf , lastError = f . model . RequestGlobal ( ctx , selected . ID , f . folderID , state . file . Name , blockNo , state . block . Offset , state . block . Size , state . block . Hash , selected . FromTemporary )
2018-02-25 10:14:02 +01:00
activity . done ( selected )
if lastError != nil {
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Block request returned error" , slogutil . FilePath ( state . file . Name ), "offset" , state . block . Offset , "size" , state . block . Size , "device" , selected . ID . Short (), slogutil . Error ( lastError ))
2018-02-25 10:14:02 +01:00
continue
}
// Verify that the received block matches the desired hash, if not
2026-05-09 12:15:18 +02:00
// try pulling it from another device. For receive-encrypted
// folders, the hash is not SHA256 as it's an encrypted hash token.
// In that case we can't verify the block integrity so we'll take it
// on trust. (The other side can and will verify.)
2020-11-09 15:33:32 +01:00
if f . Type != config . FolderTypeReceiveEncrypted {
lastError = f . verifyBuffer ( buf , state . block )
}
2018-02-25 10:14:02 +01:00
if lastError != nil {
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Block hash mismatch" , slogutil . FilePath ( state . file . Name ), "offset" , state . block . Offset , "size" , state . block . Size )
2018-02-25 10:14:02 +01:00
continue
}
// Save the block data we got from the cluster
2025-11-27 21:34:35 +01:00
err = f . limitedWriteAt ( ctx , fd , buf , state . block . Offset )
2018-02-25 10:14:02 +01:00
if err != nil {
2022-08-16 10:01:49 +02:00
state . fail ( fmt . Errorf ( "save: %w" , err ))
2018-02-25 10:14:02 +01:00
} else {
state . pullDone ( state . block )
}
break
2014-07-24 09:38:16 +02:00
}
2018-02-25 10:14:02 +01:00
out <- state . sharedPullerState
2014-03-28 14:36:57 +01:00
}
2014-04-27 12:14:53 +02:00
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) performFinish ( file , curFile protocol . FileInfo , hasCurFile bool , tempName string , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) error {
2014-11-29 23:18:56 +01:00
// Set the correct permission bits on the new file
2018-09-16 09:48:14 +02:00
if ! f . IgnorePerms && ! file . NoPermissions {
2023-08-04 19:57:30 +02:00
if err := f . mtimefs . Chmod ( tempName , fs . FileMode ( file . Permissions & 0 o777 )); err != nil {
2022-09-14 09:50:55 +02:00
return fmt . Errorf ( "setting permissions: %w" , err )
}
}
2022-11-09 06:54:04 +01:00
// Set file xattrs and ownership.
if err := f . setPlatformData ( & file , tempName ); err != nil {
return fmt . Errorf ( "setting metadata: %w" , err )
2019-01-25 09:52:21 +01:00
}
2020-12-27 22:26:25 +01:00
if stat , err := f . mtimefs . Lstat ( file . Name ); err == nil {
2015-08-08 12:44:17 +02:00
// There is an old file or directory already in place. We need to
// handle that.
2024-03-02 09:55:18 -05:00
if err := f . scanIfItemChanged ( file . Name , stat , curFile , hasCurFile , false , scanChan ); err != nil {
2022-09-14 09:50:55 +02:00
return fmt . Errorf ( "checking existing file: %w" , err )
2017-11-13 15:16:27 +00:00
}
2025-09-13 14:16:28 +00:00
if ! curFile . IsDirectory () && ! curFile . IsSymlink () && file . InConflictWith ( curFile ) {
2015-08-08 12:44:17 +02:00
// The new file has been changed in conflict with the existing one. We
// should file it away as a conflict instead of just removing or
2020-10-19 08:53:19 +02:00
// archiving.
2019-03-07 15:15:14 +01:00
// Directories and symlinks aren't checked for conflicts.
2015-08-08 12:44:17 +02:00
2019-07-31 10:53:35 +02:00
err = f . inWritableDir ( func ( name string ) error {
2019-02-12 16:05:20 +01:00
return f . moveForConflict ( name , file . ModifiedBy . String (), scanChan )
2019-07-31 10:53:35 +02:00
}, curFile . Name )
2019-03-07 15:15:14 +01:00
} else {
2025-03-29 12:50:08 +00:00
err = f . deleteItemOnDisk ( curFile , scanChan )
2019-03-07 15:15:14 +01:00
}
if err != nil {
2022-09-14 09:50:55 +02:00
return fmt . Errorf ( "moving for conflict: %w" , err )
2015-08-08 12:44:17 +02:00
}
2020-06-19 23:47:29 +02:00
} else if ! fs . IsNotExist ( err ) {
2022-09-14 09:50:55 +02:00
return fmt . Errorf ( "checking existing file: %w" , err )
2014-11-29 23:18:56 +01:00
}
2015-08-08 12:44:17 +02:00
2016-05-22 09:06:07 +00:00
// Replace the original content with the new one. If it didn't work,
// leave the temp file in place for reuse.
2024-12-01 16:50:17 +01:00
if err := osutil . RenameOrCopy ( f . CopyRangeMethod . ToFS (), f . mtimefs , f . mtimefs , tempName , file . Name ); err != nil {
2022-09-14 09:50:55 +02:00
return fmt . Errorf ( "replacing file: %w" , err )
2014-11-29 23:18:56 +01:00
}
2014-04-27 12:14:53 +02:00
2016-08-16 18:22:19 +00:00
// Set the correct timestamp on the new file
2020-12-27 22:26:25 +01:00
f . mtimefs . Chtimes ( file . Name , file . ModTime (), file . ModTime ()) // never fails
2016-08-16 18:22:19 +00:00
2014-11-29 23:18:56 +01:00
// Record the updated file in the index
2018-09-16 09:48:14 +02:00
dbUpdateChan <- dbUpdateJob { file , dbUpdateHandleFile }
2015-05-27 11:14:39 +02:00
return nil
2014-11-16 23:18:59 +00:00
}
2014-11-09 04:26:52 +00:00
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) finisherRoutine ( ctx context . Context , in <- chan * sharedPullerState , dbUpdateChan chan <- dbUpdateJob , scanChan chan <- string ) {
2014-11-16 23:18:59 +00:00
for state := range in {
2014-11-29 23:18:56 +01:00
if closed , err := state . finalClose (); closed {
2025-11-27 21:34:35 +01:00
f . sl . DebugContext ( ctx , "Closing temp file" , slogutil . FilePath ( state . file . Name ))
2014-11-29 23:18:56 +01:00
2016-04-26 15:11:19 +00:00
f . queue . Done ( state . file . Name )
2015-05-27 11:14:39 +02:00
if err == nil {
2025-03-29 12:50:08 +00:00
err = f . performFinish ( state . file , state . curFile , state . hasCurFile , state . tempName , dbUpdateChan , scanChan )
2015-01-07 23:12:12 +00:00
}
2015-05-27 11:14:39 +02:00
if err != nil {
2022-09-14 09:50:55 +02:00
f . newPullError ( state . file . Name , fmt . Errorf ( "finishing: %w" , err ))
2017-10-12 06:16:46 +00:00
} else {
2025-11-27 21:34:35 +01:00
slog . InfoContext ( ctx , "Synced file" , f . LogAttr (), state . file . LogAttr (), slog . Group ( "blocks" , slog . Int ( "local" , state . reused + state . copyTotal ), slog . Int ( "download" , state . pullTotal )))
2025-08-07 11:19:36 +02:00
2019-10-04 12:03:13 +01:00
minBlocksPerBlock := state . file . BlockSize () / protocol . MinBlockSize
2017-11-09 21:16:29 +00:00
blockStatsMut . Lock ()
2019-10-04 12:03:13 +01:00
blockStats [ "total" ] += ( state . reused + state . copyTotal + state . pullTotal ) * minBlocksPerBlock
blockStats [ "reused" ] += state . reused * minBlocksPerBlock
blockStats [ "pulled" ] += state . pullTotal * minBlocksPerBlock
2017-11-09 21:16:29 +00:00
// copyOriginShifted is counted towards copyOrigin due to progress bar reasons
// for reporting reasons we want to separate these.
2025-08-07 11:19:36 +02:00
blockStats [ "copyOrigin" ] += state . copyOrigin * minBlocksPerBlock
2019-10-04 12:03:13 +01:00
blockStats [ "copyElsewhere" ] += ( state . copyTotal - state . copyOrigin ) * minBlocksPerBlock
2017-11-09 21:16:29 +00:00
blockStatsMut . Unlock ()
2015-05-27 11:14:39 +02:00
}
2017-09-23 15:22:26 +01:00
2020-11-09 15:33:32 +01:00
if f . Type != config . FolderTypeReceiveEncrypted {
f . model . progressEmitter . Deregister ( state )
}
2019-03-26 21:31:33 +01:00
2019-08-15 16:29:37 +02:00
f . evLogger . Log ( events . ItemFinished , map [ string ] interface {}{
2016-04-26 15:11:19 +00:00
"folder" : f . folderID ,
2015-05-27 11:14:39 +02:00
"item" : state . file . Name ,
"error" : events . Error ( err ),
"type" : "file" ,
"action" : "update" ,
})
2014-04-27 12:14:53 +02:00
}
}
2014-09-27 14:44:15 +02:00
}
2014-04-27 12:14:53 +02:00
2014-12-01 19:23:06 +00:00
// Moves the given filename to the front of the job queue
2016-12-16 22:23:35 +00:00
func ( f * sendReceiveFolder ) BringToFront ( filename string ) {
2016-04-26 15:11:19 +00:00
f . queue . BringToFront ( filename )
2014-12-01 19:23:06 +00:00
}
2019-06-27 20:25:38 +02:00
func ( f * sendReceiveFolder ) Jobs ( page , perpage int ) ([] string , [] string , int ) {
return f . queue . Jobs ( page , perpage )
2014-12-01 19:23:06 +00:00
}
2015-04-05 15:34:29 +02:00
// dbUpdaterRoutine aggregates db updates and commits them in batches no
// larger than 1000 items, and no more delayed than 2 seconds.
2025-12-23 08:26:58 +01:00
func ( f * sendReceiveFolder ) dbUpdaterRoutine ( dbUpdateChan <- chan dbUpdateJob ) int {
2017-08-31 10:47:39 +02:00
const maxBatchTime = 2 * time . Second
2015-04-05 15:34:29 +02:00
2025-12-23 08:26:58 +01:00
changed := 0
2017-08-19 14:36:56 +00:00
changedDirs := make ( map [ string ] struct {})
2019-03-05 21:32:37 +01:00
found := false
var lastFile protocol . FileInfo
2021-04-29 22:01:46 +02:00
tick := time . NewTicker ( maxBatchTime )
defer tick . Stop ()
2025-03-29 12:50:08 +00:00
batch := NewFileInfoBatch ( func ( files [] protocol . FileInfo ) error {
2017-08-19 14:36:56 +00:00
// sync directories
for dir := range changedDirs {
delete ( changedDirs , dir )
2025-06-06 11:54:40 +02:00
if ! f . DisableFsync {
2020-12-27 22:26:25 +01:00
fd , err := f . mtimefs . Open ( dir )
2020-05-01 08:36:46 +01:00
if err != nil {
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Fsync failed" , slogutil . FilePath ( dir ), slogutil . Error ( err ))
2020-05-01 08:36:46 +01:00
continue
}
if err := fd . Sync (); err != nil {
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Fsync failed" , slogutil . FilePath ( dir ), slogutil . Error ( err ))
2020-05-01 08:36:46 +01:00
}
fd . Close ()
2017-08-19 14:36:56 +00:00
}
2016-11-21 18:09:29 +01:00
}
2016-05-19 00:19:26 +00:00
// All updates to file/folder objects that originated remotely
// (across the network) use this call to updateLocals
2019-04-07 13:29:17 +02:00
f . updateLocalsFromPulling ( files )
2015-06-16 12:12:34 +01:00
if found {
2019-03-11 17:57:21 +01:00
f . ReceivedFile ( lastFile . Name , lastFile . IsDeleted ())
2019-03-05 21:32:37 +01:00
found = false
2015-06-16 12:12:34 +01:00
}
2019-03-05 21:32:37 +01:00
return nil
2021-04-29 22:01:46 +02:00
})
2015-06-16 12:12:34 +01:00
2015-04-05 15:34:29 +02:00
loop :
for {
select {
2017-12-07 08:42:03 +00:00
case job , ok := <- dbUpdateChan :
2015-04-05 15:34:29 +02:00
if ! ok {
break loop
}
2019-03-05 21:32:37 +01:00
switch job . jobType {
case dbUpdateHandleFile , dbUpdateShortcutFile :
changedDirs [ filepath . Dir ( job . file . Name )] = struct {}{}
case dbUpdateHandleDir :
changedDirs [ job . file . Name ] = struct {}{}
case dbUpdateHandleSymlink , dbUpdateInvalidate :
// fsyncing symlinks is only supported by MacOS
// and invalidated files are db only changes -> no sync
}
2015-04-05 15:34:29 +02:00
2019-03-05 21:32:37 +01:00
// For some reason we seem to care about file deletions and
// content modification, but not about metadata and dirs/symlinks.
if ! job . file . IsInvalid () && job . jobType & ( dbUpdateHandleFile | dbUpdateDeleteFile ) != 0 {
found = true
lastFile = job . file
2015-04-05 15:34:29 +02:00
}
2019-03-05 21:32:37 +01:00
job . file . Sequence = 0
2021-04-29 22:01:46 +02:00
batch . Append ( job . file )
batch . FlushIfFull ()
2025-12-23 08:26:58 +01:00
changed ++
2019-03-05 21:32:37 +01:00
2015-04-05 15:34:29 +02:00
case <- tick . C :
2021-04-29 22:01:46 +02:00
batch . Flush ()
2015-04-05 15:34:29 +02:00
}
}
2021-04-29 22:01:46 +02:00
batch . Flush ()
2025-12-23 08:26:58 +01:00
return changed
2015-04-05 15:34:29 +02:00
}
2017-12-07 08:42:03 +00:00
// pullScannerRoutine aggregates paths to be scanned after pulling. The scan is
// scheduled once when scanChan is closed (scanning can not happen during pulling).
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) pullScannerRoutine ( ctx context . Context , scanChan <- chan string ) {
2017-12-07 08:42:03 +00:00
toBeScanned := make ( map [ string ] struct {})
for path := range scanChan {
toBeScanned [ path ] = struct {}{}
}
if len ( toBeScanned ) != 0 {
scanList := make ([] string , 0 , len ( toBeScanned ))
for path := range toBeScanned {
2025-11-27 21:34:35 +01:00
slog . DebugContext ( ctx , "Scheduling scan after pulling" , slogutil . FilePath ( path ))
2017-12-07 08:42:03 +00:00
scanList = append ( scanList , path )
}
2019-02-02 12:16:27 +01:00
f . Scan ( scanList )
2017-12-07 08:42:03 +00:00
}
}
2019-03-04 13:20:40 +01:00
func ( f * sendReceiveFolder ) moveForConflict ( name , lastModBy string , scanChan chan <- string ) error {
if isConflict ( name ) {
2025-08-07 11:19:36 +02:00
f . sl . Info ( "Conflict on existing conflict copy; not copying again" , slogutil . FilePath ( name ))
2020-12-27 22:26:25 +01:00
if err := f . mtimefs . Remove ( name ); err != nil && ! fs . IsNotExist ( err ) {
2022-08-16 10:01:49 +02:00
return fmt . Errorf ( "%s: %w" , contextRemovingOldItem , err )
2016-01-03 21:15:02 +01:00
}
return nil
}
2016-12-21 11:23:20 +00:00
if f . MaxConflicts == 0 {
2020-12-27 22:26:25 +01:00
if err := f . mtimefs . Remove ( name ); err != nil && ! fs . IsNotExist ( err ) {
2022-08-16 10:01:49 +02:00
return fmt . Errorf ( "%s: %w" , contextRemovingOldItem , err )
2015-10-13 20:50:58 +01:00
}
return nil
}
2025-04-04 18:24:04 +02:00
metricFolderConflictsTotal . WithLabelValues ( f . ID ). Inc ()
2019-03-04 13:20:40 +01:00
newName := conflictName ( name , lastModBy )
2020-12-27 22:26:25 +01:00
err := f . mtimefs . Rename ( name , newName )
2017-08-19 14:36:56 +00:00
if fs . IsNotExist ( err ) {
2015-04-28 11:33:54 +02:00
// We were supposed to move a file away but it does not exist. Either
// the user has already moved it away, or the conflict was between a
// remote modification and a local delete. In either way it does not
// matter, go ahead as if the move succeeded.
2015-10-13 20:50:58 +01:00
err = nil
}
2016-12-21 11:23:20 +00:00
if f . MaxConflicts > - 1 {
2020-12-27 22:26:25 +01:00
matches := existingConflicts ( name , f . mtimefs )
2019-03-04 13:20:40 +01:00
if len ( matches ) > f . MaxConflicts {
2025-05-26 20:37:49 +02:00
slices . SortFunc ( matches , func ( a , b string ) int {
return strings . Compare ( b , a )
})
2016-12-21 11:23:20 +00:00
for _ , match := range matches [ f . MaxConflicts :] {
2020-12-27 22:26:25 +01:00
if gerr := f . mtimefs . Remove ( match ); gerr != nil {
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Failed to remove extra conflict copy" , slogutil . Error ( gerr ))
2015-10-13 20:50:58 +01:00
}
}
}
2015-04-28 11:33:54 +02:00
}
2019-02-12 16:05:20 +01:00
if err == nil {
scanChan <- newName
}
2015-04-28 11:33:54 +02:00
return err
2015-03-29 16:16:36 +02:00
}
2015-06-26 13:31:30 +02:00
2019-03-04 14:01:52 +01:00
func ( f * sendReceiveFolder ) newPullError ( path string , err error ) {
2025-11-27 21:34:35 +01:00
if errors . Is ( err , context . Canceled ) || errors . Is ( err , context . DeadlineExceeded ) {
2019-11-19 09:56:53 +01:00
// Error because the folder stopped - no point logging/tracking
return
}
2020-11-06 14:22:20 +01:00
f . errorsMut . Lock ()
defer f . errorsMut . Unlock ()
2015-06-26 13:31:30 +02:00
// We might get more than one error report for a file (i.e. error on
// Write() followed by Close()); we keep the first error as that is
// probably closer to the root cause.
2020-07-28 11:13:15 +02:00
if _ , ok := f . tempPullErrors [ path ]; ok {
2015-06-26 13:31:30 +02:00
return
}
2019-03-04 14:01:52 +01:00
// Establish context to differentiate from errors while scanning.
// Use "syncing" as opposed to "pulling" as the latter might be used
2022-08-23 09:44:11 -04:00
// for errors occurring specifically in the puller routine.
2022-09-14 09:50:55 +02:00
errStr := fmt . Sprintf ( "syncing: %s" , err )
2020-07-28 11:13:15 +02:00
f . tempPullErrors [ path ] = errStr
2019-11-19 09:56:53 +01:00
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "New pull error" , slogutil . FilePath ( path ), slogutil . Error ( err ))
2015-06-26 13:31:30 +02:00
}
2019-03-07 15:15:14 +01:00
// deleteItemOnDisk deletes the file represented by old that is about to be replaced by new.
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) deleteItemOnDisk ( item protocol . FileInfo , scanChan chan <- string ) ( err error ) {
2019-03-07 15:15:14 +01:00
defer func () {
2022-08-16 10:01:49 +02:00
if err != nil {
err = fmt . Errorf ( "%s: %w" , contextRemovingOldItem , err )
}
2019-03-07 15:15:14 +01:00
}()
switch {
case item . IsDirectory ():
// Directories aren't archived and need special treatment due
// to potential children.
2025-03-29 12:50:08 +00:00
return f . deleteDirOnDisk ( item . Name , scanChan )
2019-03-07 15:15:14 +01:00
case ! item . IsSymlink () && f . versioner != nil :
// If we should use versioning, let the versioner archive the
// file before we replace it. Archiving a non-existent file is not
// an error.
// Symlinks aren't archived.
2019-07-31 10:53:35 +02:00
return f . inWritableDir ( f . versioner . Archive , item . Name )
2019-03-07 15:15:14 +01:00
}
2020-12-27 22:26:25 +01:00
return f . inWritableDir ( f . mtimefs . Remove , item . Name )
2019-03-07 15:15:14 +01:00
}
// deleteDirOnDisk attempts to delete a directory. It checks for files/dirs inside
2017-12-07 08:42:03 +00:00
// the directory and removes them if possible or returns an error if it fails
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) deleteDirOnDisk ( dir string , scanChan chan <- string ) error {
2020-12-27 22:26:25 +01:00
if err := osutil . TraversesSymlink ( f . mtimefs , filepath . Dir ( dir )); err != nil {
2019-10-22 21:55:51 +02:00
return err
}
2025-03-29 12:50:08 +00:00
if err := f . deleteDirOnDiskHandleChildren ( dir , scanChan ); err != nil {
2021-01-02 21:40:37 +01:00
return err
}
err := f . inWritableDir ( f . mtimefs . Remove , dir )
if err == nil || fs . IsNotExist ( err ) {
// It was removed or it doesn't exist to start with
return nil
}
if _ , serr := f . mtimefs . Lstat ( dir ); serr != nil && ! fs . IsPermission ( serr ) {
// We get an error just looking at the directory, and it's not a
// permission problem. Lets assume the error is in fact some variant
// of "file does not exist" (possibly expressed as some parent being a
// file and not a directory etc) and that the delete is handled.
return nil
}
2017-12-07 08:42:03 +00:00
2021-01-02 21:40:37 +01:00
return err
}
2017-12-07 08:42:03 +00:00
2025-03-29 12:50:08 +00:00
func ( f * sendReceiveFolder ) deleteDirOnDiskHandleChildren ( dir string , scanChan chan <- string ) error {
2021-01-02 21:40:37 +01:00
var dirsToDelete [] string
2020-05-25 08:46:24 +02:00
var hasIgnored , hasKnown , hasToBeScanned , hasReceiveOnlyChanged bool
2021-01-02 21:40:37 +01:00
var delErr error
err := f . mtimefs . Walk ( dir , func ( path string , info fs . FileInfo , err error ) error {
if path == dir {
return nil
}
if err != nil {
return err
}
switch match := f . ignores . Match ( path ); {
case match . IsDeletable ():
if info . IsDir () {
dirsToDelete = append ( dirsToDelete , path )
return nil
}
fallthrough
case fs . IsTemporary ( path ):
if err := f . mtimefs . Remove ( path ); err != nil && delErr == nil {
delErr = err
}
return nil
case match . IsIgnored ():
2017-12-07 08:42:03 +00:00
hasIgnored = true
2021-01-02 21:40:37 +01:00
return nil
2020-05-25 08:46:24 +02:00
}
2025-03-29 12:50:08 +00:00
cf , ok , err := f . model . sdb . GetDeviceFile ( f . folderID , protocol . LocalDeviceID , path )
2025-05-16 22:50:13 +02:00
if err != nil {
return err
}
2020-05-25 08:46:24 +02:00
switch {
case ! ok || cf . IsDeleted ():
// Something appeared in the dir that we either are not
// aware of at all or that we think should be deleted
// -> schedule scan.
2021-01-02 21:40:37 +01:00
scanChan <- path
2017-12-07 08:42:03 +00:00
hasToBeScanned = true
2021-01-02 21:40:37 +01:00
return nil
2020-05-25 08:46:24 +02:00
case ok && f . Type == config . FolderTypeReceiveOnly && cf . IsReceiveOnlyChanged ():
hasReceiveOnlyChanged = true
2021-01-02 21:40:37 +01:00
return nil
2020-05-25 08:46:24 +02:00
}
2022-09-14 09:50:55 +02:00
diskFile , err := scanner . CreateFileInfo ( info , path , f . mtimefs , f . SyncOwnership , f . SyncXattrs , f . XattrFilter )
2020-05-25 08:46:24 +02:00
if err != nil {
// Lets just assume the file has changed.
2021-01-02 21:40:37 +01:00
scanChan <- path
2020-05-25 08:46:24 +02:00
hasToBeScanned = true
2025-06-06 11:54:40 +02:00
return nil //nolint:nilerr
2020-05-25 08:46:24 +02:00
}
2022-07-26 08:24:58 +02:00
if ! cf . IsEquivalentOptional ( diskFile , protocol . FileInfoComparison {
ModTimeWindow : f . modTimeWindow ,
IgnorePerms : f . IgnorePerms ,
IgnoreBlocks : true ,
IgnoreFlags : protocol . LocalAllFlags ,
IgnoreOwnership : ! f . SyncOwnership ,
2022-09-14 09:50:55 +02:00
IgnoreXattrs : ! f . SyncXattrs ,
2022-07-26 08:24:58 +02:00
}) {
2020-05-25 08:46:24 +02:00
// File on disk changed compared to what we have in db
// -> schedule scan.
2021-01-02 21:40:37 +01:00
scanChan <- path
2020-05-25 08:46:24 +02:00
hasToBeScanned = true
2021-01-02 21:40:37 +01:00
return nil
2020-05-25 08:46:24 +02:00
}
// Dir contains file that is valid according to db and
// not ignored -> something weird is going on
hasKnown = true
2021-01-02 21:40:37 +01:00
return nil
})
if err != nil {
return err
}
for i := range dirsToDelete {
if err := f . mtimefs . Remove ( dirsToDelete [ len ( dirsToDelete ) - 1 - i ]); err != nil && delErr == nil {
delErr = err
}
2017-12-07 08:42:03 +00:00
}
2021-01-02 21:40:37 +01:00
// "Error precedence":
// Something changed on disk, check that and maybe all else gets resolved
2017-12-07 08:42:03 +00:00
if hasToBeScanned {
return errDirHasToBeScanned
}
2021-01-02 21:40:37 +01:00
// Ignored files will never be touched, i.e. this will keep failing until
// user acts.
2017-12-07 08:42:03 +00:00
if hasIgnored {
return errDirHasIgnored
}
2020-05-25 08:46:24 +02:00
if hasReceiveOnlyChanged {
2020-08-20 10:56:29 +02:00
// Pretend we deleted the directory. It will be resurrected as a
// receive-only changed item on scan.
scanChan <- dir
return nil
2020-05-25 08:46:24 +02:00
}
2017-12-07 08:42:03 +00:00
if hasKnown {
return errDirNotEmpty
}
2021-01-02 21:40:37 +01:00
// All good, except maybe failing to remove a (?d) ignored item
return delErr
2017-12-07 08:42:03 +00:00
}
2019-06-29 07:45:41 +02:00
// scanIfItemChanged schedules the given file for scanning and returns errModified
// if it differs from the information in the database. Returns nil if the file has
// not changed.
2024-03-02 09:55:18 -05:00
func ( f * sendReceiveFolder ) scanIfItemChanged ( name string , stat fs . FileInfo , item protocol . FileInfo , hasItem bool , fromDelete bool , scanChan chan <- string ) ( err error ) {
2019-03-07 15:15:14 +01:00
defer func () {
2025-06-06 11:54:40 +02:00
if errors . Is ( err , errModified ) {
2020-06-16 15:20:08 +02:00
scanChan <- name
2019-03-07 15:15:14 +01:00
}
}()
if ! hasItem || item . Deleted {
// The item appeared from nowhere
2019-06-29 07:45:41 +02:00
return errModified
2019-03-07 15:15:14 +01:00
}
// Check that the item on disk is what we expect it to be according
// to the database. If there's a mismatch here, there might be local
// changes that we don't know about yet and we should scan before
// touching the item.
2022-09-14 09:50:55 +02:00
statItem , err := scanner . CreateFileInfo ( stat , item . Name , f . mtimefs , f . SyncOwnership , f . SyncXattrs , f . XattrFilter )
2019-03-07 15:15:14 +01:00
if err != nil {
2022-08-16 10:01:49 +02:00
return fmt . Errorf ( "comparing item on disk to db: %w" , err )
2019-06-29 07:45:41 +02:00
}
2022-07-26 08:24:58 +02:00
if ! statItem . IsEquivalentOptional ( item , protocol . FileInfoComparison {
ModTimeWindow : f . modTimeWindow ,
IgnorePerms : f . IgnorePerms ,
IgnoreBlocks : true ,
IgnoreFlags : protocol . LocalAllFlags ,
2024-03-02 09:55:18 -05:00
IgnoreOwnership : fromDelete || ! f . SyncOwnership ,
IgnoreXattrs : fromDelete || ! f . SyncXattrs ,
2022-07-26 08:24:58 +02:00
}) {
2019-06-29 07:45:41 +02:00
return errModified
2019-03-07 15:15:14 +01:00
}
2019-06-29 07:45:41 +02:00
return nil
2019-03-07 15:15:14 +01:00
}
2018-09-16 09:48:14 +02:00
// checkToBeDeleted makes sure the file on disk is compatible with what there is
// in the DB before the caller proceeds with actually deleting it.
2020-09-12 07:45:50 +02:00
// I.e. non-nil error status means "Do not delete!" or "is already deleted".
func ( f * sendReceiveFolder ) checkToBeDeleted ( file , cur protocol . FileInfo , hasCur bool , scanChan chan <- string ) error {
2020-12-27 22:26:25 +01:00
if err := osutil . TraversesSymlink ( f . mtimefs , filepath . Dir ( file . Name )); err != nil {
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Not deleting item behind symlink on disk, but updating database" , slogutil . FilePath ( file . Name ))
2020-06-16 15:20:08 +02:00
return fs . ErrNotExist
}
2020-12-27 22:26:25 +01:00
stat , err := f . mtimefs . Lstat ( file . Name )
2020-09-12 07:45:50 +02:00
deleted := fs . IsNotExist ( err ) || fs . IsErrCaseConflict ( err )
if ! deleted && err != nil {
2018-09-16 09:48:14 +02:00
return err
}
2020-09-12 07:45:50 +02:00
if deleted {
2020-09-10 08:25:44 +02:00
if hasCur && ! cur . Deleted && ! cur . IsUnsupported () {
2020-06-16 15:20:08 +02:00
scanChan <- file . Name
return errModified
}
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Not deleting item we don't have, but updating database" , slogutil . FilePath ( file . Name ))
2020-09-12 07:45:50 +02:00
return err
2020-06-16 15:20:08 +02:00
}
2024-03-02 09:55:18 -05:00
return f . scanIfItemChanged ( file . Name , stat , cur , hasCur , true , scanChan )
2018-09-16 09:48:14 +02:00
}
2022-11-09 06:54:04 +01:00
// setPlatformData makes adjustments to the metadata that should happen for
// all types (files, directories, symlinks). This should be one of the last
// things we do to a file when syncing changes to it.
func ( f * sendReceiveFolder ) setPlatformData ( file * protocol . FileInfo , name string ) error {
if f . SyncXattrs {
// Set extended attributes.
2022-11-11 11:49:15 +01:00
if err := f . mtimefs . SetXattr ( name , file . Platform . Xattrs (), f . XattrFilter ); errors . Is ( err , fs . ErrXattrsNotSupported ) {
2025-11-27 21:34:35 +01:00
f . sl . Debug ( "Cannot set xattrs (not supported)" , slogutil . FilePath ( file . Name ), slogutil . Error ( err ))
2022-11-09 06:54:04 +01:00
} else if err != nil {
return err
}
}
2022-07-26 08:24:58 +02:00
if f . SyncOwnership {
// Set ownership based on file metadata.
if err := f . syncOwnership ( file , name ); err != nil {
return err
}
} else if f . CopyOwnershipFromParent {
// Copy the parent owner and group.
if err := f . copyOwnershipFromParent ( name ); err != nil {
return err
}
2019-01-25 09:52:21 +01:00
}
2022-11-09 06:54:04 +01:00
2022-07-26 08:24:58 +02:00
return nil
}
func ( f * sendReceiveFolder ) copyOwnershipFromParent ( path string ) error {
2022-07-28 19:36:39 +02:00
if build . IsWindows {
2019-01-25 09:52:21 +01:00
// Can't do anything.
return nil
}
2020-12-27 22:26:25 +01:00
info , err := f . mtimefs . Lstat ( filepath . Dir ( path ))
2019-01-25 09:52:21 +01:00
if err != nil {
2022-08-16 10:01:49 +02:00
return fmt . Errorf ( "copy owner from parent: %w" , err )
2019-01-25 09:52:21 +01:00
}
2022-07-26 08:24:58 +02:00
if err := f . mtimefs . Lchown ( path , strconv . Itoa ( info . Owner ()), strconv . Itoa ( info . Group ())); err != nil {
2022-08-16 10:01:49 +02:00
return fmt . Errorf ( "copy owner from parent: %w" , err )
2019-01-25 09:52:21 +01:00
}
return nil
}
2019-07-31 10:53:35 +02:00
func ( f * sendReceiveFolder ) inWritableDir ( fn func ( string ) error , path string ) error {
2020-12-27 22:26:25 +01:00
return inWritableDir ( fn , f . mtimefs , path , f . IgnorePerms )
2019-07-31 10:53:35 +02:00
}
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) limitedWriteAt ( ctx context . Context , fd io . WriterAt , data [] byte , offset int64 ) error {
return f . withLimiter ( ctx , func () error {
2020-06-18 07:15:47 +01:00
_ , err := fd . WriteAt ( data , offset )
return err
})
}
2025-11-27 21:34:35 +01:00
func ( f * sendReceiveFolder ) withLimiter ( ctx context . Context , fn func () error ) error {
if err := f . writeLimiter . TakeWithContext ( ctx , 1 ); err != nil {
2020-06-18 07:15:47 +01:00
return err
2020-04-27 00:13:18 +02:00
}
2021-06-25 11:38:04 +02:00
defer f . writeLimiter . Give ( 1 )
2020-06-18 07:15:47 +01:00
return fn ()
2020-04-27 00:13:18 +02:00
}
2018-01-14 17:01:06 +00:00
// A []FileError is sent as part of an event and will be JSON serialized.
type FileError struct {
2015-06-26 13:31:30 +02:00
Path string `json:"path"`
Err string `json:"error"`
}
2019-03-04 13:20:40 +01:00
func conflictName ( name , lastModBy string ) string {
ext := filepath . Ext ( name )
return name [: len ( name ) - len ( ext )] + time . Now (). Format ( ".sync-conflict-20060102-150405-" ) + lastModBy + ext
}
func isConflict ( name string ) bool {
return strings . Contains ( filepath . Base ( name ), ".sync-conflict-" )
}
func existingConflicts ( name string , fs fs . Filesystem ) [] string {
ext := filepath . Ext ( name )
matches , err := fs . Glob ( name [: len ( name ) - len ( ext )] + ".sync-conflict-????????-??????*" + ext )
if err != nil {
2025-11-27 21:34:35 +01:00
slog . Debug ( "Globbing for conflicts failed" , slogutil . Error ( err ))
2019-03-04 13:20:40 +01:00
}
return matches
}