2015-10-27 10:37:03 +00:00
// Copyright (C) 2014 The Syncthing Authors.
//
// 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 06:52:18 +00:00
// You can obtain one at https://mozilla.org/MPL/2.0/.
2015-10-27 10:37:03 +00:00
package config
import (
2017-10-24 07:58:55 +00:00
"errors"
2016-11-17 15:12:41 +00:00
"fmt"
2015-10-27 10:37:03 +00:00
"runtime"
2019-07-23 19:48:53 +00:00
"strings"
"time"
"github.com/shirou/gopsutil/disk"
2015-10-27 10:37:03 +00:00
2017-08-19 14:36:56 +00:00
"github.com/syncthing/syncthing/lib/fs"
2015-10-27 10:37:03 +00:00
"github.com/syncthing/syncthing/lib/protocol"
2017-12-07 08:33:32 +00:00
"github.com/syncthing/syncthing/lib/util"
2015-10-27 10:37:03 +00:00
)
2017-10-24 07:58:55 +00:00
var (
2018-03-18 00:42:31 +00:00
ErrPathNotDirectory = errors . New ( "folder path not a directory" )
ErrPathMissing = errors . New ( "folder path missing" )
2020-01-16 14:30:29 +00:00
ErrMarkerMissing = errors . New ( "folder marker missing (this indicates potential data loss, search docs/forum to get information about how to proceed)" )
2017-10-24 07:58:55 +00:00
)
2017-11-05 12:18:05 +00:00
const DefaultMarkerName = ".stfolder"
2015-10-27 10:37:03 +00:00
type FolderConfiguration struct {
2019-01-25 08:52:21 +00:00
ID string ` xml:"id,attr" json:"id" `
Label string ` xml:"label,attr" json:"label" restart:"false" `
FilesystemType fs . FilesystemType ` xml:"filesystemType" json:"filesystemType" `
Path string ` xml:"path,attr" json:"path" `
Type FolderType ` xml:"type,attr" json:"type" `
Devices [ ] FolderDeviceConfiguration ` xml:"device" json:"devices" `
2019-02-12 06:58:24 +00:00
RescanIntervalS int ` xml:"rescanIntervalS,attr" json:"rescanIntervalS" default:"3600" `
FSWatcherEnabled bool ` xml:"fsWatcherEnabled,attr" json:"fsWatcherEnabled" default:"true" `
FSWatcherDelayS int ` xml:"fsWatcherDelayS,attr" json:"fsWatcherDelayS" default:"10" `
2019-01-25 08:52:21 +00:00
IgnorePerms bool ` xml:"ignorePerms,attr" json:"ignorePerms" `
2019-02-12 06:58:24 +00:00
AutoNormalize bool ` xml:"autoNormalize,attr" json:"autoNormalize" default:"true" `
MinDiskFree Size ` xml:"minDiskFree" json:"minDiskFree" default:"1%" `
2019-01-25 08:52:21 +00:00
Versioning VersioningConfiguration ` xml:"versioning" json:"versioning" `
Copiers int ` xml:"copiers" json:"copiers" ` // This defines how many files are handled concurrently.
PullerMaxPendingKiB int ` xml:"pullerMaxPendingKiB" json:"pullerMaxPendingKiB" `
Hashers int ` xml:"hashers" json:"hashers" ` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Order PullOrder ` xml:"order" json:"order" `
IgnoreDelete bool ` xml:"ignoreDelete" json:"ignoreDelete" `
ScanProgressIntervalS int ` xml:"scanProgressIntervalS" json:"scanProgressIntervalS" ` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
PullerPauseS int ` xml:"pullerPauseS" json:"pullerPauseS" `
2019-02-12 06:58:24 +00:00
MaxConflicts int ` xml:"maxConflicts" json:"maxConflicts" default:"-1" `
2019-01-25 08:52:21 +00:00
DisableSparseFiles bool ` xml:"disableSparseFiles" json:"disableSparseFiles" `
DisableTempIndexes bool ` xml:"disableTempIndexes" json:"disableTempIndexes" `
Paused bool ` xml:"paused" json:"paused" `
WeakHashThresholdPct int ` xml:"weakHashThresholdPct" json:"weakHashThresholdPct" ` // Use weak hash if more than X percent of the file has changed. Set to -1 to always use weak hash.
MarkerName string ` xml:"markerName" json:"markerName" `
CopyOwnershipFromParent bool ` xml:"copyOwnershipFromParent" json:"copyOwnershipFromParent" `
2019-07-23 19:48:53 +00:00
RawModTimeWindowS int ` xml:"modTimeWindowS" json:"modTimeWindowS" `
2020-04-26 22:13:18 +00:00
MaxConcurrentWrites int ` xml:"maxConcurrentWrites" json:"maxConcurrentWrites" default:"2" `
2020-05-01 07:36:46 +00:00
DisableFsync bool ` xml:"disableFsync" json:"disableFsync" `
2015-10-27 10:37:03 +00:00
2019-07-23 19:48:53 +00:00
cachedFilesystem fs . Filesystem
cachedModTimeWindow time . Duration
2016-05-04 10:47:33 +00:00
2017-04-12 09:01:19 +00:00
DeprecatedReadOnly bool ` xml:"ro,attr,omitempty" json:"-" `
2017-04-27 05:46:19 +00:00
DeprecatedMinDiskFreePct float64 ` xml:"minDiskFreePct,omitempty" json:"-" `
2018-02-25 09:14:02 +00:00
DeprecatedPullers int ` xml:"pullers,omitempty" json:"-" `
2015-10-27 10:37:03 +00:00
}
type FolderDeviceConfiguration struct {
2016-11-07 16:40:48 +00:00
DeviceID protocol . DeviceID ` xml:"id,attr" json:"deviceID" `
IntroducedBy protocol . DeviceID ` xml:"introducedBy,attr" json:"introducedBy" `
2015-10-27 10:37:03 +00:00
}
2017-12-07 07:08:24 +00:00
func NewFolderConfiguration ( myID protocol . DeviceID , id , label string , fsType fs . FilesystemType , path string ) FolderConfiguration {
2015-11-07 08:47:31 +00:00
f := FolderConfiguration {
2019-02-12 06:58:24 +00:00
ID : id ,
Label : label ,
Devices : [ ] FolderDeviceConfiguration { { DeviceID : myID } } ,
FilesystemType : fsType ,
Path : path ,
2015-11-07 08:47:31 +00:00
}
2019-02-12 06:58:24 +00:00
util . SetDefaults ( & f )
2015-11-07 08:47:31 +00:00
f . prepare ( )
return f
}
2015-10-27 10:37:03 +00:00
func ( f FolderConfiguration ) Copy ( ) FolderConfiguration {
c := f
c . Devices = make ( [ ] FolderDeviceConfiguration , len ( f . Devices ) )
copy ( c . Devices , f . Devices )
2015-10-27 10:53:42 +00:00
c . Versioning = f . Versioning . Copy ( )
2015-10-27 10:37:03 +00:00
return c
}
2017-08-19 14:36:56 +00:00
func ( f FolderConfiguration ) Filesystem ( ) fs . Filesystem {
2015-10-27 10:37:03 +00:00
// This is intentionally not a pointer method, because things like
2017-08-19 14:36:56 +00:00
// cfg.Folders["default"].Filesystem() should be valid.
2019-07-14 09:03:14 +00:00
if f . cachedFilesystem == nil {
2017-08-19 14:36:56 +00:00
l . Infoln ( "bug: uncached filesystem call (should only happen in tests)" )
return fs . NewFilesystem ( f . FilesystemType , f . Path )
2015-10-27 10:37:03 +00:00
}
2017-08-19 14:36:56 +00:00
return f . cachedFilesystem
2015-10-27 10:37:03 +00:00
}
2019-07-23 19:48:53 +00:00
func ( f FolderConfiguration ) ModTimeWindow ( ) time . Duration {
return f . cachedModTimeWindow
}
2015-10-27 10:37:03 +00:00
func ( f * FolderConfiguration ) CreateMarker ( ) error {
2018-03-18 00:42:31 +00:00
if err := f . CheckPath ( ) ; err != ErrMarkerMissing {
2017-10-24 07:58:55 +00:00
return err
2015-10-27 10:37:03 +00:00
}
2017-11-05 12:18:05 +00:00
if f . MarkerName != DefaultMarkerName {
// Folder uses a non-default marker so we shouldn't mess with it.
// Pretend we created it and let the subsequent health checks sort
// out the actual situation.
return nil
}
2015-10-27 10:37:03 +00:00
2017-10-24 07:58:55 +00:00
permBits := fs . FileMode ( 0777 )
if runtime . GOOS == "windows" {
// Windows has no umask so we must chose a safer set of bits to
// begin with.
permBits = 0700
}
fs := f . Filesystem ( )
2017-11-05 12:18:05 +00:00
err := fs . Mkdir ( DefaultMarkerName , permBits )
2017-10-24 07:58:55 +00:00
if err != nil {
return err
}
if dir , err := fs . Open ( "." ) ; err != nil {
l . Debugln ( "folder marker: open . failed:" , err )
} else if err := dir . Sync ( ) ; err != nil {
l . Debugln ( "folder marker: fsync . failed:" , err )
}
2019-02-02 11:16:27 +00:00
fs . Hide ( DefaultMarkerName )
2017-10-24 07:58:55 +00:00
2015-10-27 10:37:03 +00:00
return nil
}
2017-10-24 07:58:55 +00:00
// CheckPath returns nil if the folder root exists and contains the marker file
func ( f * FolderConfiguration ) CheckPath ( ) error {
fi , err := f . Filesystem ( ) . Stat ( "." )
2017-11-26 07:51:22 +00:00
if err != nil {
if ! fs . IsNotExist ( err ) {
return err
}
2018-03-18 00:42:31 +00:00
return ErrPathMissing
2017-10-24 07:58:55 +00:00
}
2017-11-26 07:51:22 +00:00
// Users might have the root directory as a symlink or reparse point.
// Furthermore, OneDrive bullcrap uses a magic reparse point to the cloudz...
// Yet it's impossible for this to happen, as filesystem adds a trailing
// path separator to the root, so even if you point the filesystem at a file
// Stat ends up calling stat on C:\dir\file\ which, fails with "is not a directory"
// in the error check above, and we don't even get to here.
if ! fi . IsDir ( ) && ! fi . IsSymlink ( ) {
2018-03-18 00:42:31 +00:00
return ErrPathNotDirectory
2017-11-26 07:51:22 +00:00
}
2017-11-05 12:18:05 +00:00
_ , err = f . Filesystem ( ) . Stat ( f . MarkerName )
2017-10-24 07:58:55 +00:00
if err != nil {
2017-11-26 07:51:22 +00:00
if ! fs . IsNotExist ( err ) {
return err
}
2018-03-18 00:42:31 +00:00
return ErrMarkerMissing
2017-10-24 07:58:55 +00:00
}
return nil
2015-10-27 10:37:03 +00:00
}
2017-04-23 23:50:56 +00:00
func ( f * FolderConfiguration ) CreateRoot ( ) ( err error ) {
// Directory permission bits. Will be filtered down to something
// sane by umask on Unixes.
2017-08-19 14:36:56 +00:00
permBits := fs . FileMode ( 0777 )
2017-04-23 23:50:56 +00:00
if runtime . GOOS == "windows" {
// Windows has no umask so we must chose a safer set of bits to
// begin with.
permBits = 0700
}
2017-08-19 14:36:56 +00:00
filesystem := f . Filesystem ( )
if _ , err = filesystem . Stat ( "." ) ; fs . IsNotExist ( err ) {
2018-06-10 13:41:20 +00:00
err = filesystem . MkdirAll ( "." , permBits )
2017-04-23 23:50:56 +00:00
}
return err
}
2016-11-17 15:12:41 +00:00
func ( f FolderConfiguration ) Description ( ) string {
2016-12-19 09:12:06 +00:00
if f . Label == "" {
return f . ID
}
2016-11-17 15:12:41 +00:00
return fmt . Sprintf ( "%q (%s)" , f . Label , f . ID )
}
2015-10-27 10:37:03 +00:00
func ( f * FolderConfiguration ) DeviceIDs ( ) [ ] protocol . DeviceID {
deviceIDs := make ( [ ] protocol . DeviceID , len ( f . Devices ) )
for i , n := range f . Devices {
deviceIDs [ i ] = n . DeviceID
}
return deviceIDs
}
2015-11-05 08:01:47 +00:00
func ( f * FolderConfiguration ) prepare ( ) {
2019-07-14 09:03:14 +00:00
f . cachedFilesystem = fs . NewFilesystem ( f . FilesystemType , f . Path )
2015-11-05 08:01:47 +00:00
if f . RescanIntervalS > MaxRescanIntervalS {
f . RescanIntervalS = MaxRescanIntervalS
} else if f . RescanIntervalS < 0 {
f . RescanIntervalS = 0
}
2016-05-09 11:30:19 +00:00
2017-10-20 14:52:55 +00:00
if f . FSWatcherDelayS <= 0 {
f . FSWatcherEnabled = false
f . FSWatcherDelayS = 10
}
2016-05-09 11:30:19 +00:00
if f . Versioning . Params == nil {
f . Versioning . Params = make ( map [ string ] string )
}
2017-01-04 21:04:13 +00:00
if f . WeakHashThresholdPct == 0 {
f . WeakHashThresholdPct = 25
}
2017-11-05 12:18:05 +00:00
if f . MarkerName == "" {
f . MarkerName = DefaultMarkerName
}
2019-07-23 19:48:53 +00:00
switch {
case f . RawModTimeWindowS > 0 :
f . cachedModTimeWindow = time . Duration ( f . RawModTimeWindowS ) * time . Second
case runtime . GOOS == "android" :
2019-08-15 13:51:09 +00:00
if usage , err := disk . Usage ( f . Filesystem ( ) . URI ( ) ) ; err != nil {
2019-07-23 19:48:53 +00:00
f . cachedModTimeWindow = 2 * time . Second
2019-08-15 13:51:09 +00:00
l . Debugf ( ` Detecting FS at "%v" on android: Setting mtime window to 2s: err == "%v" ` , f . Path , err )
} else if usage . Fstype == "" || strings . Contains ( strings . ToLower ( usage . Fstype ) , "fat" ) {
f . cachedModTimeWindow = 2 * time . Second
l . Debugf ( ` Detecting FS at "%v" on android: Setting mtime window to 2s: usage.Fstype == "%v" ` , f . Path , usage . Fstype )
2019-07-30 13:23:00 +00:00
} else {
l . Debugf ( ` Detecting FS at %v on android: Leaving mtime window at 0: usage.Fstype == "%v" ` , f . Path , usage . Fstype )
2019-07-23 19:48:53 +00:00
}
}
2015-11-05 08:01:47 +00:00
}
2017-12-07 08:33:32 +00:00
// RequiresRestartOnly returns a copy with only the attributes that require
// restart on change.
func ( f FolderConfiguration ) RequiresRestartOnly ( ) FolderConfiguration {
copy := f
// Manual handling for things that are not taken care of by the tag
// copier, yet should not cause a restart.
copy . cachedFilesystem = nil
blank := FolderConfiguration { }
util . CopyMatchingTag ( & blank , & copy , "restart" , func ( v string ) bool {
if len ( v ) > 0 && v != "false" {
panic ( fmt . Sprintf ( ` unexpected tag value: %s. expected untagged or "false" ` , v ) )
}
return v == "false"
} )
return copy
}
2018-06-06 21:34:11 +00:00
func ( f * FolderConfiguration ) SharedWith ( device protocol . DeviceID ) bool {
for _ , dev := range f . Devices {
if dev . DeviceID == device {
return true
}
}
return false
}
2018-08-25 08:16:38 +00:00
func ( f * FolderConfiguration ) CheckAvailableSpace ( req int64 ) error {
2018-10-12 11:34:56 +00:00
val := f . MinDiskFree . BaseValue ( )
if val <= 0 {
return nil
}
2018-08-25 08:16:38 +00:00
fs := f . Filesystem ( )
usage , err := fs . Usage ( "." )
if err != nil {
return nil
}
usage . Free -= req
if usage . Free > 0 {
2019-02-12 12:25:11 +00:00
if err := CheckFreeSpace ( f . MinDiskFree , usage ) ; err == nil {
2018-08-25 08:16:38 +00:00
return nil
}
}
return fmt . Errorf ( "insufficient space in %v %v" , fs . Type ( ) , fs . URI ( ) )
2017-10-24 07:58:55 +00:00
}