123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- // Copyright (C) 2014 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- // Package upgrade downloads and compares releases, and upgrades the running binary.
- package upgrade
- import (
- "errors"
- "fmt"
- "os"
- "path"
- "runtime"
- "strconv"
- "strings"
- "github.com/syncthing/syncthing/lib/build"
- )
- type Release struct {
- Tag string `json:"tag_name"`
- Prerelease bool `json:"prerelease"`
- Assets []Asset `json:"assets"`
- // The HTML URL is needed for human readable links in the output created
- // by cmd/stupgrades.
- HTMLURL string `json:"html_url"`
- }
- type Asset struct {
- URL string `json:"url"`
- Name string `json:"name"`
- // The browser URL is needed for human readable links in the output created
- // by cmd/stupgrades.
- BrowserURL string `json:"browser_download_url,omitempty"`
- }
- // ReleaseCompatibility defines the structure of compat.json, which is
- // included with each elease.
- type ReleaseCompatibility struct {
- Runtime string `json:"runtime,omitempty"`
- Requirements map[string]string `json:"requirements,omitempty"`
- }
- var (
- ErrNoReleaseDownload = errors.New("couldn't find a release to download")
- ErrNoVersionToSelect = errors.New("no version to select")
- ErrUpgradeUnsupported = errors.New("upgrade unsupported")
- ErrUpgradeInProgress = errors.New("upgrade already in progress")
- upgradeUnlocked = make(chan bool, 1)
- )
- func init() {
- upgradeUnlocked <- true
- }
- func To(rel Release) error {
- select {
- case <-upgradeUnlocked:
- path, err := os.Executable()
- if err != nil {
- upgradeUnlocked <- true
- return err
- }
- err = upgradeTo(path, rel)
- // If we've failed to upgrade, unlock so that another attempt could be made
- if err != nil {
- upgradeUnlocked <- true
- }
- return err
- default:
- return ErrUpgradeInProgress
- }
- }
- func ToURL(url string) error {
- select {
- case <-upgradeUnlocked:
- binary, err := os.Executable()
- if err != nil {
- upgradeUnlocked <- true
- return err
- }
- err = upgradeToURL(path.Base(url), binary, url)
- // If we've failed to upgrade, unlock so that another attempt could be made
- if err != nil {
- upgradeUnlocked <- true
- }
- return err
- default:
- return ErrUpgradeInProgress
- }
- }
- type Relation int
- const (
- MajorOlder Relation = -2 // Older by a major version (x in x.y.z or 0.x.y).
- Older Relation = -1 // Older by a minor version (y or z in x.y.z, or y in 0.x.y)
- Equal Relation = 0 // Versions are semantically equal
- Newer Relation = 1 // Newer by a minor version (y or z in x.y.z, or y in 0.x.y)
- MajorNewer Relation = 2 // Newer by a major version (x in x.y.z or 0.x.y).
- )
- // CompareVersions returns a relation describing how a compares to b.
- func CompareVersions(a, b string) Relation {
- arel, apre := versionParts(a)
- brel, bpre := versionParts(b)
- minlen := len(arel)
- if l := len(brel); l < minlen {
- minlen = l
- }
- // First compare major-minor-patch versions
- for i := 0; i < minlen; i++ {
- if arel[i] < brel[i] {
- if i == 0 {
- // major version difference
- if arel[0] == 0 && brel[0] == 1 {
- // special case, v0.x is equivalent in majorness to v1.x.
- return Older
- }
- return MajorOlder
- }
- // minor or patch version difference
- return Older
- }
- if arel[i] > brel[i] {
- if i == 0 {
- // major version difference
- if arel[0] == 1 && brel[0] == 0 {
- // special case, v0.x is equivalent in majorness to v1.x.
- return Newer
- }
- return MajorNewer
- }
- // minor or patch version difference
- return Newer
- }
- }
- // Longer version is newer, when the preceding parts are equal
- if len(arel) < len(brel) {
- return Older
- }
- if len(arel) > len(brel) {
- return Newer
- }
- // Prerelease versions are older, if the versions are the same
- if len(apre) == 0 && len(bpre) > 0 {
- return Newer
- }
- if len(apre) > 0 && len(bpre) == 0 {
- return Older
- }
- minlen = len(apre)
- if l := len(bpre); l < minlen {
- minlen = l
- }
- // Compare prerelease strings
- for i := 0; i < minlen; i++ {
- switch av := apre[i].(type) {
- case int:
- switch bv := bpre[i].(type) {
- case int:
- if av < bv {
- return Older
- }
- if av > bv {
- return Newer
- }
- case string:
- return Older
- }
- case string:
- switch bv := bpre[i].(type) {
- case int:
- return Newer
- case string:
- if av < bv {
- return Older
- }
- if av > bv {
- return Newer
- }
- }
- }
- }
- // If all else is equal, longer prerelease string is newer
- if len(apre) < len(bpre) {
- return Older
- }
- if len(apre) > len(bpre) {
- return Newer
- }
- // Looks like they're actually the same
- return Equal
- }
- // Split a version into parts.
- // "1.2.3-beta.2" -> []int{1, 2, 3}, []interface{}{"beta", 2}
- func versionParts(v string) ([]int, []interface{}) {
- if strings.HasPrefix(v, "v") || strings.HasPrefix(v, "V") {
- // Strip initial 'v' or 'V' prefix if present.
- v = v[1:]
- }
- parts := strings.SplitN(v, "+", 2)
- parts = strings.SplitN(parts[0], "-", 2)
- fields := strings.Split(parts[0], ".")
- release := make([]int, len(fields))
- for i, s := range fields {
- v, _ := strconv.Atoi(s)
- release[i] = v
- }
- var prerelease []interface{}
- if len(parts) > 1 {
- fields = strings.Split(parts[1], ".")
- prerelease = make([]interface{}, len(fields))
- for i, s := range fields {
- v, err := strconv.Atoi(s)
- if err == nil {
- prerelease[i] = v
- } else {
- prerelease[i] = s
- }
- }
- }
- return release, prerelease
- }
- func releaseNames(tag string) []string {
- // We must ensure that the release asset matches the expected naming
- // standard, containing both the architecture/OS and the tag name we
- // expect. This protects against malformed release data potentially
- // tricking us into doing a downgrade.
- if build.IsDarwin {
- return []string{
- fmt.Sprintf("syncthing-macos-%s-%s.", runtime.GOARCH, tag),
- fmt.Sprintf("syncthing-macosx-%s-%s.", runtime.GOARCH, tag),
- }
- }
- return []string{
- fmt.Sprintf("syncthing-%s-%s-%s.", runtime.GOOS, runtime.GOARCH, tag),
- }
- }
|