upgrade_common.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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. // Package upgrade downloads and compares releases, and upgrades the running binary.
  7. package upgrade
  8. import (
  9. "errors"
  10. "fmt"
  11. "os"
  12. "path"
  13. "runtime"
  14. "strconv"
  15. "strings"
  16. "github.com/syncthing/syncthing/lib/build"
  17. )
  18. type Release struct {
  19. Tag string `json:"tag_name"`
  20. Prerelease bool `json:"prerelease"`
  21. Assets []Asset `json:"assets"`
  22. // The HTML URL is needed for human readable links in the output created
  23. // by cmd/infra/stupgrades.
  24. HTMLURL string `json:"html_url"`
  25. // The compatibility information is included with each current release.
  26. Compatibility *ReleaseCompatibility `json:"compatibility,omitempty"`
  27. }
  28. type Asset struct {
  29. URL string `json:"url"`
  30. Name string `json:"name"`
  31. // The browser URL is needed for human readable links in the output created
  32. // by cmd/infra/stupgrades.
  33. BrowserURL string `json:"browser_download_url,omitempty"`
  34. }
  35. // ReleaseCompatibility defines the structure of compat.json, which is
  36. // included with each release.
  37. type ReleaseCompatibility struct {
  38. Runtime string `json:"runtime,omitempty"`
  39. Requirements map[string]string `json:"requirements,omitempty"`
  40. }
  41. var (
  42. ErrNoReleaseDownload = errors.New("couldn't find a release to download")
  43. ErrNoVersionToSelect = errors.New("no version to select")
  44. ErrUpgradeUnsupported = errors.New("upgrade unsupported")
  45. ErrUpgradeInProgress = errors.New("upgrade already in progress")
  46. upgradeUnlocked = make(chan bool, 1)
  47. )
  48. func init() {
  49. upgradeUnlocked <- true
  50. }
  51. func To(rel Release) error {
  52. select {
  53. case <-upgradeUnlocked:
  54. path, err := os.Executable()
  55. if err != nil {
  56. upgradeUnlocked <- true
  57. return err
  58. }
  59. err = upgradeTo(path, rel)
  60. // If we've failed to upgrade, unlock so that another attempt could be made
  61. if err != nil {
  62. upgradeUnlocked <- true
  63. }
  64. return err
  65. default:
  66. return ErrUpgradeInProgress
  67. }
  68. }
  69. func ToURL(url string) error {
  70. select {
  71. case <-upgradeUnlocked:
  72. binary, err := os.Executable()
  73. if err != nil {
  74. upgradeUnlocked <- true
  75. return err
  76. }
  77. err = upgradeToURL(path.Base(url), binary, url)
  78. // If we've failed to upgrade, unlock so that another attempt could be made
  79. if err != nil {
  80. upgradeUnlocked <- true
  81. }
  82. return err
  83. default:
  84. return ErrUpgradeInProgress
  85. }
  86. }
  87. type Relation int
  88. const (
  89. MajorOlder Relation = -2 // Older by a major version (x in x.y.z or 0.x.y).
  90. Older Relation = -1 // Older by a minor version (y or z in x.y.z, or y in 0.x.y)
  91. Equal Relation = 0 // Versions are semantically equal
  92. Newer Relation = 1 // Newer by a minor version (y or z in x.y.z, or y in 0.x.y)
  93. MajorNewer Relation = 2 // Newer by a major version (x in x.y.z or 0.x.y).
  94. )
  95. // CompareVersions returns a relation describing how a compares to b.
  96. func CompareVersions(a, b string) Relation {
  97. arel, apre := versionParts(a)
  98. brel, bpre := versionParts(b)
  99. minlen := len(arel)
  100. if l := len(brel); l < minlen {
  101. minlen = l
  102. }
  103. // First compare major-minor-patch versions
  104. for i := 0; i < minlen; i++ {
  105. if arel[i] < brel[i] {
  106. if i == 0 {
  107. // major version difference
  108. if arel[0] == 0 && brel[0] == 1 {
  109. // special case, v0.x is equivalent in majorness to v1.x.
  110. return Older
  111. }
  112. return MajorOlder
  113. }
  114. // minor or patch version difference
  115. return Older
  116. }
  117. if arel[i] > brel[i] {
  118. if i == 0 {
  119. // major version difference
  120. if arel[0] == 1 && brel[0] == 0 {
  121. // special case, v0.x is equivalent in majorness to v1.x.
  122. return Newer
  123. }
  124. return MajorNewer
  125. }
  126. // minor or patch version difference
  127. return Newer
  128. }
  129. }
  130. // Longer version is newer, when the preceding parts are equal
  131. if len(arel) < len(brel) {
  132. return Older
  133. }
  134. if len(arel) > len(brel) {
  135. return Newer
  136. }
  137. // Prerelease versions are older, if the versions are the same
  138. if len(apre) == 0 && len(bpre) > 0 {
  139. return Newer
  140. }
  141. if len(apre) > 0 && len(bpre) == 0 {
  142. return Older
  143. }
  144. minlen = len(apre)
  145. if l := len(bpre); l < minlen {
  146. minlen = l
  147. }
  148. // Compare prerelease strings
  149. for i := 0; i < minlen; i++ {
  150. switch av := apre[i].(type) {
  151. case int:
  152. switch bv := bpre[i].(type) {
  153. case int:
  154. if av < bv {
  155. return Older
  156. }
  157. if av > bv {
  158. return Newer
  159. }
  160. case string:
  161. return Older
  162. }
  163. case string:
  164. switch bv := bpre[i].(type) {
  165. case int:
  166. return Newer
  167. case string:
  168. if av < bv {
  169. return Older
  170. }
  171. if av > bv {
  172. return Newer
  173. }
  174. }
  175. }
  176. }
  177. // If all else is equal, longer prerelease string is newer
  178. if len(apre) < len(bpre) {
  179. return Older
  180. }
  181. if len(apre) > len(bpre) {
  182. return Newer
  183. }
  184. // Looks like they're actually the same
  185. return Equal
  186. }
  187. // Split a version into parts.
  188. // "1.2.3-beta.2" -> []int{1, 2, 3}, []interface{}{"beta", 2}
  189. func versionParts(v string) ([]int, []interface{}) {
  190. if strings.HasPrefix(v, "v") || strings.HasPrefix(v, "V") {
  191. // Strip initial 'v' or 'V' prefix if present.
  192. v = v[1:]
  193. }
  194. parts := strings.SplitN(v, "+", 2)
  195. parts = strings.SplitN(parts[0], "-", 2)
  196. fields := strings.Split(parts[0], ".")
  197. release := make([]int, len(fields))
  198. for i, s := range fields {
  199. v, _ := strconv.Atoi(s)
  200. release[i] = v
  201. }
  202. var prerelease []interface{}
  203. if len(parts) > 1 {
  204. fields = strings.Split(parts[1], ".")
  205. prerelease = make([]interface{}, len(fields))
  206. for i, s := range fields {
  207. v, err := strconv.Atoi(s)
  208. if err == nil {
  209. prerelease[i] = v
  210. } else {
  211. prerelease[i] = s
  212. }
  213. }
  214. }
  215. return release, prerelease
  216. }
  217. func releaseNames(tag string) []string {
  218. // We must ensure that the release asset matches the expected naming
  219. // standard, containing both the architecture/OS and the tag name we
  220. // expect. This protects against malformed release data potentially
  221. // tricking us into doing a downgrade.
  222. if build.IsDarwin {
  223. return []string{
  224. fmt.Sprintf("syncthing-macos-%s-%s.", runtime.GOARCH, tag),
  225. fmt.Sprintf("syncthing-macosx-%s-%s.", runtime.GOARCH, tag),
  226. }
  227. }
  228. return []string{
  229. fmt.Sprintf("syncthing-%s-%s-%s.", runtime.GOOS, runtime.GOARCH, tag),
  230. }
  231. }