folderconfiguration.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package config
  7. import (
  8. "errors"
  9. "fmt"
  10. "sort"
  11. "strings"
  12. "time"
  13. "github.com/shirou/gopsutil/v3/disk"
  14. "github.com/syncthing/syncthing/lib/build"
  15. "github.com/syncthing/syncthing/lib/db"
  16. "github.com/syncthing/syncthing/lib/fs"
  17. "github.com/syncthing/syncthing/lib/protocol"
  18. "github.com/syncthing/syncthing/lib/util"
  19. )
  20. var (
  21. ErrPathNotDirectory = errors.New("folder path not a directory")
  22. ErrPathMissing = errors.New("folder path missing")
  23. ErrMarkerMissing = errors.New("folder marker missing (this indicates potential data loss, search docs/forum to get information about how to proceed)")
  24. )
  25. const (
  26. DefaultMarkerName = ".stfolder"
  27. EncryptionTokenName = "syncthing-encryption_password_token"
  28. maxConcurrentWritesDefault = 2
  29. maxConcurrentWritesLimit = 64
  30. )
  31. func (f FolderConfiguration) Copy() FolderConfiguration {
  32. c := f
  33. c.Devices = make([]FolderDeviceConfiguration, len(f.Devices))
  34. copy(c.Devices, f.Devices)
  35. c.Versioning = f.Versioning.Copy()
  36. return c
  37. }
  38. // Filesystem creates a filesystem for the path and options of this folder.
  39. // The fset parameter may be nil, in which case no mtime handling on top of
  40. // the fileystem is provided.
  41. func (f FolderConfiguration) Filesystem(fset *db.FileSet) fs.Filesystem {
  42. // This is intentionally not a pointer method, because things like
  43. // cfg.Folders["default"].Filesystem(nil) should be valid.
  44. opts := make([]fs.Option, 0, 3)
  45. if f.FilesystemType == fs.FilesystemTypeBasic && f.JunctionsAsDirs {
  46. opts = append(opts, new(fs.OptionJunctionsAsDirs))
  47. }
  48. if !f.CaseSensitiveFS {
  49. opts = append(opts, new(fs.OptionDetectCaseConflicts))
  50. }
  51. if fset != nil {
  52. opts = append(opts, fset.MtimeOption())
  53. }
  54. return fs.NewFilesystem(f.FilesystemType, f.Path, opts...)
  55. }
  56. func (f FolderConfiguration) ModTimeWindow() time.Duration {
  57. dur := time.Duration(f.RawModTimeWindowS) * time.Second
  58. if f.RawModTimeWindowS < 1 && build.IsAndroid {
  59. if usage, err := disk.Usage(f.Filesystem(nil).URI()); err != nil {
  60. dur = 2 * time.Second
  61. l.Debugf(`Detecting FS at "%v" on android: Setting mtime window to 2s: err == "%v"`, f.Path, err)
  62. } else if strings.HasPrefix(strings.ToLower(usage.Fstype), "ext2") || strings.HasPrefix(strings.ToLower(usage.Fstype), "ext3") || strings.HasPrefix(strings.ToLower(usage.Fstype), "ext4") {
  63. l.Debugf(`Detecting FS at %v on android: Leaving mtime window at 0: usage.Fstype == "%v"`, f.Path, usage.Fstype)
  64. } else {
  65. dur = 2 * time.Second
  66. l.Debugf(`Detecting FS at "%v" on android: Setting mtime window to 2s: usage.Fstype == "%v"`, f.Path, usage.Fstype)
  67. }
  68. }
  69. return dur
  70. }
  71. func (f *FolderConfiguration) CreateMarker() error {
  72. if err := f.CheckPath(); err != ErrMarkerMissing {
  73. return err
  74. }
  75. if f.MarkerName != DefaultMarkerName {
  76. // Folder uses a non-default marker so we shouldn't mess with it.
  77. // Pretend we created it and let the subsequent health checks sort
  78. // out the actual situation.
  79. return nil
  80. }
  81. permBits := fs.FileMode(0777)
  82. if build.IsWindows {
  83. // Windows has no umask so we must chose a safer set of bits to
  84. // begin with.
  85. permBits = 0700
  86. }
  87. fs := f.Filesystem(nil)
  88. err := fs.Mkdir(DefaultMarkerName, permBits)
  89. if err != nil {
  90. return err
  91. }
  92. if dir, err := fs.Open("."); err != nil {
  93. l.Debugln("folder marker: open . failed:", err)
  94. } else if err := dir.Sync(); err != nil {
  95. l.Debugln("folder marker: fsync . failed:", err)
  96. }
  97. fs.Hide(DefaultMarkerName)
  98. return nil
  99. }
  100. // CheckPath returns nil if the folder root exists and contains the marker file
  101. func (f *FolderConfiguration) CheckPath() error {
  102. fi, err := f.Filesystem(nil).Stat(".")
  103. if err != nil {
  104. if !fs.IsNotExist(err) {
  105. return err
  106. }
  107. return ErrPathMissing
  108. }
  109. // Users might have the root directory as a symlink or reparse point.
  110. // Furthermore, OneDrive bullcrap uses a magic reparse point to the cloudz...
  111. // Yet it's impossible for this to happen, as filesystem adds a trailing
  112. // path separator to the root, so even if you point the filesystem at a file
  113. // Stat ends up calling stat on C:\dir\file\ which, fails with "is not a directory"
  114. // in the error check above, and we don't even get to here.
  115. if !fi.IsDir() && !fi.IsSymlink() {
  116. return ErrPathNotDirectory
  117. }
  118. _, err = f.Filesystem(nil).Stat(f.MarkerName)
  119. if err != nil {
  120. if !fs.IsNotExist(err) {
  121. return err
  122. }
  123. return ErrMarkerMissing
  124. }
  125. return nil
  126. }
  127. func (f *FolderConfiguration) CreateRoot() (err error) {
  128. // Directory permission bits. Will be filtered down to something
  129. // sane by umask on Unixes.
  130. permBits := fs.FileMode(0777)
  131. if build.IsWindows {
  132. // Windows has no umask so we must chose a safer set of bits to
  133. // begin with.
  134. permBits = 0700
  135. }
  136. filesystem := f.Filesystem(nil)
  137. if _, err = filesystem.Stat("."); fs.IsNotExist(err) {
  138. err = filesystem.MkdirAll(".", permBits)
  139. }
  140. return err
  141. }
  142. func (f FolderConfiguration) Description() string {
  143. if f.Label == "" {
  144. return f.ID
  145. }
  146. return fmt.Sprintf("%q (%s)", f.Label, f.ID)
  147. }
  148. func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
  149. deviceIDs := make([]protocol.DeviceID, len(f.Devices))
  150. for i, n := range f.Devices {
  151. deviceIDs[i] = n.DeviceID
  152. }
  153. return deviceIDs
  154. }
  155. func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
  156. // Ensure that
  157. // - any loose devices are not present in the wrong places
  158. // - there are no duplicate devices
  159. // - we are part of the devices
  160. f.Devices = ensureExistingDevices(f.Devices, existingDevices)
  161. f.Devices = ensureNoDuplicateFolderDevices(f.Devices)
  162. f.Devices = ensureDevicePresent(f.Devices, myID)
  163. sort.Slice(f.Devices, func(a, b int) bool {
  164. return f.Devices[a].DeviceID.Compare(f.Devices[b].DeviceID) == -1
  165. })
  166. if f.RescanIntervalS > MaxRescanIntervalS {
  167. f.RescanIntervalS = MaxRescanIntervalS
  168. } else if f.RescanIntervalS < 0 {
  169. f.RescanIntervalS = 0
  170. }
  171. if f.FSWatcherDelayS <= 0 {
  172. f.FSWatcherEnabled = false
  173. f.FSWatcherDelayS = 10
  174. }
  175. if f.Versioning.CleanupIntervalS > MaxRescanIntervalS {
  176. f.Versioning.CleanupIntervalS = MaxRescanIntervalS
  177. } else if f.Versioning.CleanupIntervalS < 0 {
  178. f.Versioning.CleanupIntervalS = 0
  179. }
  180. if f.WeakHashThresholdPct == 0 {
  181. f.WeakHashThresholdPct = 25
  182. }
  183. if f.MarkerName == "" {
  184. f.MarkerName = DefaultMarkerName
  185. }
  186. if f.MaxConcurrentWrites <= 0 {
  187. f.MaxConcurrentWrites = maxConcurrentWritesDefault
  188. } else if f.MaxConcurrentWrites > maxConcurrentWritesLimit {
  189. f.MaxConcurrentWrites = maxConcurrentWritesLimit
  190. }
  191. if f.Type == FolderTypeReceiveEncrypted {
  192. f.DisableTempIndexes = true
  193. f.IgnorePerms = true
  194. }
  195. }
  196. // RequiresRestartOnly returns a copy with only the attributes that require
  197. // restart on change.
  198. func (f FolderConfiguration) RequiresRestartOnly() FolderConfiguration {
  199. copy := f
  200. // Manual handling for things that are not taken care of by the tag
  201. // copier, yet should not cause a restart.
  202. blank := FolderConfiguration{}
  203. util.CopyMatchingTag(&blank, &copy, "restart", func(v string) bool {
  204. if len(v) > 0 && v != "false" {
  205. panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "false"`, v))
  206. }
  207. return v == "false"
  208. })
  209. return copy
  210. }
  211. func (f *FolderConfiguration) Device(device protocol.DeviceID) (FolderDeviceConfiguration, bool) {
  212. for _, dev := range f.Devices {
  213. if dev.DeviceID == device {
  214. return dev, true
  215. }
  216. }
  217. return FolderDeviceConfiguration{}, false
  218. }
  219. func (f *FolderConfiguration) SharedWith(device protocol.DeviceID) bool {
  220. _, ok := f.Device(device)
  221. return ok
  222. }
  223. func (f *FolderConfiguration) CheckAvailableSpace(req uint64) error {
  224. val := f.MinDiskFree.BaseValue()
  225. if val <= 0 {
  226. return nil
  227. }
  228. fs := f.Filesystem(nil)
  229. usage, err := fs.Usage(".")
  230. if err != nil {
  231. return nil
  232. }
  233. if err := checkAvailableSpace(req, f.MinDiskFree, usage); err != nil {
  234. return fmt.Errorf("insufficient space in folder %v (%v): %w", f.Description(), fs.URI(), err)
  235. }
  236. return nil
  237. }