PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/rc/rc.go

https://gitlab.com/shinvdu/syncthing
Go | 625 lines | 500 code | 85 blank | 40 comment | 109 complexity | 2193df9cb95832666afe82e99565c904 MD5 | raw file
  1. // Copyright (C) 2015 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 http://mozilla.org/MPL/2.0/.
  6. // Package rc provides remote control of a Syncthing process via the REST API.
  7. package rc
  8. import (
  9. "bufio"
  10. "bytes"
  11. "encoding/json"
  12. "errors"
  13. "fmt"
  14. "io"
  15. "io/ioutil"
  16. "log"
  17. "net/http"
  18. "net/url"
  19. "os"
  20. "os/exec"
  21. "path/filepath"
  22. "strconv"
  23. stdsync "sync"
  24. "time"
  25. "github.com/syncthing/syncthing/lib/config"
  26. "github.com/syncthing/syncthing/lib/dialer"
  27. "github.com/syncthing/syncthing/lib/protocol"
  28. "github.com/syncthing/syncthing/lib/sync"
  29. )
  30. // APIKey is set via the STGUIAPIKEY variable when we launch the binary, to
  31. // ensure that we have API access regardless of authentication settings.
  32. const APIKey = "592A47BC-A7DF-4C2F-89E0-A80B3E5094EE"
  33. type Process struct {
  34. // Set at initialization
  35. addr string
  36. // Set by eventLoop()
  37. eventMut sync.Mutex
  38. id protocol.DeviceID
  39. folders []string
  40. startComplete bool
  41. startCompleteCond *stdsync.Cond
  42. stop bool
  43. localVersion map[string]map[string]int64 // Folder ID => Device ID => LocalVersion
  44. done map[string]bool // Folder ID => 100%
  45. cmd *exec.Cmd
  46. logfd *os.File
  47. }
  48. // NewProcess returns a new Process talking to Syncthing at the specified address.
  49. // Example: NewProcess("127.0.0.1:8082")
  50. func NewProcess(addr string) *Process {
  51. p := &Process{
  52. addr: addr,
  53. localVersion: make(map[string]map[string]int64),
  54. done: make(map[string]bool),
  55. eventMut: sync.NewMutex(),
  56. }
  57. p.startCompleteCond = stdsync.NewCond(p.eventMut)
  58. return p
  59. }
  60. func (p *Process) ID() protocol.DeviceID {
  61. return p.id
  62. }
  63. // LogTo creates the specified log file and ensures that stdout and stderr
  64. // from the Start()ed process is redirected there. Must be called before
  65. // Start().
  66. func (p *Process) LogTo(filename string) error {
  67. if p.cmd != nil {
  68. panic("logfd cannot be set with an existing cmd")
  69. }
  70. if p.logfd != nil {
  71. p.logfd.Close()
  72. }
  73. fd, err := os.Create(filename)
  74. if err != nil {
  75. return err
  76. }
  77. p.logfd = fd
  78. return nil
  79. }
  80. // Start runs the specified Syncthing binary with the given arguments.
  81. // Syncthing should be configured to provide an API on the address given to
  82. // NewProcess. Event processing is started.
  83. func (p *Process) Start(bin string, args ...string) error {
  84. cmd := exec.Command(bin, args...)
  85. if p.logfd != nil {
  86. cmd.Stdout = p.logfd
  87. cmd.Stderr = p.logfd
  88. }
  89. cmd.Env = append(os.Environ(), "STNORESTART=1", "STGUIAPIKEY="+APIKey)
  90. err := cmd.Start()
  91. if err != nil {
  92. return err
  93. }
  94. p.cmd = cmd
  95. go p.eventLoop()
  96. return nil
  97. }
  98. // AwaitStartup waits for the Syncthing process to start and perform initial
  99. // scans of all folders.
  100. func (p *Process) AwaitStartup() {
  101. p.eventMut.Lock()
  102. for !p.startComplete {
  103. p.startCompleteCond.Wait()
  104. }
  105. p.eventMut.Unlock()
  106. return
  107. }
  108. // Stop stops the running Syncthing process. If the process was logging to a
  109. // local file (set by LogTo), the log file will be opened and checked for
  110. // panics and data races. The presence of either will be signalled in the form
  111. // of a returned error.
  112. func (p *Process) Stop() (*os.ProcessState, error) {
  113. p.eventMut.Lock()
  114. if p.stop {
  115. p.eventMut.Unlock()
  116. return p.cmd.ProcessState, nil
  117. }
  118. p.stop = true
  119. p.eventMut.Unlock()
  120. if _, err := p.Post("/rest/system/shutdown", nil); err != nil && err != io.ErrUnexpectedEOF {
  121. // Unexpected EOF is somewhat expected here, as we may exit before
  122. // returning something sensible.
  123. return nil, err
  124. }
  125. p.cmd.Wait()
  126. var err error
  127. if p.logfd != nil {
  128. err = p.checkForProblems(p.logfd)
  129. }
  130. return p.cmd.ProcessState, err
  131. }
  132. // Get performs an HTTP GET and returns the bytes and/or an error. Any non-200
  133. // return code is returned as an error.
  134. func (p *Process) Get(path string) ([]byte, error) {
  135. client := &http.Client{
  136. Timeout: 30 * time.Second,
  137. Transport: &http.Transport{
  138. Dial: dialer.Dial,
  139. Proxy: http.ProxyFromEnvironment,
  140. DisableKeepAlives: true,
  141. },
  142. }
  143. url := fmt.Sprintf("http://%s%s", p.addr, path)
  144. req, err := http.NewRequest("GET", url, nil)
  145. if err != nil {
  146. return nil, err
  147. }
  148. req.Header.Add("X-API-Key", APIKey)
  149. resp, err := client.Do(req)
  150. if err != nil {
  151. return nil, err
  152. }
  153. return p.readResponse(resp)
  154. }
  155. // Post performs an HTTP POST and returns the bytes and/or an error. Any
  156. // non-200 return code is returned as an error.
  157. func (p *Process) Post(path string, data io.Reader) ([]byte, error) {
  158. client := &http.Client{
  159. Timeout: 600 * time.Second,
  160. Transport: &http.Transport{
  161. DisableKeepAlives: true,
  162. },
  163. }
  164. url := fmt.Sprintf("http://%s%s", p.addr, path)
  165. req, err := http.NewRequest("POST", url, data)
  166. if err != nil {
  167. return nil, err
  168. }
  169. req.Header.Add("X-API-Key", APIKey)
  170. req.Header.Add("Content-Type", "application/json")
  171. resp, err := client.Do(req)
  172. if err != nil {
  173. return nil, err
  174. }
  175. return p.readResponse(resp)
  176. }
  177. type Event struct {
  178. ID int
  179. Time time.Time
  180. Type string
  181. Data interface{}
  182. }
  183. func (p *Process) Events(since int) ([]Event, error) {
  184. bs, err := p.Get(fmt.Sprintf("/rest/events?since=%d", since))
  185. if err != nil {
  186. return nil, err
  187. }
  188. var evs []Event
  189. dec := json.NewDecoder(bytes.NewReader(bs))
  190. dec.UseNumber()
  191. err = dec.Decode(&evs)
  192. if err != nil {
  193. return nil, fmt.Errorf("Events: %s in %q", err, bs)
  194. }
  195. return evs, err
  196. }
  197. func (p *Process) Rescan(folder string) error {
  198. _, err := p.Post("/rest/db/scan?folder="+url.QueryEscape(folder), nil)
  199. return err
  200. }
  201. func (p *Process) RescanDelay(folder string, delaySeconds int) error {
  202. _, err := p.Post(fmt.Sprintf("/rest/db/scan?folder=%s&next=%d", url.QueryEscape(folder), delaySeconds), nil)
  203. return err
  204. }
  205. func (p *Process) RescanSub(folder string, sub string, delaySeconds int) error {
  206. return p.RescanSubs(folder, []string{sub}, delaySeconds)
  207. }
  208. func (p *Process) RescanSubs(folder string, subs []string, delaySeconds int) error {
  209. data := url.Values{}
  210. data.Set("folder", folder)
  211. for _, sub := range subs {
  212. data.Add("sub", sub)
  213. }
  214. data.Set("next", strconv.Itoa(delaySeconds))
  215. _, err := p.Post("/rest/db/scan?"+data.Encode(), nil)
  216. return err
  217. }
  218. func (p *Process) ConfigInSync() (bool, error) {
  219. bs, err := p.Get("/rest/system/config/insync")
  220. if err != nil {
  221. return false, err
  222. }
  223. return bytes.Contains(bs, []byte("true")), nil
  224. }
  225. func (p *Process) GetConfig() (config.Configuration, error) {
  226. var cfg config.Configuration
  227. bs, err := p.Get("/rest/system/config")
  228. if err != nil {
  229. return cfg, err
  230. }
  231. err = json.Unmarshal(bs, &cfg)
  232. return cfg, err
  233. }
  234. func (p *Process) PostConfig(cfg config.Configuration) error {
  235. buf := new(bytes.Buffer)
  236. if err := json.NewEncoder(buf).Encode(cfg); err != nil {
  237. return err
  238. }
  239. _, err := p.Post("/rest/system/config", buf)
  240. return err
  241. }
  242. func (p *Process) PauseDevice(dev protocol.DeviceID) error {
  243. _, err := p.Post("/rest/system/pause?device="+dev.String(), nil)
  244. return err
  245. }
  246. func (p *Process) ResumeDevice(dev protocol.DeviceID) error {
  247. _, err := p.Post("/rest/system/resume?device="+dev.String(), nil)
  248. return err
  249. }
  250. func InSync(folder string, ps ...*Process) bool {
  251. for _, p := range ps {
  252. p.eventMut.Lock()
  253. }
  254. defer func() {
  255. for _, p := range ps {
  256. p.eventMut.Unlock()
  257. }
  258. }()
  259. for i := range ps {
  260. // If our latest FolderSummary didn't report 100%, then we are not done.
  261. if !ps[i].done[folder] {
  262. return false
  263. }
  264. // Check LocalVersion for each device. The local version seen by remote
  265. // devices should be the same as what it has locally, or the index
  266. // hasn't been sent yet.
  267. sourceID := ps[i].id.String()
  268. sourceVersion := ps[i].localVersion[folder][sourceID]
  269. for j := range ps {
  270. if i != j {
  271. remoteVersion := ps[j].localVersion[folder][sourceID]
  272. if remoteVersion != sourceVersion {
  273. return false
  274. }
  275. }
  276. }
  277. }
  278. return true
  279. }
  280. func AwaitSync(folder string, ps ...*Process) {
  281. for {
  282. time.Sleep(250 * time.Millisecond)
  283. if InSync(folder, ps...) {
  284. return
  285. }
  286. }
  287. }
  288. type Model struct {
  289. GlobalBytes int
  290. GlobalDeleted int
  291. GlobalFiles int
  292. InSyncBytes int
  293. InSyncFiles int
  294. Invalid string
  295. LocalBytes int
  296. LocalDeleted int
  297. LocalFiles int
  298. NeedBytes int
  299. NeedFiles int
  300. State string
  301. StateChanged time.Time
  302. Version int
  303. }
  304. func (p *Process) Model(folder string) (Model, error) {
  305. bs, err := p.Get("/rest/db/status?folder=" + url.QueryEscape(folder))
  306. if err != nil {
  307. return Model{}, err
  308. }
  309. var res Model
  310. if err := json.Unmarshal(bs, &res); err != nil {
  311. return Model{}, err
  312. }
  313. l.Debugf("%+v", res)
  314. return res, nil
  315. }
  316. func (p *Process) readResponse(resp *http.Response) ([]byte, error) {
  317. bs, err := ioutil.ReadAll(resp.Body)
  318. resp.Body.Close()
  319. if err != nil {
  320. return bs, err
  321. }
  322. if resp.StatusCode != 200 {
  323. return bs, fmt.Errorf("%s", resp.Status)
  324. }
  325. return bs, nil
  326. }
  327. func (p *Process) checkForProblems(logfd *os.File) error {
  328. fd, err := os.Open(logfd.Name())
  329. if err != nil {
  330. return err
  331. }
  332. defer fd.Close()
  333. raceConditionStart := []byte("WARNING: DATA RACE")
  334. raceConditionSep := []byte("==================")
  335. panicConditionStart := []byte("panic:")
  336. panicConditionSep := []byte(p.id.String()[:5])
  337. sc := bufio.NewScanner(fd)
  338. race := false
  339. _panic := false
  340. for sc.Scan() {
  341. line := sc.Bytes()
  342. if race || _panic {
  343. if bytes.Contains(line, panicConditionSep) {
  344. _panic = false
  345. continue
  346. }
  347. fmt.Printf("%s\n", line)
  348. if bytes.Contains(line, raceConditionSep) {
  349. race = false
  350. }
  351. } else if bytes.Contains(line, raceConditionStart) {
  352. fmt.Printf("%s\n", raceConditionSep)
  353. fmt.Printf("%s\n", raceConditionStart)
  354. race = true
  355. if err == nil {
  356. err = errors.New("Race condition detected")
  357. }
  358. } else if bytes.Contains(line, panicConditionStart) {
  359. _panic = true
  360. if err == nil {
  361. err = errors.New("Panic detected")
  362. }
  363. }
  364. }
  365. return err
  366. }
  367. func (p *Process) eventLoop() {
  368. since := 0
  369. notScanned := make(map[string]struct{})
  370. start := time.Now()
  371. for {
  372. p.eventMut.Lock()
  373. if p.stop {
  374. p.eventMut.Unlock()
  375. return
  376. }
  377. p.eventMut.Unlock()
  378. time.Sleep(250 * time.Millisecond)
  379. events, err := p.Events(since)
  380. if err != nil {
  381. if time.Since(start) < 5*time.Second {
  382. // The API has probably not started yet, lets give it some time.
  383. continue
  384. }
  385. // If we're stopping, no need to print the error.
  386. p.eventMut.Lock()
  387. if p.stop {
  388. p.eventMut.Unlock()
  389. return
  390. }
  391. p.eventMut.Unlock()
  392. log.Println("eventLoop: events:", err)
  393. continue
  394. }
  395. since = events[len(events)-1].ID
  396. for _, ev := range events {
  397. switch ev.Type {
  398. case "Starting":
  399. // The Starting event tells us where the configuration is. Load
  400. // it and populate our list of folders.
  401. data := ev.Data.(map[string]interface{})
  402. id, err := protocol.DeviceIDFromString(data["myID"].(string))
  403. if err != nil {
  404. log.Println("eventLoop: DeviceIdFromString:", err)
  405. continue
  406. }
  407. p.id = id
  408. home := data["home"].(string)
  409. w, err := config.Load(filepath.Join(home, "config.xml"), protocol.LocalDeviceID)
  410. if err != nil {
  411. log.Println("eventLoop: Starting:", err)
  412. continue
  413. }
  414. for id := range w.Folders() {
  415. p.eventMut.Lock()
  416. p.folders = append(p.folders, id)
  417. p.eventMut.Unlock()
  418. notScanned[id] = struct{}{}
  419. }
  420. case "StateChanged":
  421. // When a folder changes to idle, we tick it off by removing
  422. // it from p.notScanned.
  423. if !p.startComplete {
  424. data := ev.Data.(map[string]interface{})
  425. to := data["to"].(string)
  426. if to == "idle" {
  427. folder := data["folder"].(string)
  428. delete(notScanned, folder)
  429. if len(notScanned) == 0 {
  430. p.eventMut.Lock()
  431. p.startComplete = true
  432. p.startCompleteCond.Broadcast()
  433. p.eventMut.Unlock()
  434. }
  435. }
  436. }
  437. case "LocalIndexUpdated":
  438. data := ev.Data.(map[string]interface{})
  439. folder := data["folder"].(string)
  440. version, _ := data["version"].(json.Number).Int64()
  441. p.eventMut.Lock()
  442. m := p.localVersion[folder]
  443. if m == nil {
  444. m = make(map[string]int64)
  445. }
  446. m[p.id.String()] = version
  447. p.localVersion[folder] = m
  448. p.done[folder] = false
  449. l.Debugf("LocalIndexUpdated %v %v done=false\n\t%+v", p.id, folder, m)
  450. p.eventMut.Unlock()
  451. case "RemoteIndexUpdated":
  452. data := ev.Data.(map[string]interface{})
  453. device := data["device"].(string)
  454. folder := data["folder"].(string)
  455. version, _ := data["version"].(json.Number).Int64()
  456. p.eventMut.Lock()
  457. m := p.localVersion[folder]
  458. if m == nil {
  459. m = make(map[string]int64)
  460. }
  461. m[device] = version
  462. p.localVersion[folder] = m
  463. p.done[folder] = false
  464. l.Debugf("RemoteIndexUpdated %v %v done=false\n\t%+v", p.id, folder, m)
  465. p.eventMut.Unlock()
  466. case "FolderSummary":
  467. data := ev.Data.(map[string]interface{})
  468. folder := data["folder"].(string)
  469. summary := data["summary"].(map[string]interface{})
  470. need, _ := summary["needBytes"].(json.Number).Int64()
  471. done := need == 0
  472. p.eventMut.Lock()
  473. p.done[folder] = done
  474. l.Debugf("Foldersummary %v %v\n\t%+v", p.id, folder, p.done)
  475. p.eventMut.Unlock()
  476. }
  477. }
  478. }
  479. }
  480. type ConnectionStats struct {
  481. Address string
  482. Type string
  483. Connected bool
  484. Paused bool
  485. ClientVersion string
  486. InBytesTotal int64
  487. OutBytesTotal int64
  488. }
  489. func (p *Process) Connections() (map[string]ConnectionStats, error) {
  490. bs, err := p.Get("/rest/system/connections")
  491. if err != nil {
  492. return nil, err
  493. }
  494. var res map[string]ConnectionStats
  495. if err := json.Unmarshal(bs, &res); err != nil {
  496. return nil, err
  497. }
  498. return res, nil
  499. }
  500. type SystemStatus struct {
  501. Alloc int64
  502. CPUPercent float64
  503. Goroutines int
  504. MyID protocol.DeviceID
  505. PathSeparator string
  506. StartTime time.Time
  507. Sys int64
  508. Themes []string
  509. Tilde string
  510. Uptime int
  511. }
  512. func (p *Process) SystemStatus() (SystemStatus, error) {
  513. bs, err := p.Get("/rest/system/status")
  514. if err != nil {
  515. return SystemStatus{}, err
  516. }
  517. var res SystemStatus
  518. if err := json.Unmarshal(bs, &res); err != nil {
  519. return SystemStatus{}, err
  520. }
  521. return res, nil
  522. }
  523. type SystemVersion struct {
  524. Arch string
  525. Codename string
  526. LongVersion string
  527. OS string
  528. Version string
  529. }
  530. func (p *Process) SystemVersion() (SystemVersion, error) {
  531. bs, err := p.Get("/rest/system/version")
  532. if err != nil {
  533. return SystemVersion{}, err
  534. }
  535. var res SystemVersion
  536. if err := json.Unmarshal(bs, &res); err != nil {
  537. return SystemVersion{}, err
  538. }
  539. return res, nil
  540. }