mirror of
https://github.com/safing/portmaster
synced 2025-04-07 12:39:09 +00:00
* 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>
355 lines
7.4 KiB
Go
355 lines
7.4 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/safing/portmaster/base/database/iterator"
|
|
"github.com/safing/portmaster/base/database/query"
|
|
"github.com/safing/portmaster/base/database/record"
|
|
"github.com/safing/portmaster/base/database/storage"
|
|
)
|
|
|
|
// A Controller takes care of all the extra database logic.
|
|
type Controller struct {
|
|
database *Database
|
|
storage storage.Interface
|
|
shadowDelete bool
|
|
|
|
hooksLock sync.RWMutex
|
|
hooks []*RegisteredHook
|
|
|
|
subscriptionLock sync.RWMutex
|
|
subscriptions []*Subscription
|
|
}
|
|
|
|
// newController creates a new controller for a storage.
|
|
func newController(database *Database, storageInt storage.Interface, shadowDelete bool) *Controller {
|
|
return &Controller{
|
|
database: database,
|
|
storage: storageInt,
|
|
shadowDelete: shadowDelete,
|
|
}
|
|
}
|
|
|
|
// ReadOnly returns whether the storage is read only.
|
|
func (c *Controller) ReadOnly() bool {
|
|
return c.storage.ReadOnly()
|
|
}
|
|
|
|
// Injected returns whether the storage is injected.
|
|
func (c *Controller) Injected() bool {
|
|
return c.storage.Injected()
|
|
}
|
|
|
|
// Get returns the record with the given key.
|
|
func (c *Controller) Get(key string) (record.Record, error) {
|
|
if shuttingDown.IsSet() {
|
|
return nil, ErrShuttingDown
|
|
}
|
|
|
|
if err := c.runPreGetHooks(key); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r, err := c.storage.Get(key)
|
|
if err != nil {
|
|
// replace not found error
|
|
if errors.Is(err, storage.ErrNotFound) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
r, err = c.runPostGetHooks(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !r.Meta().CheckValidity() {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// GetMeta returns the metadata of the record with the given key.
|
|
func (c *Controller) GetMeta(key string) (*record.Meta, error) {
|
|
if shuttingDown.IsSet() {
|
|
return nil, ErrShuttingDown
|
|
}
|
|
|
|
var m *record.Meta
|
|
var err error
|
|
if metaDB, ok := c.storage.(storage.MetaHandler); ok {
|
|
m, err = metaDB.GetMeta(key)
|
|
if err != nil {
|
|
// replace not found error
|
|
if errors.Is(err, storage.ErrNotFound) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
} else {
|
|
r, err := c.storage.Get(key)
|
|
if err != nil {
|
|
// replace not found error
|
|
if errors.Is(err, storage.ErrNotFound) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
m = r.Meta()
|
|
}
|
|
|
|
if !m.CheckValidity() {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// Put saves a record in the database, executes any registered
|
|
// pre-put hooks and finally send an update to all subscribers.
|
|
// The record must be locked and secured from concurrent access
|
|
// when calling Put().
|
|
func (c *Controller) Put(r record.Record) (err error) {
|
|
if shuttingDown.IsSet() {
|
|
return ErrShuttingDown
|
|
}
|
|
|
|
if c.ReadOnly() {
|
|
return ErrReadOnly
|
|
}
|
|
|
|
r, err = c.runPrePutHooks(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !c.shadowDelete && r.Meta().IsDeleted() {
|
|
// Immediate delete.
|
|
err = c.storage.Delete(r.DatabaseKey())
|
|
} else {
|
|
// Put or shadow delete.
|
|
r, err = c.storage.Put(r)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if r == nil {
|
|
return errors.New("storage returned nil record after successful put operation")
|
|
}
|
|
|
|
c.notifySubscribers(r)
|
|
|
|
return nil
|
|
}
|
|
|
|
// PutMany stores many records in the database. It does not
|
|
// process any hooks or update subscriptions. Use with care!
|
|
func (c *Controller) PutMany() (chan<- record.Record, <-chan error) {
|
|
if shuttingDown.IsSet() {
|
|
errs := make(chan error, 1)
|
|
errs <- ErrShuttingDown
|
|
return make(chan record.Record), errs
|
|
}
|
|
|
|
if c.ReadOnly() {
|
|
errs := make(chan error, 1)
|
|
errs <- ErrReadOnly
|
|
return make(chan record.Record), errs
|
|
}
|
|
|
|
if batcher, ok := c.storage.(storage.Batcher); ok {
|
|
return batcher.PutMany(c.shadowDelete)
|
|
}
|
|
|
|
errs := make(chan error, 1)
|
|
errs <- ErrNotImplemented
|
|
return make(chan record.Record), errs
|
|
}
|
|
|
|
// Query executes the given query on the database.
|
|
func (c *Controller) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) {
|
|
if shuttingDown.IsSet() {
|
|
return nil, ErrShuttingDown
|
|
}
|
|
|
|
it, err := c.storage.Query(q, local, internal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return it, nil
|
|
}
|
|
|
|
// PushUpdate pushes a record update to subscribers.
|
|
// The caller must hold the record's lock when calling
|
|
// PushUpdate.
|
|
func (c *Controller) PushUpdate(r record.Record) {
|
|
if c != nil {
|
|
if shuttingDown.IsSet() {
|
|
return
|
|
}
|
|
|
|
c.notifySubscribers(r)
|
|
}
|
|
}
|
|
|
|
func (c *Controller) addSubscription(sub *Subscription) {
|
|
if shuttingDown.IsSet() {
|
|
return
|
|
}
|
|
|
|
c.subscriptionLock.Lock()
|
|
defer c.subscriptionLock.Unlock()
|
|
|
|
c.subscriptions = append(c.subscriptions, sub)
|
|
}
|
|
|
|
// Maintain runs the Maintain method on the storage.
|
|
func (c *Controller) Maintain(ctx context.Context) error {
|
|
if shuttingDown.IsSet() {
|
|
return ErrShuttingDown
|
|
}
|
|
|
|
if maintainer, ok := c.storage.(storage.Maintainer); ok {
|
|
return maintainer.Maintain(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MaintainThorough runs the MaintainThorough method on the
|
|
// storage.
|
|
func (c *Controller) MaintainThorough(ctx context.Context) error {
|
|
if shuttingDown.IsSet() {
|
|
return ErrShuttingDown
|
|
}
|
|
|
|
if maintainer, ok := c.storage.(storage.Maintainer); ok {
|
|
return maintainer.MaintainThorough(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MaintainRecordStates runs the record state lifecycle
|
|
// maintenance on the storage.
|
|
func (c *Controller) MaintainRecordStates(ctx context.Context, purgeDeletedBefore time.Time) error {
|
|
if shuttingDown.IsSet() {
|
|
return ErrShuttingDown
|
|
}
|
|
|
|
return c.storage.MaintainRecordStates(ctx, purgeDeletedBefore, c.shadowDelete)
|
|
}
|
|
|
|
// Purge deletes all records that match the given query.
|
|
// It returns the number of successful deletes and an error.
|
|
func (c *Controller) Purge(ctx context.Context, q *query.Query, local, internal bool) (int, error) {
|
|
if shuttingDown.IsSet() {
|
|
return 0, ErrShuttingDown
|
|
}
|
|
|
|
if purger, ok := c.storage.(storage.Purger); ok {
|
|
return purger.Purge(ctx, q, local, internal, c.shadowDelete)
|
|
}
|
|
|
|
return 0, ErrNotImplemented
|
|
}
|
|
|
|
// Shutdown shuts down the storage.
|
|
func (c *Controller) Shutdown() error {
|
|
return c.storage.Shutdown()
|
|
}
|
|
|
|
// notifySubscribers notifies all subscribers that are interested
|
|
// in r. r must be locked when calling notifySubscribers.
|
|
// Any subscriber that is not blocking on it's feed channel will
|
|
// be skipped.
|
|
func (c *Controller) notifySubscribers(r record.Record) {
|
|
c.subscriptionLock.RLock()
|
|
defer c.subscriptionLock.RUnlock()
|
|
|
|
for _, sub := range c.subscriptions {
|
|
if r.Meta().CheckPermission(sub.local, sub.internal) && sub.q.Matches(r) {
|
|
select {
|
|
case sub.Feed <- r:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Controller) runPreGetHooks(key string) error {
|
|
c.hooksLock.RLock()
|
|
defer c.hooksLock.RUnlock()
|
|
|
|
for _, hook := range c.hooks {
|
|
if !hook.h.UsesPreGet() {
|
|
continue
|
|
}
|
|
|
|
if !hook.q.MatchesKey(key) {
|
|
continue
|
|
}
|
|
|
|
if err := hook.h.PreGet(key); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) runPostGetHooks(r record.Record) (record.Record, error) {
|
|
c.hooksLock.RLock()
|
|
defer c.hooksLock.RUnlock()
|
|
|
|
var err error
|
|
for _, hook := range c.hooks {
|
|
if !hook.h.UsesPostGet() {
|
|
continue
|
|
}
|
|
|
|
if !hook.q.Matches(r) {
|
|
continue
|
|
}
|
|
|
|
r, err = hook.h.PostGet(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (c *Controller) runPrePutHooks(r record.Record) (record.Record, error) {
|
|
c.hooksLock.RLock()
|
|
defer c.hooksLock.RUnlock()
|
|
|
|
var err error
|
|
for _, hook := range c.hooks {
|
|
if !hook.h.UsesPrePut() {
|
|
continue
|
|
}
|
|
|
|
if !hook.q.Matches(r) {
|
|
continue
|
|
}
|
|
|
|
r, err = hook.h.PrePut(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|