wrapper.go 14 KB

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