safing-portmaster/base/updater/registry.go
Daniel Hååvi 80664d1a27
Restructure modules (#1572)
* Move portbase into monorepo

* Add new simple module mgr

* [WIP] Switch to new simple module mgr

* Add StateMgr and more worker variants

* [WIP] Switch more modules

* [WIP] Switch more modules

* [WIP] swtich more modules

* [WIP] switch all SPN modules

* [WIP] switch all service modules

* [WIP] Convert all workers to the new module system

* [WIP] add new task system to module manager

* [WIP] Add second take for scheduling workers

* [WIP] Add FIXME for bugs in new scheduler

* [WIP] Add minor improvements to scheduler

* [WIP] Add new worker scheduler

* [WIP] Fix more bug related to new module system

* [WIP] Fix start handing of the new module system

* [WIP] Improve startup process

* [WIP] Fix minor issues

* [WIP] Fix missing subsystem in settings

* [WIP] Initialize managers in constructor

* [WIP] Move module event initialization to constrictors

* [WIP] Fix setting for enabling and disabling the SPN module

* [WIP] Move API registeration into module construction

* [WIP] Update states mgr for all modules

* [WIP] Add CmdLine operation support

* Add state helper methods to module group and instance

* Add notification and module status handling to status package

* Fix starting issues

* Remove pilot widget and update security lock to new status data

* Remove debug logs

* Improve http server shutdown

* Add workaround for cleanly shutting down firewall+netquery

* Improve logging

* Add syncing states with notifications for new module system

* Improve starting, stopping, shutdown; resolve FIXMEs/TODOs

* [WIP] Fix most unit tests

* Review new module system and fix minor issues

* Push shutdown and restart events again via API

* Set sleep mode via interface

* Update example/template module

* [WIP] Fix spn/cabin unit test

* Remove deprecated UI elements

* Make log output more similar for the logging transition phase

* Switch spn hub and observer cmds to new module system

* Fix log sources

* Make worker mgr less error prone

* Fix tests and minor issues

* Fix observation hub

* Improve shutdown and restart handling

* Split up big connection.go source file

* Move varint and dsd packages to structures repo

* Improve expansion test

* Fix linter warnings

* Fix interception module on windows

* Fix linter errors

---------

Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
2024-08-09 18:15:48 +03:00

270 lines
7.3 KiB
Go

package updater
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/base/utils"
)
const (
onWindows = runtime.GOOS == "windows"
)
// 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", 0o0700)
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)
}