mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
249 lines
5.8 KiB
Go
249 lines
5.8 KiB
Go
package updates
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
semver "github.com/hashicorp/go-version"
|
|
|
|
"github.com/safing/portmaster/base/log"
|
|
)
|
|
|
|
const (
|
|
defaultFileMode = os.FileMode(0o0644)
|
|
executableFileMode = os.FileMode(0o0744)
|
|
executableUIFileMode = os.FileMode(0o0755)
|
|
defaultDirMode = os.FileMode(0o0755)
|
|
)
|
|
|
|
type Registry struct {
|
|
bundle *Bundle
|
|
dir string
|
|
purgeDir string
|
|
files map[string]File
|
|
|
|
version *semver.Version
|
|
}
|
|
|
|
func CreateRegistry(index UpdateIndex) (Registry, error) {
|
|
registry := Registry{
|
|
dir: index.Directory,
|
|
purgeDir: index.PurgeDirectory,
|
|
files: make(map[string]File),
|
|
}
|
|
// Parse bundle
|
|
var err error
|
|
registry.bundle, err = LoadBundle(filepath.Join(index.Directory, index.IndexFile))
|
|
if err != nil {
|
|
return Registry{}, err
|
|
}
|
|
|
|
// Parse version
|
|
registry.version, err = semver.NewVersion(registry.bundle.Version)
|
|
if err != nil {
|
|
log.Errorf("updates: failed to parse current version: %s", err)
|
|
}
|
|
|
|
// Process files
|
|
for _, artifact := range registry.bundle.Artifacts {
|
|
artifactPath := filepath.Join(registry.dir, artifact.Filename)
|
|
registry.files[artifact.Filename] = File{id: artifact.Filename, path: artifactPath, version: registry.bundle.Version, sha256: artifact.SHA256}
|
|
}
|
|
return registry, nil
|
|
}
|
|
|
|
func CreateRegistryFromFolder(index UpdateIndex) (Registry, error) {
|
|
registry := Registry{
|
|
dir: index.Directory,
|
|
purgeDir: index.PurgeDirectory,
|
|
files: make(map[string]File),
|
|
}
|
|
|
|
files, err := os.ReadDir(index.Directory)
|
|
if err != nil {
|
|
return Registry{}, nil
|
|
}
|
|
for _, file := range files {
|
|
// Skip dirs
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
|
|
// Skip the uninstaller. (Windows)
|
|
if strings.Contains(strings.ToLower(file.Name()), "uninstall") {
|
|
continue
|
|
}
|
|
|
|
artifactPath := filepath.Join(registry.dir, file.Name())
|
|
registry.files[file.Name()] = File{id: file.Name(), path: artifactPath, version: "", sha256: ""}
|
|
}
|
|
|
|
return registry, nil
|
|
}
|
|
|
|
func (r *Registry) performUpgrade(downloadDir string, indexFile string) error {
|
|
// Make sure provided update is valid
|
|
indexFilepath := filepath.Join(downloadDir, indexFile)
|
|
bundle, err := LoadBundle(indexFilepath)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid update: %w", err)
|
|
}
|
|
|
|
err = bundle.Verify(downloadDir)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid update: %w", err)
|
|
}
|
|
|
|
// Make sure purge dir is empty.
|
|
_ = os.RemoveAll(r.purgeDir)
|
|
|
|
// Create purge dir.
|
|
err = os.MkdirAll(r.purgeDir, defaultDirMode)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create directory: %w", err)
|
|
}
|
|
|
|
// Move current version files into purge folder.
|
|
log.Debugf("updates: removing the old version")
|
|
for _, file := range r.files {
|
|
purgePath := filepath.Join(r.purgeDir, file.id)
|
|
err := moveFile(file.path, purgePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to move file %s: %w", file.path, err)
|
|
}
|
|
}
|
|
|
|
// Move the new index file
|
|
log.Debugf("updates: installing the new version")
|
|
newIndexFile := filepath.Join(r.dir, indexFile)
|
|
err = moveFile(indexFilepath, newIndexFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to move index file %s: %w", indexFile, err)
|
|
}
|
|
|
|
// Move downloaded files to the current version folder.
|
|
for _, artifact := range bundle.Artifacts {
|
|
fromFilepath := filepath.Join(downloadDir, artifact.Filename)
|
|
toFilepath := filepath.Join(r.dir, artifact.Filename)
|
|
err = moveFile(fromFilepath, toFilepath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to move file %s: %w", fromFilepath, err)
|
|
} else {
|
|
log.Debugf("updates: %s moved", artifact.Filename)
|
|
}
|
|
}
|
|
|
|
log.Infof("updates: update complete")
|
|
|
|
return nil
|
|
}
|
|
|
|
func moveFile(currentPath, newPath string) error {
|
|
err := os.Rename(currentPath, newPath)
|
|
if err == nil {
|
|
// Moving was successful return
|
|
return nil
|
|
}
|
|
|
|
log.Debugf("updates: failed to move '%s' fallback to copy+delete: %s -> %s", err, currentPath, newPath)
|
|
|
|
// Failed to move, try copy and delete
|
|
currentFile, err := os.Open(currentPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = currentFile.Close() }()
|
|
|
|
newFile, err := os.Create(newPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = newFile.Close() }()
|
|
|
|
_, err = io.Copy(newFile, currentFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Make sure file is closed before deletion.
|
|
_ = currentFile.Close()
|
|
currentFile = nil
|
|
|
|
err = os.Remove(currentPath)
|
|
if err != nil {
|
|
log.Errorf("updates: failed to delete while moving file: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) performRecoverableUpgrade(downloadDir string, indexFile string) error {
|
|
upgradeError := r.performUpgrade(downloadDir, indexFile)
|
|
if upgradeError != nil {
|
|
err := r.recover()
|
|
recoverStatus := "(recovery successful)"
|
|
if err != nil {
|
|
recoverStatus = "(recovery failed)"
|
|
log.Errorf("updates: failed to recover: %s", err)
|
|
}
|
|
|
|
return fmt.Errorf("upgrade failed: %w %s", upgradeError, recoverStatus)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) recover() error {
|
|
files, err := os.ReadDir(r.purgeDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, file := range files {
|
|
recoverPath := filepath.Join(r.purgeDir, file.Name())
|
|
currentFilepath := filepath.Join(r.dir, file.Name())
|
|
err := moveFile(recoverPath, currentFilepath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) CleanOldFiles() error {
|
|
err := os.RemoveAll(r.purgeDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete folder: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type File struct {
|
|
id string
|
|
path string
|
|
version string
|
|
sha256 string
|
|
}
|
|
|
|
// Identifier return the id of the file witch is the same as the filename.
|
|
func (f *File) Identifier() string {
|
|
return f.id
|
|
}
|
|
|
|
// Path returns the path + filename of the file.
|
|
func (f *File) Path() string {
|
|
return f.path
|
|
}
|
|
|
|
// Version returns the version of the file. (currently not filled).
|
|
func (f *File) Version() string {
|
|
return f.version
|
|
}
|
|
|
|
// Sha256 returns the sha356 sum of the file.
|
|
func (f *File) Sha256() string {
|
|
return f.sha256
|
|
}
|