wrapper.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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. //go:generate -command counterfeiter go run github.com/maxbrunsfeld/counterfeiter/v6
  7. //go:generate counterfeiter -o mocks/mocked_wrapper.go --fake-name Wrapper . Wrapper
  8. package config
  9. import (
  10. "context"
  11. "errors"
  12. "os"
  13. "reflect"
  14. "sync/atomic"
  15. "time"
  16. "github.com/syncthing/syncthing/lib/events"
  17. "github.com/syncthing/syncthing/lib/osutil"
  18. "github.com/syncthing/syncthing/lib/protocol"
  19. "github.com/syncthing/syncthing/lib/sync"
  20. "github.com/thejerf/suture/v4"
  21. )
  22. const (
  23. maxModifications = 1000
  24. minSaveInterval = 5 * time.Second
  25. )
  26. var errTooManyModifications = errors.New("too many concurrent config modifications")
  27. // The Committer interface is implemented by objects that need to know about
  28. // or have a say in configuration changes.
  29. //
  30. // When the configuration is about to be changed, VerifyConfiguration() is
  31. // called for each subscribing object, with the old and new configuration. A
  32. // nil error is returned if the new configuration is acceptable (i.e. does not
  33. // contain any errors that would prevent it from being a valid config).
  34. // Otherwise an error describing the problem is returned.
  35. //
  36. // If any subscriber returns an error from VerifyConfiguration(), the
  37. // configuration change is not committed and an error is returned to whoever
  38. // tried to commit the broken config.
  39. //
  40. // If all verification calls returns nil, CommitConfiguration() is called for
  41. // each subscribing object. The callee returns true if the new configuration
  42. // has been successfully applied, otherwise false. Any Commit() call returning
  43. // false will result in a "restart needed" response to the API/user. Note that
  44. // the new configuration will still have been applied by those who were
  45. // capable of doing so.
  46. //
  47. // A Committer must take care not to hold any locks while changing the
  48. // configuration (e.g. calling Wrapper.SetFolder), that are also acquired in any
  49. // methods of the Committer interface.
  50. type Committer interface {
  51. VerifyConfiguration(from, to Configuration) error
  52. CommitConfiguration(from, to Configuration) (handled bool)
  53. String() string
  54. }
  55. // Waiter allows to wait for the given config operation to complete.
  56. type Waiter interface {
  57. Wait()
  58. }
  59. type noopWaiter struct{}
  60. func (noopWaiter) Wait() {}
  61. // ModifyFunction gets a pointer to a copy of the currently active configuration
  62. // for modification.
  63. type ModifyFunction func(*Configuration)
  64. // Wrapper handles a Configuration, i.e. it provides methods to access, change
  65. // and save the config, and notifies registered subscribers (Committer) of
  66. // changes.
  67. //
  68. // Modify allows changing the currently active configuration through the given
  69. // ModifyFunction. It can be called concurrently: All calls will be queued and
  70. // called in order.
  71. type Wrapper interface {
  72. ConfigPath() string
  73. MyID() protocol.DeviceID
  74. RawCopy() Configuration
  75. RequiresRestart() bool
  76. Save() error
  77. Modify(ModifyFunction) (Waiter, error)
  78. RemoveFolder(id string) (Waiter, error)
  79. RemoveDevice(id protocol.DeviceID) (Waiter, error)
  80. GUI() GUIConfiguration
  81. LDAP() LDAPConfiguration
  82. Options() OptionsConfiguration
  83. Folder(id string) (FolderConfiguration, bool)
  84. Folders() map[string]FolderConfiguration
  85. FolderList() []FolderConfiguration
  86. FolderPasswords(device protocol.DeviceID) map[string]string
  87. DefaultFolder() FolderConfiguration
  88. Device(id protocol.DeviceID) (DeviceConfiguration, bool)
  89. Devices() map[protocol.DeviceID]DeviceConfiguration
  90. DeviceList() []DeviceConfiguration
  91. DefaultDevice() DeviceConfiguration
  92. IgnoredDevices() []ObservedDevice
  93. IgnoredDevice(id protocol.DeviceID) bool
  94. IgnoredFolder(device protocol.DeviceID, folder string) bool
  95. Subscribe(c Committer) Configuration
  96. Unsubscribe(c Committer)
  97. suture.Service
  98. }
  99. type wrapper struct {
  100. cfg Configuration
  101. path string
  102. evLogger events.Logger
  103. myID protocol.DeviceID
  104. queue chan modifyEntry
  105. waiter Waiter // Latest ongoing config change
  106. subs []Committer
  107. mut sync.Mutex
  108. requiresRestart uint32 // an atomic bool
  109. }
  110. // Wrap wraps an existing Configuration structure and ties it to a file on
  111. // disk.
  112. // The returned Wrapper is a suture.Service, thus needs to be started (added to
  113. // a supervisor).
  114. func Wrap(path string, cfg Configuration, myID protocol.DeviceID, evLogger events.Logger) Wrapper {
  115. w := &wrapper{
  116. cfg: cfg,
  117. path: path,
  118. evLogger: evLogger,
  119. myID: myID,
  120. queue: make(chan modifyEntry, maxModifications),
  121. waiter: noopWaiter{}, // Noop until first config change
  122. mut: sync.NewMutex(),
  123. }
  124. return w
  125. }
  126. // Load loads an existing file on disk and returns a new configuration
  127. // wrapper.
  128. // The returned Wrapper is a suture.Service, thus needs to be started (added to
  129. // a supervisor).
  130. func Load(path string, myID protocol.DeviceID, evLogger events.Logger) (Wrapper, int, error) {
  131. fd, err := os.Open(path)
  132. if err != nil {
  133. return nil, 0, err
  134. }
  135. defer fd.Close()
  136. cfg, originalVersion, err := ReadXML(fd, myID)
  137. if err != nil {
  138. return nil, 0, err
  139. }
  140. return Wrap(path, cfg, myID, evLogger), originalVersion, nil
  141. }
  142. func (w *wrapper) ConfigPath() string {
  143. return w.path
  144. }
  145. func (w *wrapper) MyID() protocol.DeviceID {
  146. return w.myID
  147. }
  148. // Subscribe registers the given handler to be called on any future
  149. // configuration changes. It returns the config that is in effect while
  150. // subscribing, that can be used for initial setup.
  151. func (w *wrapper) Subscribe(c Committer) Configuration {
  152. w.mut.Lock()
  153. defer w.mut.Unlock()
  154. w.subs = append(w.subs, c)
  155. return w.cfg.Copy()
  156. }
  157. // Unsubscribe de-registers the given handler from any future calls to
  158. // configuration changes and only returns after a potential ongoing config
  159. // change is done.
  160. func (w *wrapper) Unsubscribe(c Committer) {
  161. w.mut.Lock()
  162. for i := range w.subs {
  163. if w.subs[i] == c {
  164. copy(w.subs[i:], w.subs[i+1:])
  165. w.subs[len(w.subs)-1] = nil
  166. w.subs = w.subs[:len(w.subs)-1]
  167. break
  168. }
  169. }
  170. waiter := w.waiter
  171. w.mut.Unlock()
  172. // Waiting mustn't be done under lock, as the goroutines in notifyListener
  173. // may dead-lock when trying to access lock on config read operations.
  174. waiter.Wait()
  175. }
  176. // RawCopy returns a copy of the currently wrapped Configuration object.
  177. func (w *wrapper) RawCopy() Configuration {
  178. w.mut.Lock()
  179. defer w.mut.Unlock()
  180. return w.cfg.Copy()
  181. }
  182. func (w *wrapper) Modify(fn ModifyFunction) (Waiter, error) {
  183. return w.modifyQueued(fn)
  184. }
  185. func (w *wrapper) modifyQueued(modifyFunc ModifyFunction) (Waiter, error) {
  186. e := modifyEntry{
  187. modifyFunc: modifyFunc,
  188. res: make(chan modifyResult),
  189. }
  190. select {
  191. case w.queue <- e:
  192. default:
  193. return noopWaiter{}, errTooManyModifications
  194. }
  195. res := <-e.res
  196. return res.w, res.err
  197. }
  198. func (w *wrapper) Serve(ctx context.Context) error {
  199. defer w.serveSave()
  200. var e modifyEntry
  201. saveTimer := time.NewTimer(0)
  202. <-saveTimer.C
  203. saveTimerRunning := false
  204. for {
  205. select {
  206. case e = <-w.queue:
  207. case <-saveTimer.C:
  208. w.serveSave()
  209. saveTimerRunning = false
  210. continue
  211. case <-ctx.Done():
  212. return ctx.Err()
  213. }
  214. var waiter Waiter = noopWaiter{}
  215. var err error
  216. // Let the caller modify the config.
  217. to := w.RawCopy()
  218. e.modifyFunc(&to)
  219. // Check if the config was actually changed at all.
  220. w.mut.Lock()
  221. if !reflect.DeepEqual(w.cfg, to) {
  222. waiter, err = w.replaceLocked(to)
  223. if !saveTimerRunning {
  224. saveTimer.Reset(minSaveInterval)
  225. saveTimerRunning = true
  226. }
  227. }
  228. w.mut.Unlock()
  229. e.res <- modifyResult{
  230. w: waiter,
  231. err: err,
  232. }
  233. // Wait for all subscriber to handle the config change before continuing
  234. // to process the next change.
  235. done := make(chan struct{})
  236. go func() {
  237. waiter.Wait()
  238. close(done)
  239. }()
  240. select {
  241. case <-done:
  242. case <-ctx.Done():
  243. return ctx.Err()
  244. }
  245. }
  246. }
  247. func (w *wrapper) serveSave() {
  248. if err := w.Save(); err != nil {
  249. l.Warnln("Failed to save config:", err)
  250. }
  251. }
  252. func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
  253. from := w.cfg
  254. if err := to.prepare(w.myID); err != nil {
  255. return noopWaiter{}, err
  256. }
  257. for _, sub := range w.subs {
  258. l.Debugln(sub, "verifying configuration")
  259. if err := sub.VerifyConfiguration(from.Copy(), to.Copy()); err != nil {
  260. l.Debugln(sub, "rejected config:", err)
  261. return noopWaiter{}, err
  262. }
  263. }
  264. w.cfg = to
  265. w.waiter = w.notifyListeners(from.Copy(), to.Copy())
  266. return w.waiter, nil
  267. }
  268. func (w *wrapper) notifyListeners(from, to Configuration) Waiter {
  269. wg := sync.NewWaitGroup()
  270. wg.Add(len(w.subs))
  271. for _, sub := range w.subs {
  272. go func(commiter Committer) {
  273. w.notifyListener(commiter, from, to)
  274. wg.Done()
  275. }(sub)
  276. }
  277. return wg
  278. }
  279. func (w *wrapper) notifyListener(sub Committer, from, to Configuration) {
  280. l.Debugln(sub, "committing configuration")
  281. if !sub.CommitConfiguration(from, to) {
  282. l.Debugln(sub, "requires restart")
  283. w.setRequiresRestart()
  284. }
  285. }
  286. // Devices returns a map of devices.
  287. func (w *wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
  288. w.mut.Lock()
  289. defer w.mut.Unlock()
  290. deviceMap := make(map[protocol.DeviceID]DeviceConfiguration, len(w.cfg.Devices))
  291. for _, dev := range w.cfg.Devices {
  292. deviceMap[dev.DeviceID] = dev.Copy()
  293. }
  294. return deviceMap
  295. }
  296. // DeviceList returns a slice of devices.
  297. func (w *wrapper) DeviceList() []DeviceConfiguration {
  298. w.mut.Lock()
  299. defer w.mut.Unlock()
  300. return w.cfg.Copy().Devices
  301. }
  302. // RemoveDevice removes the device from the configuration
  303. func (w *wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
  304. return w.modifyQueued(func(cfg *Configuration) {
  305. if _, i, ok := cfg.Device(id); ok {
  306. cfg.Devices = append(cfg.Devices[:i], cfg.Devices[i+1:]...)
  307. }
  308. })
  309. }
  310. func (w *wrapper) DefaultDevice() DeviceConfiguration {
  311. w.mut.Lock()
  312. defer w.mut.Unlock()
  313. return w.cfg.Defaults.Device.Copy()
  314. }
  315. // Folders returns a map of folders.
  316. func (w *wrapper) Folders() map[string]FolderConfiguration {
  317. w.mut.Lock()
  318. defer w.mut.Unlock()
  319. folderMap := make(map[string]FolderConfiguration, len(w.cfg.Folders))
  320. for _, fld := range w.cfg.Folders {
  321. folderMap[fld.ID] = fld.Copy()
  322. }
  323. return folderMap
  324. }
  325. // FolderList returns a slice of folders.
  326. func (w *wrapper) FolderList() []FolderConfiguration {
  327. w.mut.Lock()
  328. defer w.mut.Unlock()
  329. return w.cfg.Copy().Folders
  330. }
  331. // RemoveFolder removes the folder from the configuration
  332. func (w *wrapper) RemoveFolder(id string) (Waiter, error) {
  333. return w.modifyQueued(func(cfg *Configuration) {
  334. if _, i, ok := cfg.Folder(id); ok {
  335. cfg.Folders = append(cfg.Folders[:i], cfg.Folders[i+1:]...)
  336. }
  337. })
  338. }
  339. // FolderPasswords returns the folder passwords set for this device, for
  340. // folders that have an encryption password set.
  341. func (w *wrapper) FolderPasswords(device protocol.DeviceID) map[string]string {
  342. w.mut.Lock()
  343. defer w.mut.Unlock()
  344. return w.cfg.FolderPasswords(device)
  345. }
  346. func (w *wrapper) DefaultFolder() FolderConfiguration {
  347. w.mut.Lock()
  348. defer w.mut.Unlock()
  349. return w.cfg.Defaults.Folder.Copy()
  350. }
  351. // Options returns the current options configuration object.
  352. func (w *wrapper) Options() OptionsConfiguration {
  353. w.mut.Lock()
  354. defer w.mut.Unlock()
  355. return w.cfg.Options.Copy()
  356. }
  357. func (w *wrapper) LDAP() LDAPConfiguration {
  358. w.mut.Lock()
  359. defer w.mut.Unlock()
  360. return w.cfg.LDAP.Copy()
  361. }
  362. // GUI returns the current GUI configuration object.
  363. func (w *wrapper) GUI() GUIConfiguration {
  364. w.mut.Lock()
  365. defer w.mut.Unlock()
  366. return w.cfg.GUI.Copy()
  367. }
  368. // IgnoredDevice returns whether or not connection attempts from the given
  369. // device should be silently ignored.
  370. func (w *wrapper) IgnoredDevice(id protocol.DeviceID) bool {
  371. w.mut.Lock()
  372. defer w.mut.Unlock()
  373. for _, device := range w.cfg.IgnoredDevices {
  374. if device.ID == id {
  375. return true
  376. }
  377. }
  378. return false
  379. }
  380. // IgnoredDevices returns a slice of ignored devices.
  381. func (w *wrapper) IgnoredDevices() []ObservedDevice {
  382. w.mut.Lock()
  383. defer w.mut.Unlock()
  384. res := make([]ObservedDevice, len(w.cfg.IgnoredDevices))
  385. copy(res, w.cfg.IgnoredDevices)
  386. return res
  387. }
  388. // IgnoredFolder returns whether or not share attempts for the given
  389. // folder should be silently ignored.
  390. func (w *wrapper) IgnoredFolder(device protocol.DeviceID, folder string) bool {
  391. dev, ok := w.Device(device)
  392. if !ok {
  393. return false
  394. }
  395. return dev.IgnoredFolder(folder)
  396. }
  397. // Device returns the configuration for the given device and an "ok" bool.
  398. func (w *wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
  399. w.mut.Lock()
  400. defer w.mut.Unlock()
  401. device, _, ok := w.cfg.Device(id)
  402. if !ok {
  403. return DeviceConfiguration{}, false
  404. }
  405. return device.Copy(), ok
  406. }
  407. // Folder returns the configuration for the given folder and an "ok" bool.
  408. func (w *wrapper) Folder(id string) (FolderConfiguration, bool) {
  409. w.mut.Lock()
  410. defer w.mut.Unlock()
  411. fcfg, _, ok := w.cfg.Folder(id)
  412. if !ok {
  413. return FolderConfiguration{}, false
  414. }
  415. return fcfg.Copy(), ok
  416. }
  417. // Save writes the configuration to disk, and generates a ConfigSaved event.
  418. func (w *wrapper) Save() error {
  419. w.mut.Lock()
  420. defer w.mut.Unlock()
  421. fd, err := osutil.CreateAtomic(w.path)
  422. if err != nil {
  423. l.Debugln("CreateAtomic:", err)
  424. return err
  425. }
  426. if err := w.cfg.WriteXML(fd); err != nil {
  427. l.Debugln("WriteXML:", err)
  428. fd.Close()
  429. return err
  430. }
  431. if err := fd.Close(); err != nil {
  432. l.Debugln("Close:", err)
  433. return err
  434. }
  435. w.evLogger.Log(events.ConfigSaved, w.cfg)
  436. return nil
  437. }
  438. func (w *wrapper) RequiresRestart() bool {
  439. return atomic.LoadUint32(&w.requiresRestart) != 0
  440. }
  441. func (w *wrapper) setRequiresRestart() {
  442. atomic.StoreUint32(&w.requiresRestart, 1)
  443. }
  444. type modifyEntry struct {
  445. modifyFunc ModifyFunction
  446. res chan modifyResult
  447. }
  448. type modifyResult struct {
  449. w Waiter
  450. err error
  451. }