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 go tool counterfeiter -o mocks/mocked_wrapper.go --fake-name Wrapper . Wrapper
  7. package config
  8. import (
  9. "context"
  10. "errors"
  11. "log/slog"
  12. "os"
  13. "reflect"
  14. "sync"
  15. "sync/atomic"
  16. "time"
  17. "github.com/thejerf/suture/v4"
  18. "github.com/syncthing/syncthing/internal/slogutil"
  19. "github.com/syncthing/syncthing/lib/events"
  20. "github.com/syncthing/syncthing/lib/osutil"
  21. "github.com/syncthing/syncthing/lib/protocol"
  22. "github.com/syncthing/syncthing/lib/sliceutil"
  23. )
  24. const (
  25. maxModifications = 1000
  26. minSaveInterval = 5 * time.Second
  27. )
  28. var errTooManyModifications = errors.New("too many concurrent config modifications")
  29. // The Committer and Verifier interfaces are implemented by objects
  30. // that need to know about or have a say in configuration changes.
  31. //
  32. // When the configuration is about to be changed, VerifyConfiguration() is
  33. // called for each subscribing object that implements it, with copies of the
  34. // old and new configuration. A nil error is returned if the new configuration
  35. // is acceptable (i.e., does not contain any errors that would prevent it from
  36. // being a valid config). Otherwise an error describing the problem is returned.
  37. //
  38. // If any subscriber returns an error from VerifyConfiguration(), the
  39. // configuration change is not committed and an error is returned to whoever
  40. // tried to commit the broken config.
  41. //
  42. // If all verification calls returns nil, CommitConfiguration() is called for
  43. // each subscribing object. The callee returns true if the new configuration
  44. // has been successfully applied, otherwise false. Any Commit() call returning
  45. // false will result in a "restart needed" response to the API/user. Note that
  46. // the new configuration will still have been applied by those who were
  47. // capable of doing so.
  48. //
  49. // A Committer must take care not to hold any locks while changing the
  50. // configuration (e.g. calling Wrapper.SetFolder), that are also acquired in any
  51. // methods of the Committer interface.
  52. type Committer interface {
  53. CommitConfiguration(from, to Configuration) (handled bool)
  54. String() string
  55. }
  56. // A Verifier can determine if a new configuration is acceptable.
  57. // See the description for Committer, above.
  58. type Verifier interface {
  59. VerifyConfiguration(from, to Configuration) error
  60. }
  61. // Waiter allows to wait for the given config operation to complete.
  62. type Waiter interface {
  63. Wait()
  64. }
  65. type noopWaiter struct{}
  66. func (noopWaiter) Wait() {}
  67. // ModifyFunction gets a pointer to a copy of the currently active configuration
  68. // for modification.
  69. type ModifyFunction func(*Configuration)
  70. // Wrapper handles a Configuration, i.e. it provides methods to access, change
  71. // and save the config, and notifies registered subscribers (Committer) of
  72. // changes.
  73. //
  74. // Modify allows changing the currently active configuration through the given
  75. // ModifyFunction. It can be called concurrently: All calls will be queued and
  76. // called in order.
  77. type Wrapper interface {
  78. ConfigPath() string
  79. MyID() protocol.DeviceID
  80. RawCopy() Configuration
  81. RequiresRestart() bool
  82. Save() error
  83. Modify(ModifyFunction) (Waiter, error)
  84. RemoveFolder(id string) (Waiter, error)
  85. RemoveDevice(id protocol.DeviceID) (Waiter, error)
  86. GUI() GUIConfiguration
  87. LDAP() LDAPConfiguration
  88. Options() OptionsConfiguration
  89. DefaultIgnores() Ignores
  90. Folder(id string) (FolderConfiguration, bool)
  91. Folders() map[string]FolderConfiguration
  92. FolderList() []FolderConfiguration
  93. FolderPasswords(device protocol.DeviceID) map[string]string
  94. DefaultFolder() FolderConfiguration
  95. Device(id protocol.DeviceID) (DeviceConfiguration, bool)
  96. Devices() map[protocol.DeviceID]DeviceConfiguration
  97. DeviceList() []DeviceConfiguration
  98. DefaultDevice() DeviceConfiguration
  99. IgnoredDevices() []ObservedDevice
  100. IgnoredDevice(id protocol.DeviceID) bool
  101. IgnoredFolder(device protocol.DeviceID, folder string) bool
  102. Subscribe(c Committer) Configuration
  103. Unsubscribe(c Committer)
  104. suture.Service
  105. }
  106. type wrapper struct {
  107. cfg Configuration
  108. path string
  109. evLogger events.Logger
  110. myID protocol.DeviceID
  111. queue chan modifyEntry
  112. waiter Waiter // Latest ongoing config change
  113. subs []Committer
  114. mut sync.Mutex
  115. requiresRestart atomic.Bool
  116. }
  117. // Wrap wraps an existing Configuration structure and ties it to a file on
  118. // disk.
  119. // The returned Wrapper is a suture.Service, thus needs to be started (added to
  120. // a supervisor).
  121. func Wrap(path string, cfg Configuration, myID protocol.DeviceID, evLogger events.Logger) Wrapper {
  122. w := &wrapper{
  123. cfg: cfg,
  124. path: path,
  125. evLogger: evLogger,
  126. myID: myID,
  127. queue: make(chan modifyEntry, maxModifications),
  128. waiter: noopWaiter{}, // Noop until first config change
  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. slog.Error("Failed to save config", slogutil.Error(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 := new(sync.WaitGroup)
  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. }