upgrade_common.go 4.9 KB

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