safing-portmaster/base/updater/registry.go
2024-12-06 14:47:24 +02:00

265 lines
7.2 KiB
Go

package updater
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/base/utils"
)
// ResourceRegistry is a registry for managing update resources.
type ResourceRegistry struct {
sync.RWMutex
Name string
storageDir *utils.DirStructure
tmpDir *utils.DirStructure
indexes []*Index
state *RegistryState
resources map[string]*Resource
UpdateURLs []string
UserAgent string
MandatoryUpdates []string
AutoUnpack []string
// Verification holds a map of VerificationOptions assigned to their
// applicable identifier path prefix.
// Use an empty string to denote the default.
// Use empty options to disable verification for a path prefix.
Verification map[string]*VerificationOptions
// UsePreReleases signifies that pre-releases should be used when selecting a
// version. Even if false, a pre-release version will still be used if it is
// defined as the current version by an index.
UsePreReleases bool
// DevMode specifies if a local 0.0.0 version should be always chosen, when available.
DevMode bool
// Online specifies if resources may be downloaded if not available locally.
Online bool
// StateNotifyFunc may be set to receive any changes to the registry state.
// The specified function may lock the state, but may not block or take a
// lot of time.
StateNotifyFunc func(*RegistryState)
}
// AddIndex adds a new index to the resource registry.
// The order is important, as indexes added later will override the current
// release from earlier indexes.
func (reg *ResourceRegistry) AddIndex(idx Index) {
reg.Lock()
defer reg.Unlock()
// Get channel name from path.
idx.Channel = strings.TrimSuffix(
filepath.Base(idx.Path), filepath.Ext(idx.Path),
)
reg.indexes = append(reg.indexes, &idx)
}
// PreInitUpdateState sets the initial update state of the registry before initialization.
func (reg *ResourceRegistry) PreInitUpdateState(s UpdateState) error {
if reg.state != nil {
return errors.New("registry already initialized")
}
reg.state = &RegistryState{
Updates: s,
}
return nil
}
// Initialize initializes a raw registry struct and makes it ready for usage.
func (reg *ResourceRegistry) Initialize(storageDir *utils.DirStructure) error {
// check if storage dir is available
err := storageDir.Ensure()
if err != nil {
return err
}
// set default name
if reg.Name == "" {
reg.Name = "updater"
}
// initialize private attributes
reg.storageDir = storageDir
reg.tmpDir = storageDir.ChildDir("tmp", utils.AdminOnlyPermission)
reg.resources = make(map[string]*Resource)
if reg.state == nil {
reg.state = &RegistryState{}
}
reg.state.ID = StateReady
reg.state.reg = reg
// remove tmp dir to delete old entries
err = reg.Cleanup()
if err != nil {
log.Warningf("%s: failed to remove tmp dir: %s", reg.Name, err)
}
// (re-)create tmp dir
err = reg.tmpDir.Ensure()
if err != nil {
log.Warningf("%s: failed to create tmp dir: %s", reg.Name, err)
}
// Check verification options.
if reg.Verification != nil {
for prefix, opts := range reg.Verification {
// Check if verification is disable for this prefix.
if opts == nil {
continue
}
// If enabled, a trust store is required.
if opts.TrustStore == nil {
return fmt.Errorf("verification enabled for prefix %q, but no trust store configured", prefix)
}
// DownloadPolicy must be equal or stricter than DiskLoadPolicy.
if opts.DiskLoadPolicy < opts.DownloadPolicy {
return errors.New("verification download policy must be equal or stricter than the disk load policy")
}
// Warn if all policies are disabled.
if opts.DownloadPolicy == SignaturePolicyDisable &&
opts.DiskLoadPolicy == SignaturePolicyDisable {
log.Warningf("%s: verification enabled for prefix %q, but all policies set to disable", reg.Name, prefix)
}
}
}
return nil
}
// StorageDir returns the main storage dir of the resource registry.
func (reg *ResourceRegistry) StorageDir() *utils.DirStructure {
return reg.storageDir
}
// TmpDir returns the temporary working dir of the resource registry.
func (reg *ResourceRegistry) TmpDir() *utils.DirStructure {
return reg.tmpDir
}
// SetDevMode sets the development mode flag.
func (reg *ResourceRegistry) SetDevMode(on bool) {
reg.Lock()
defer reg.Unlock()
reg.DevMode = on
}
// SetUsePreReleases sets the UsePreReleases flag.
func (reg *ResourceRegistry) SetUsePreReleases(yes bool) {
reg.Lock()
defer reg.Unlock()
reg.UsePreReleases = yes
}
// AddResource adds a resource to the registry. Does _not_ select new version.
func (reg *ResourceRegistry) AddResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error {
reg.Lock()
defer reg.Unlock()
err := reg.addResource(identifier, version, index, available, currentRelease, preRelease)
return err
}
func (reg *ResourceRegistry) addResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error {
res, ok := reg.resources[identifier]
if !ok {
res = reg.newResource(identifier)
reg.resources[identifier] = res
}
res.Index = index
return res.AddVersion(version, available, currentRelease, preRelease)
}
// AddResources adds resources to the registry. Errors are logged, the last one is returned. Despite errors, non-failing resources are still added. Does _not_ select new versions.
func (reg *ResourceRegistry) AddResources(versions map[string]string, index *Index, available, currentRelease, preRelease bool) error {
reg.Lock()
defer reg.Unlock()
// add versions and their flags to registry
var lastError error
for identifier, version := range versions {
lastError = reg.addResource(identifier, version, index, available, currentRelease, preRelease)
if lastError != nil {
log.Warningf("%s: failed to add resource %s: %s", reg.Name, identifier, lastError)
}
}
return lastError
}
// SelectVersions selects new resource versions depending on the current registry state.
func (reg *ResourceRegistry) SelectVersions() {
reg.RLock()
defer reg.RUnlock()
for _, res := range reg.resources {
res.Lock()
res.selectVersion()
res.Unlock()
}
}
// GetSelectedVersions returns a list of the currently selected versions.
func (reg *ResourceRegistry) GetSelectedVersions() (versions map[string]string) {
reg.RLock()
defer reg.RUnlock()
for _, res := range reg.resources {
res.Lock()
versions[res.Identifier] = res.SelectedVersion.VersionNumber
res.Unlock()
}
return
}
// Purge deletes old updates, retaining a certain amount, specified by the keep
// parameter. Will at least keep 2 updates per resource.
func (reg *ResourceRegistry) Purge(keep int) {
reg.RLock()
defer reg.RUnlock()
for _, res := range reg.resources {
res.Purge(keep)
}
}
// ResetResources removes all resources from the registry.
func (reg *ResourceRegistry) ResetResources() {
reg.Lock()
defer reg.Unlock()
reg.resources = make(map[string]*Resource)
}
// ResetIndexes removes all indexes from the registry.
func (reg *ResourceRegistry) ResetIndexes() {
reg.Lock()
defer reg.Unlock()
reg.indexes = make([]*Index, 0, len(reg.indexes))
}
// Cleanup removes temporary files.
func (reg *ResourceRegistry) Cleanup() error {
// delete download tmp dir
return os.RemoveAll(reg.tmpDir.Path)
}