Update updates package to support pmctl

This commit is contained in:
Daniel 2019-03-13 09:40:36 +01:00
parent e07a2a058a
commit 6f268906ea
9 changed files with 66 additions and 190 deletions

View file

@ -34,9 +34,9 @@ func fetchFile(realFilepath, updateFilepath string, tries int) error {
return fmt.Errorf("error build url (%s + %s): %s", updateURLs[tries%len(updateURLs)], updateFilepath, err) return fmt.Errorf("error build url (%s + %s): %s", updateURLs[tries%len(updateURLs)], updateFilepath, err)
} }
// create destination dir // check destination dir
dirPath := filepath.Dir(realFilepath) dirPath := filepath.Dir(realFilepath)
err = os.MkdirAll(dirPath, 0755) err = CheckDir(dirPath)
if err != nil { if err != nil {
return fmt.Errorf("updates: could not create updates folder: %s", dirPath) return fmt.Errorf("updates: could not create updates folder: %s", dirPath)
} }
@ -75,7 +75,7 @@ func fetchFile(realFilepath, updateFilepath string, tries int) error {
log.Warningf("updates: failed to set permissions on downloaded file %s: %s", realFilepath, err) log.Warningf("updates: failed to set permissions on downloaded file %s: %s", realFilepath, err)
} }
log.Infof("update: fetched %s (stored to %s)", downloadURL, realFilepath) log.Infof("updates: fetched %s (stored to %s)", downloadURL, realFilepath)
return nil return nil
} }

View file

