2015-10-27 11:37:03 +01: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 07:52:18 +01:00
// You can obtain one at https://mozilla.org/MPL/2.0/.
2015-10-27 11:37:03 +01:00
package config
import (
2017-10-24 09:58:55 +02:00
"errors"
2016-11-17 16:12:41 +01:00
"fmt"
2015-10-27 11:37:03 +01:00
"runtime"
2017-08-19 16:36:56 +02:00
"github.com/syncthing/syncthing/lib/fs"
2015-10-27 11:37:03 +01:00
"github.com/syncthing/syncthing/lib/protocol"
2017-12-07 09:33:32 +01:00
"github.com/syncthing/syncthing/lib/util"
2018-01-01 15:39:23 +01:00
"github.com/syncthing/syncthing/lib/versioner"
2015-10-27 11:37:03 +01:00
)
2017-10-24 09:58:55 +02:00
var (
2018-03-18 01:42:31 +01:00
ErrPathNotDirectory = errors . New ( "folder path not a directory" )
ErrPathMissing = errors . New ( "folder path missing" )
ErrMarkerMissing = errors . New ( "folder marker missing" )
2017-10-24 09:58:55 +02:00
)
2017-11-05 13:18:05 +01:00
const DefaultMarkerName = ".stfolder"
2015-10-27 11:37:03 +01:00
type FolderConfiguration struct {
2017-12-07 09:33:32 +01:00
ID string ` xml:"id,attr" json:"id" `
Label string ` xml:"label,attr" json:"label" restart:"false" `
2017-08-19 16:36:56 +02:00
FilesystemType fs . FilesystemType ` xml:"filesystemType" json:"filesystemType" `
Path string ` xml:"path,attr" json:"path" `
2016-05-04 13:26:36 +02:00
Type FolderType ` xml:"type,attr" json:"type" `
2015-10-27 11:37:03 +01:00
Devices [ ] FolderDeviceConfiguration ` xml:"device" json:"devices" `
RescanIntervalS int ` xml:"rescanIntervalS,attr" json:"rescanIntervalS" `
2017-10-20 16:52:55 +02:00
FSWatcherEnabled bool ` xml:"fsWatcherEnabled,attr" json:"fsWatcherEnabled" `
FSWatcherDelayS int ` xml:"fsWatcherDelayS,attr" json:"fsWatcherDelayS" `
2015-10-27 11:37:03 +01:00
IgnorePerms bool ` xml:"ignorePerms,attr" json:"ignorePerms" `
AutoNormalize bool ` xml:"autoNormalize,attr" json:"autoNormalize" `
2017-04-12 11:01:19 +02:00
MinDiskFree Size ` xml:"minDiskFree" json:"minDiskFree" `
2015-10-27 11:37:03 +01:00
Versioning VersioningConfiguration ` xml:"versioning" json:"versioning" `
Copiers int ` xml:"copiers" json:"copiers" ` // This defines how many files are handled concurrently.
2018-02-25 10:14:02 +01:00
PullerMaxPendingKiB int ` xml:"pullerMaxPendingKiB" json:"pullerMaxPendingKiB" `
2015-10-27 11:37:03 +01:00
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" `
MaxConflicts int ` xml:"maxConflicts" json:"maxConflicts" `
2015-11-21 16:30:53 +01:00
DisableSparseFiles bool ` xml:"disableSparseFiles" json:"disableSparseFiles" `
2016-04-15 12:59:41 +02:00
DisableTempIndexes bool ` xml:"disableTempIndexes" json:"disableTempIndexes" `
2016-12-21 19:41:25 +01:00
Paused bool ` xml:"paused" json:"paused" `
2017-01-04 22:04:13 +01:00
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.
2017-11-05 13:18:05 +01:00
MarkerName string ` xml:"markerName" json:"markerName" `
2018-04-16 20:08:50 +02:00
UseLargeBlocks bool ` xml:"useLargeBlocks" json:"useLargeBlocks" `
2015-10-27 11:37:03 +01:00
2017-08-19 16:36:56 +02:00
cachedFilesystem fs . Filesystem
2016-05-04 12:47:33 +02:00
2017-04-12 11:01:19 +02:00
DeprecatedReadOnly bool ` xml:"ro,attr,omitempty" json:"-" `
2017-04-27 07:46:19 +02:00
DeprecatedMinDiskFreePct float64 ` xml:"minDiskFreePct,omitempty" json:"-" `
2018-02-25 10:14:02 +01:00
DeprecatedPullers int ` xml:"pullers,omitempty" json:"-" `
2015-10-27 11:37:03 +01:00
}
type FolderDeviceConfiguration struct {
2016-11-07 17:40:48 +01:00
DeviceID protocol . DeviceID ` xml:"id,attr" json:"deviceID" `
IntroducedBy protocol . DeviceID ` xml:"introducedBy,attr" json:"introducedBy" `
2015-10-27 11:37:03 +01:00
}
2017-12-07 08:08:24 +01:00
func NewFolderConfiguration ( myID protocol . DeviceID , id , label string , fsType fs . FilesystemType , path string ) FolderConfiguration {
2015-11-07 09:47:31 +01:00
f := FolderConfiguration {
2018-03-25 22:05:47 +02:00
ID : id ,
Label : label ,
RescanIntervalS : 3600 ,
FSWatcherEnabled : true ,
FSWatcherDelayS : 10 ,
MinDiskFree : Size { Value : 1 , Unit : "%" } ,
Devices : [ ] FolderDeviceConfiguration { { DeviceID : myID } } ,
AutoNormalize : true ,
MaxConflicts : - 1 ,
FilesystemType : fsType ,
Path : path ,
2015-11-07 09:47:31 +01:00
}
f . prepare ( )
return f
}
2015-10-27 11:37:03 +01:00
func ( f FolderConfiguration ) Copy ( ) FolderConfiguration {
c := f
c . Devices = make ( [ ] FolderDeviceConfiguration , len ( f . Devices ) )
copy ( c . Devices , f . Devices )
2015-10-27 11:53:42 +01:00
c . Versioning = f . Versioning . Copy ( )
2015-10-27 11:37:03 +01:00
return c
}
2017-08-19 16:36:56 +02:00
func ( f FolderConfiguration ) Filesystem ( ) fs . Filesystem {
2015-10-27 11:37:03 +01:00
// This is intentionally not a pointer method, because things like
2017-08-19 16:36:56 +02:00
// cfg.Folders["default"].Filesystem() should be valid.
if f . cachedFilesystem == nil && f . Path != "" {
l . Infoln ( "bug: uncached filesystem call (should only happen in tests)" )
return fs . NewFilesystem ( f . FilesystemType , f . Path )
2015-10-27 11:37:03 +01:00
}
2017-08-19 16:36:56 +02:00
return f . cachedFilesystem
2015-10-27 11:37:03 +01:00
}
2018-01-01 15:39:23 +01:00
func ( f FolderConfiguration ) Versioner ( ) versioner . Versioner {
if f . Versioning . Type == "" {
return nil
}
versionerFactory , ok := versioner . Factories [ f . Versioning . Type ]
if ! ok {
l . Fatalf ( "Requested versioning type %q that does not exist" , f . Versioning . Type )
}
return versionerFactory ( f . ID , f . Filesystem ( ) , f . Versioning . Params )
}
2015-10-27 11:37:03 +01:00
func ( f * FolderConfiguration ) CreateMarker ( ) error {
2018-03-18 01:42:31 +01:00
if err := f . CheckPath ( ) ; err != ErrMarkerMissing {
2017-10-24 09:58:55 +02:00
return err
2015-10-27 11:37:03 +01:00
}
2017-11-05 13:18:05 +01: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 11:37:03 +01:00
2017-10-24 09:58:55 +02: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 13:18:05 +01:00
err := fs . Mkdir ( DefaultMarkerName , permBits )
2017-10-24 09:58:55 +02: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 )
}
2017-11-05 13:18:05 +01:00
fs . Hide ( DefaultMarkerName )
2017-10-24 09:58:55 +02:00
2015-10-27 11:37:03 +01:00
return nil
}
2017-10-24 09:58:55 +02: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 08:51:22 +01:00
if err != nil {
if ! fs . IsNotExist ( err ) {
return err
}
2018-03-18 01:42:31 +01:00
return ErrPathMissing
2017-10-24 09:58:55 +02:00
}
2017-11-26 08:51:22 +01: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 01:42:31 +01:00
return ErrPathNotDirectory
2017-11-26 08:51:22 +01:00
}
2017-11-05 13:18:05 +01:00
_ , err = f . Filesystem ( ) . Stat ( f . MarkerName )
2017-10-24 09:58:55 +02:00
if err != nil {
2017-11-26 08:51:22 +01:00
if ! fs . IsNotExist ( err ) {
return err
}
2018-03-18 01:42:31 +01:00
return ErrMarkerMissing
2017-10-24 09:58:55 +02:00
}
return nil
2015-10-27 11:37:03 +01:00
}
2017-04-24 01:50:56 +02:00
func ( f * FolderConfiguration ) CreateRoot ( ) ( err error ) {
// Directory permission bits. Will be filtered down to something
// sane by umask on Unixes.
2017-08-19 16:36:56 +02:00
permBits := fs . FileMode ( 0777 )
2017-04-24 01:50:56 +02: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 16:36:56 +02:00
filesystem := f . Filesystem ( )
if _ , err = filesystem . Stat ( "." ) ; fs . IsNotExist ( err ) {
2018-06-10 15:41:20 +02:00
err = filesystem . MkdirAll ( "." , permBits )
2017-04-24 01:50:56 +02:00
}
return err
}
2016-11-17 16:12:41 +01:00
func ( f FolderConfiguration ) Description ( ) string {
2016-12-19 10:12:06 +01:00
if f . Label == "" {
return f . ID
}
2016-11-17 16:12:41 +01:00
return fmt . Sprintf ( "%q (%s)" , f . Label , f . ID )
}
2015-10-27 11:37:03 +01: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 09:01:47 +01:00
func ( f * FolderConfiguration ) prepare ( ) {
2017-08-19 16:36:56 +02:00
if f . Path != "" {
f . cachedFilesystem = fs . NewFilesystem ( f . FilesystemType , f . Path )
2015-11-05 09:01:47 +01:00
}
if f . RescanIntervalS > MaxRescanIntervalS {
f . RescanIntervalS = MaxRescanIntervalS
} else if f . RescanIntervalS < 0 {
f . RescanIntervalS = 0
}
2016-05-09 13:30:19 +02:00
2017-10-20 16:52:55 +02:00
if f . FSWatcherDelayS <= 0 {
f . FSWatcherEnabled = false
f . FSWatcherDelayS = 10
}
2016-05-09 13:30:19 +02:00
if f . Versioning . Params == nil {
f . Versioning . Params = make ( map [ string ] string )
}
2017-01-04 22:04:13 +01:00
if f . WeakHashThresholdPct == 0 {
f . WeakHashThresholdPct = 25
}
2017-11-05 13:18:05 +01:00
if f . MarkerName == "" {
f . MarkerName = DefaultMarkerName
}
2015-11-05 09:01:47 +01:00
}
2017-12-07 09:33:32 +01: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 23:34:11 +02:00
func ( f * FolderConfiguration ) SharedWith ( device protocol . DeviceID ) bool {
for _ , dev := range f . Devices {
if dev . DeviceID == device {
return true
}
}
return false
}
2015-10-27 11:37:03 +01:00
type FolderDeviceConfigurationList [ ] FolderDeviceConfiguration
func ( l FolderDeviceConfigurationList ) Less ( a , b int ) bool {
return l [ a ] . DeviceID . Compare ( l [ b ] . DeviceID ) == - 1
}
func ( l FolderDeviceConfigurationList ) Swap ( a , b int ) {
l [ a ] , l [ b ] = l [ b ] , l [ a ]
}
func ( l FolderDeviceConfigurationList ) Len ( ) int {
return len ( l )
}
2017-10-24 09:58:55 +02:00
func ( f * FolderConfiguration ) CheckFreeSpace ( ) ( err error ) {
return checkFreeSpace ( f . MinDiskFree , f . Filesystem ( ) )
}
2018-04-22 18:01:52 +02:00
type FolderConfigurationList [ ] FolderConfiguration
func ( l FolderConfigurationList ) Len ( ) int {
return len ( l )
}
func ( l FolderConfigurationList ) Less ( a , b int ) bool {
return l [ a ] . ID < l [ b ] . ID
}
func ( l FolderConfigurationList ) Swap ( a , b int ) {
l [ a ] , l [ b ] = l [ b ] , l [ a ]
}