upgrade_common.go 5.7 KB

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