upgrade_common.go 5.5 KB

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