folderconfiguration.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. "runtime"
  11. "strings"
  12. "time"
  13. "github.com/shirou/gopsutil/disk"
  14. "github.com/syncthing/syncthing/lib/fs"
  15. "github.com/syncthing/syncthing/lib/protocol"
  16. "github.com/syncthing/syncthing/lib/util"
  17. )
  18. var (
  19. ErrPathNotDirectory = errors.New("folder path not a directory")
  20. ErrPathMissing = errors.New("folder path missing")
  21. ErrMarkerMissing = errors.New("folder marker missing (this indicates potential data loss, search docs/forum to get information about how to proceed)")
  22. )
  23. const DefaultMarkerName = ".stfolder"
  24. func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.FilesystemType, path string) FolderConfiguration {
  25. f := FolderConfiguration{
  26. ID: id,
  27. Label: label,
  28. Devices: []FolderDeviceConfiguration{{DeviceID: myID}},
  29. FilesystemType: fsType,
  30. Path: path,
  31. }
  32. util.SetDefaults(&f)
  33. f.prepare()
  34. return f
  35. }
  36. func (f FolderConfiguration) Copy() FolderConfiguration {
  37. c := f
  38. c.Devices = make([]FolderDeviceConfiguration, len(f.Devices))
  39. copy(c.Devices, f.Devices)
  40. c.Versioning = f.Versioning.Copy()
  41. return c
  42. }
  43. func (f FolderConfiguration) Filesystem() fs.Filesystem {
  44. // This is intentionally not a pointer method, because things like
  45. // cfg.Folders["default"].Filesystem() should be valid.
  46. var opts []fs.Option
  47. if f.FilesystemType == fs.FilesystemTypeBasic && f.JunctionsAsDirs {
  48. opts = append(opts, fs.WithJunctionsAsDirs())
  49. }
  50. filesystem := fs.NewFilesystem(f.FilesystemType, f.Path, opts...)
  51. if !f.CaseSensitiveFS {
  52. filesystem = fs.NewCaseFilesystem(filesystem)
  53. }
  54. return filesystem
  55. }
  56. func (f FolderConfiguration) ModTimeWindow() time.Duration {
  57. dur := time.Duration(f.RawModTimeWindowS) * time.Second
  58. if f.RawModTimeWindowS < 1 && runtime.GOOS == "android" {
  59. if usage, err := disk.Usage(f.Filesystem().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 usage.Fstype == "" || strings.Contains(strings.ToLower(usage.Fstype), "fat") {
  63. dur = 2 * time.Second
  64. l.Debugf(`Detecting FS at "%v" on android: Setting mtime window to 2s: usage.Fstype == "%v"`, f.Path, usage.Fstype)
  65. } else {
  66. l.Debugf(`Detecting FS at %v on android: Leaving mtime window at 0: 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 runtime.GOOS == "windows" {
  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()
  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().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().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 runtime.GOOS == "windows" {
  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()
  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() {
  156. if f.RescanIntervalS > MaxRescanIntervalS {
  157. f.RescanIntervalS = MaxRescanIntervalS
  158. } else if f.RescanIntervalS < 0 {
  159. f.RescanIntervalS = 0
  160. }
  161. if f.FSWatcherDelayS <= 0 {
  162. f.FSWatcherEnabled = false
  163. f.FSWatcherDelayS = 10
  164. }
  165. if f.Versioning.Params == nil {
  166. f.Versioning.Params = make(map[string]string)
  167. }
  168. if f.Versioning.CleanupIntervalS > MaxRescanIntervalS {
  169. f.Versioning.CleanupIntervalS = MaxRescanIntervalS
  170. } else if f.Versioning.CleanupIntervalS < 0 {
  171. f.Versioning.CleanupIntervalS = 0
  172. }
  173. if f.WeakHashThresholdPct == 0 {
  174. f.WeakHashThresholdPct = 25
  175. }
  176. if f.MarkerName == "" {
  177. f.MarkerName = DefaultMarkerName
  178. }
  179. }
  180. // RequiresRestartOnly returns a copy with only the attributes that require
  181. // restart on change.
  182. func (f FolderConfiguration) RequiresRestartOnly() FolderConfiguration {
  183. copy := f
  184. // Manual handling for things that are not taken care of by the tag
  185. // copier, yet should not cause a restart.
  186. blank := FolderConfiguration{}
  187. util.CopyMatchingTag(&blank, &copy, "restart", func(v string) bool {
  188. if len(v) > 0 && v != "false" {
  189. panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "false"`, v))
  190. }
  191. return v == "false"
  192. })
  193. return copy
  194. }
  195. func (f *FolderConfiguration) SharedWith(device protocol.DeviceID) bool {
  196. for _, dev := range f.Devices {
  197. if dev.DeviceID == device {
  198. return true
  199. }
  200. }
  201. return false
  202. }
  203. func (f *FolderConfiguration) CheckAvailableSpace(req uint64) error {
  204. val := f.MinDiskFree.BaseValue()
  205. if val <= 0 {
  206. return nil
  207. }
  208. fs := f.Filesystem()
  209. usage, err := fs.Usage(".")
  210. if err != nil {
  211. return nil
  212. }
  213. if !checkAvailableSpace(req, f.MinDiskFree, usage) {
  214. return fmt.Errorf("insufficient space in %v %v", fs.Type(), fs.URI())
  215. }
  216. return nil
  217. }