wrapper.go 14 KB

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