upgrade_common.go 4.8 KB

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