@ -7,7 +7,7 @@ type File struct {
stable bool stable bool
} }
func newFile(filepath string, version string, stable bool) *File { func NewFile(filepath string, version string, stable bool) *File {
return &File{ return &File{
filepath: filepath, filepath: filepath,
version: version, version: version,

View file

@ -8,7 +8,7 @@ import (
var versionRegex = regexp.MustCompile("_v[0-9]+-[0-9]+-[0-9]+b?") var versionRegex = regexp.MustCompile("_v[0-9]+-[0-9]+-[0-9]+b?")
func getIdentifierAndVersion(versionedPath string) (identifier, version string, ok bool) { func GetIdentifierAndVersion(versionedPath string) (identifier, version string, ok bool) {
// extract version // extract version
rawVersion := versionRegex.FindString(versionedPath) rawVersion := versionRegex.FindString(versionedPath)
if rawVersion == "" { if rawVersion == "" {
@ -27,7 +27,7 @@ func getIdentifierAndVersion(versionedPath string) (identifier, version string,
return versionedPath[:i] + versionedPath[i+len(rawVersion):], version, true return versionedPath[:i] + versionedPath[i+len(rawVersion):], version, true
} }
func getVersionedPath(identifier, version string) (versionedPath string) { func GetVersionedPath(identifier, version string) (versionedPath string) {
// split in half // split in half
splittedFilePath := strings.SplitN(identifier, ".", 2) splittedFilePath := strings.SplitN(identifier, ".", 2)
// replace . with - // replace . with -

View file

@ -31,6 +31,8 @@ func GetFile(identifier string) (*File, error) {
func getLatestFilePath(identifier string) (versionedFilePath, version string, stable bool, ok bool) { func getLatestFilePath(identifier string) (versionedFilePath, version string, stable bool, ok bool) {
updatesLock.RLock() updatesLock.RLock()
defer updatesLock.RUnlock()
version, ok = stableUpdates[identifier] version, ok = stableUpdates[identifier]
if !ok { if !ok {
version, ok = localUpdates[identifier] version, ok = localUpdates[identifier]
@ -41,10 +43,9 @@ func getLatestFilePath(identifier string) (versionedFilePath, version string, st
// err := reloadLatest() // err := reloadLatest()
} }
} }
updatesLock.RUnlock()
// TODO: Fix for stable release // TODO: Fix for stable release
return getVersionedPath(identifier, version), version, false, true return GetVersionedPath(identifier, version), version, false, true
} }
func loadOrFetchFile(identifier string) (*File, error) { func loadOrFetchFile(identifier string) (*File, error) {
@ -59,19 +60,24 @@ func loadOrFetchFile(identifier string) (*File, error) {
if _, err := os.Stat(realFilePath); err == nil { if _, err := os.Stat(realFilePath); err == nil {
// file exists // file exists
updateUsedStatus(identifier, version) updateUsedStatus(identifier, version)
return newFile(realFilePath, version, stable), nil return NewFile(realFilePath, version, stable), nil
}
// check download dir
err := CheckDir(filepath.Join(updateStoragePath, "tmp"))
if err != nil {
return nil, fmt.Errorf("could not prepare tmp directory for download: %s", err)
} }
// download file // download file
log.Tracef("updates: starting download of %s", versionedFilePath) log.Tracef("updates: starting download of %s", versionedFilePath)
var err error
for tries := 0; tries < 5; tries++ { for tries := 0; tries < 5; tries++ {
err := fetchFile(realFilePath, versionedFilePath, tries) err = fetchFile(realFilePath, versionedFilePath, tries)
if err != nil { if err != nil {
log.Tracef("updates: failed to download %s: %s, retrying (%d)", versionedFilePath, err, tries+1) log.Tracef("updates: failed to download %s: %s, retrying (%d)", versionedFilePath, err, tries+1)
} else { } else {
updateUsedStatus(identifier, version) updateUsedStatus(identifier, version)
return newFile(realFilePath, version, stable), nil return NewFile(realFilePath, version, stable), nil
} }
} }
log.Warningf("updates: failed to download %s: %s", versionedFilePath, err) log.Warningf("updates: failed to download %s: %s", versionedFilePath, err)

View file

@ -21,8 +21,8 @@ var (
updatesLock sync.RWMutex updatesLock sync.RWMutex
) )
// ReloadLatest reloads available updates from disk. // LoadLatest (re)loads the latest available updates from disk.
func ReloadLatest() error { func LoadLatest() error {
newLocalUpdates := make(map[string]string) newLocalUpdates := make(map[string]string)
// all // all
@ -55,13 +55,6 @@ func ReloadLatest() error {
log.Tracef("updates: load complete") log.Tracef("updates: load complete")
if len(stableUpdates) == 0 {
err := loadIndexesFromDisk()
if err != nil {
return err
}
}
// update version status // update version status
updatesLock.RLock() updatesLock.RLock()
defer updatesLock.RUnlock() defer updatesLock.RUnlock()
@ -76,11 +69,13 @@ func ScanForLatest(baseDir string, hardFail bool) (latest map[string]string, las
filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error { filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
if !os.IsNotExist(err) {
lastError = err lastError = err
if hardFail { if hardFail {
return err return err
} }
log.Warningf("updates: could not read %s", path) log.Warningf("updates: could not read %s", path)
}
return nil return nil
} }
if !info.IsDir() { if !info.IsDir() {
@ -88,7 +83,7 @@ func ScanForLatest(baseDir string, hardFail bool) (latest map[string]string, las
} }
relativePath := strings.TrimLeft(strings.TrimPrefix(path, baseDir), "/") relativePath := strings.TrimLeft(strings.TrimPrefix(path, baseDir), "/")
identifierPath, version, ok := getIdentifierAndVersion(relativePath) identifierPath, version, ok := GetIdentifierAndVersion(relativePath)
if !ok { if !ok {
return nil return nil
} }
@ -118,13 +113,9 @@ func ScanForLatest(baseDir string, hardFail bool) (latest map[string]string, las
return latest, nil return latest, nil
} }
func loadIndexesFromDisk() error { func LoadIndexes() error {
data, err := ioutil.ReadFile(filepath.Join(updateStoragePath, "stable.json")) data, err := ioutil.ReadFile(filepath.Join(updateStoragePath, "stable.json"))
if err != nil { if err != nil {
if os.IsNotExist(err) {
log.Infof("updates: stable.json does not yet exist, waiting for first update cycle")
return nil
}
return err return err
} }

View file

@ -1,40 +1,45 @@
package updates package updates
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"github.com/Safing/portbase/database" "github.com/Safing/portbase/database"
"github.com/Safing/portbase/info" "github.com/Safing/portbase/info"
"github.com/Safing/portbase/log"
"github.com/Safing/portbase/modules" "github.com/Safing/portbase/modules"
// module dependencies
_ "github.com/Safing/portmaster/core"
) )
var ( var (
updateStoragePath string updateStoragePath string
) )
// SetDatabaseRoot tells the updates module where the database is - and where to put its stuff.
func SetDatabaseRoot(path string) {
if updateStoragePath == "" {
updateStoragePath = filepath.Join(path, "updates")
}
}
func init() { func init() {
modules.Register("updates", prep, start, nil, "core") modules.Register("updates", prep, start, nil, "core")
} }
func prep() error { func prep() error {
dbRoot := database.GetDatabaseRoot()
if dbRoot == "" {
return errors.New("database root is not set")
}
updateStoragePath = filepath.Join(dbRoot, "updates")
err := CheckDir(updateStoragePath)
if err != nil {
return err
}
status.Core = info.GetInfo() status.Core = info.GetInfo()
updateStoragePath = filepath.Join(database.GetDatabaseRoot(), "updates")
err := checkUpdateDirs()
if err != nil {
return err
}
err = upgradeByFlag()
if err != nil {
return err
}
return nil return nil
} }
@ -45,7 +50,16 @@ func start() error {
return err return err
} }
err = ReloadLatest() err = LoadIndexes()
if err != nil {
if os.IsNotExist(err) {
log.Infof("updates: stable.json does not yet exist, waiting for first update cycle")
} else {
return err
}
}
err = LoadLatest()
if err != nil { if err != nil {
return err return err
} }
@ -59,29 +73,7 @@ func stop() error {
return os.RemoveAll(filepath.Join(updateStoragePath, "tmp")) return os.RemoveAll(filepath.Join(updateStoragePath, "tmp"))
} }
func checkUpdateDirs() error { func CheckDir(dirPath string) error {
// all
err := checkDir(filepath.Join(updateStoragePath, "all"))
if err != nil {
return err
}
// os_platform
err = checkDir(filepath.Join(updateStoragePath, fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)))
if err != nil {
return err
}
// tmp
err = checkDir(filepath.Join(updateStoragePath, "tmp"))
if err != nil {
return err
}
return nil
}
func checkDir(dirPath string) error {
f, err := os.Stat(dirPath) f, err := os.Stat(dirPath)
if err == nil { if err == nil {
// file exists // file exists

View file

@ -7,6 +7,8 @@ import (
"github.com/Safing/portbase/notifications" "github.com/Safing/portbase/notifications"
) )
const coreIdentifier = "core/portmaster"
var lastNotified time.Time var lastNotified time.Time
func updateNotifier() { func updateNotifier() {
@ -24,7 +26,7 @@ func updateNotifier() {
// create notification // create notification
(&notifications.Notification{ (&notifications.Notification{
ID: "updates-core-update-available", ID: "updates-core-update-available",
Message: fmt.Sprintf("There is an update available for the portmaster core (%s), you may apply the update with the -upgrade flag.", version), Message: fmt.Sprintf("There is an update available for the Portmaster core (v%s), please restart the Portmaster to apply the update.", version),
Type: notifications.Info, Type: notifications.Info,
Expires: time.Now().Add(1 * time.Minute).Unix(), Expires: time.Now().Add(1 * time.Minute).Unix(),
}).Init().Save() }).Init().Save()

View file

@ -13,7 +13,7 @@ import (
func updater() { func updater() {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
for { for {
err := checkForUpdates() err := CheckForUpdates()
if err != nil { if err != nil {
log.Warningf("updates: failed to check for updates: %s", err) log.Warningf("updates: failed to check for updates: %s", err)
} }
@ -21,7 +21,7 @@ func updater() {
} }
} }
func checkForUpdates() error { func CheckForUpdates() error {
// download new index // download new index
var data []byte var data []byte
@ -46,7 +46,7 @@ func checkForUpdates() error {
return errors.New("stable.json is empty") return errors.New("stable.json is empty")
} }
// FIXINSTABLE: correct log line // FIXME IN STABLE: correct log line
log.Infof("updates: downloaded new update index: stable.json (alpha until we actually reach stable)") log.Infof("updates: downloaded new update index: stable.json (alpha until we actually reach stable)")
// update existing files // update existing files
@ -56,7 +56,7 @@ func checkForUpdates() error {
oldVersion, ok := localUpdates[identifier] oldVersion, ok := localUpdates[identifier]
if ok && newVersion != oldVersion { if ok && newVersion != oldVersion {
filePath := getVersionedPath(identifier, newVersion) filePath := GetVersionedPath(identifier, newVersion)
realFilePath := filepath.Join(updateStoragePath, filePath) realFilePath := filepath.Join(updateStoragePath, filePath)
for tries := 0; tries < 3; tries++ { for tries := 0; tries < 3; tries++ {
err := fetchFile(realFilePath, filePath, tries) err := fetchFile(realFilePath, filePath, tries)

View file

@ -1,115 +0,0 @@
package updates
import (
"flag"
"fmt"
"io"
"os"
"path/filepath"
"github.com/Safing/portbase/modules"
)
const (
coreIdentifier = "core/portmaster"
)
var (
upgradeSelf bool
)
func init() {
flag.BoolVar(&upgradeSelf, "upgrade", false, "upgrade to newest portmaster core binary")
}
func upgradeByFlag() error {
if !upgradeSelf {
return nil
}
err := ReloadLatest()
if err != nil {
return err
}
return doSelfUpgrade()
}
func doSelfUpgrade() error {
// get source
file, err := GetPlatformFile(coreIdentifier)
if err != nil {
return fmt.Errorf("%s currently not available: %s - you may need to first start portmaster and wait for it to fetch the update index", coreIdentifier, err)
}
// get destination
dst, err := os.Executable()
if err != nil {
return err
}
dst, err = filepath.EvalSymlinks(dst)
if err != nil {
return err
}
// mv destination
err = os.Rename(dst, dst+"_old")
if err != nil {
return err
}
// hard link
err = os.Link(file.Path(), dst)
if err != nil {
fmt.Printf("failed to hardlink: %s, will copy...\n", err)
err = copyFile(file.Path(), dst)
if err != nil {
return err
}
}
// check permission
info, err := os.Stat(dst)
if info.Mode() != 0755 {
err := os.Chmod(dst, 0755)
if err != nil {
return fmt.Errorf("failed to set permissions on %s: %s", dst, err)
}
}
// delete old
err = os.Remove(dst + "_old")
if err != nil {
return err
}
// gracefully exit portmaster
return modules.ErrCleanExit
}
func copyFile(srcPath, dstPath string) (err error) {
srcFile, err := os.Open(srcPath)
if err != nil {
return
}
defer srcFile.Close()
dstFile, err := os.Create(dstPath)
if err != nil {
return
}
defer func() {
closeErr := dstFile.Close()
if err == nil {
err = closeErr
}
}()
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return
}
err = dstFile.Sync()
return
}