Replace dataroot module with BinDir and DataDir on instance, adapt modules

This commit is contained in:
Daniel
2024-11-06 10:48:02 +01:00
parent 0f3f3c360f
commit 7bc1c3b764
39 changed files with 819 additions and 482 deletions

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"
@@ -111,6 +112,9 @@ func (ii *InstallInfo) checkVersion() {
// MakeNumericVersion makes a numeric version with the first three version
// segment always using three digits.
func MakeNumericVersion(version string) (numericVersion int64, err error) {
// Remove any comments.
version = strings.SplitN(version, " ", 2)[0]
// Parse version string.
ver, err := semver.NewVersion(version)
if err != nil {

View File

@@ -1,3 +1,89 @@
package service
type ServiceConfig struct{}
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/safing/jess"
)
type ServiceConfig struct {
BinDir string
DataDir string
BinariesIndexURLs []string
IntelIndexURLs []string
VerifyBinaryUpdates jess.TrustStore
VerifyIntelUpdates jess.TrustStore
}
func (sc *ServiceConfig) Init() error {
// Check directories
switch runtime.GOOS {
case "windows":
if sc.BinDir == "" {
exeDir, err := getCurrentBinaryFolder() // Default: C:/Program Files/Portmaster
if err != nil {
return fmt.Errorf("derive bin dir from runnning exe: %w", err)
}
sc.BinDir = exeDir
}
if sc.DataDir == "" {
sc.DataDir = filepath.FromSlash("$ProgramData/Portmaster")
}
case "linux":
// Fall back to defaults.
if sc.BinDir == "" {
sc.BinDir = "/usr/lib/portmaster"
}
if sc.DataDir == "" {
sc.DataDir = "/var/lib/portmaster"
}
default:
// Fail if not configured on other platforms.
if sc.BinDir == "" {
return errors.New("binary directory must be configured - auto-detection not supported on this platform")
}
if sc.DataDir == "" {
return errors.New("binary directory must be configured - auto-detection not supported on this platform")
}
}
// Expand path variables.
sc.BinDir = os.ExpandEnv(sc.BinDir)
sc.DataDir = os.ExpandEnv(sc.DataDir)
// Apply defaults for required fields.
if len(sc.BinariesIndexURLs) == 0 {
sc.BinariesIndexURLs = DefaultBinaryIndexURLs
}
if len(sc.IntelIndexURLs) == 0 {
sc.IntelIndexURLs = DefaultIntelIndexURLs
}
return nil
}
func getCurrentBinaryFolder() (string, error) {
// Get the path of the currently running executable
exePath, err := os.Executable()
if err != nil {
return "", fmt.Errorf("failed to get executable path: %w", err)
}
// Get the absolute path
absPath, err := filepath.Abs(exePath)
if err != nil {
return "", fmt.Errorf("failed to get absolute path: %w", err)
}
// Get the directory of the executable
installDir := filepath.Dir(absPath)
return installDir, nil
}

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"github.com/safing/portmaster/base/api"
"github.com/safing/portmaster/base/dataroot"
"github.com/safing/portmaster/base/info"
"github.com/safing/portmaster/service/mgr"
)
@@ -15,14 +14,10 @@ import (
var (
DefaultAPIListenAddress = "127.0.0.1:817"
dataDir string
databaseDir string
showVersion bool
)
func init() {
flag.StringVar(&dataDir, "data", "", "set data directory")
flag.StringVar(&databaseDir, "db", "", "alias to --data (deprecated)")
flag.BoolVar(&showVersion, "version", false, "show version and exit")
}
@@ -39,27 +34,6 @@ func prep(instance instance) error {
return mgr.ErrExecuteCmdLineOp
}
// check data root
if dataroot.Root() == nil {
// initialize data dir
// backwards compatibility
if dataDir == "" {
dataDir = databaseDir
}
// check data dir
if dataDir == "" {
return errors.New("please set the data directory using --data=/path/to/data/dir")
}
// initialize structure
err := dataroot.Initialize(dataDir, 0o0755)
if err != nil {
return err
}
}
// set api listen address
api.SetDefaultAPIListenAddress(DefaultAPIListenAddress)

View File

@@ -7,7 +7,6 @@ import (
"strings"
"time"
"github.com/safing/portmaster/base/dataroot"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/service/mgr"
)
@@ -26,7 +25,7 @@ func logCleaner(_ *mgr.WorkerCtx) error {
ageThreshold := time.Now().Add(-logTTL)
return filepath.Walk(
filepath.Join(dataroot.Root().Path, logFileDir),
filepath.Join(module.instance.DataDir(), logFileDir),
func(path string, info os.FileInfo, err error) error {
if err != nil {
if !errors.Is(err, os.ErrNotExist) {

View File

@@ -58,5 +58,6 @@ func New(instance instance) (*Base, error) {
}
type instance interface {
DataDir() string
SetCmdLineOperation(f func() error)
}

View File

@@ -11,9 +11,7 @@ import (
"time"
"github.com/safing/portmaster/base/api"
"github.com/safing/portmaster/base/dataroot"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/base/utils"
"github.com/safing/portmaster/service/netenv"
"github.com/safing/portmaster/service/network/netutils"
"github.com/safing/portmaster/service/network/packet"
@@ -38,15 +36,12 @@ For production use please create an API key in the settings.`
)
var (
dataRoot *utils.DirStructure
apiPortSet bool
apiIP net.IP
apiPort uint16
)
func prepAPIAuth() error {
dataRoot = dataroot.Root()
return api.SetAuthenticator(apiAuthenticator)
}
@@ -132,7 +127,7 @@ func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bo
var originalPid int
// Get authenticated path.
authenticatedPath := module.instance.BinaryUpdates().GetRootPath()
authenticatedPath := module.instance.BinaryUpdates().GetMainDir()
if authenticatedPath == "" {
return false, fmt.Errorf(deniedMsgMisconfigured, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user
}
@@ -214,7 +209,7 @@ func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bo
return false, fmt.Errorf(deniedMsgSystem, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user
default: // normal process
log.Tracer(ctx).Warningf("filter: denying api access to %s - also checked %s (trusted root is %s)", procsChecked[0], strings.Join(procsChecked[1:], " "), dataRoot.Path)
log.Tracer(ctx).Warningf("filter: denying api access to %s - also checked %s (trusted root is %s)", procsChecked[0], strings.Join(procsChecked[1:], " "), module.instance.BinDir())
return false, fmt.Errorf( //nolint:stylecheck // message for user
deniedMsgUnauthorized,
api.ErrAPIAccessDeniedMessage,

View File

@@ -160,6 +160,7 @@ func New(instance instance) (*Firewall, error) {
}
type instance interface {
BinDir() string
Config() *config.Config
BinaryUpdates() *updates.Updater
Profile() *profile.ProfileModule

View File

@@ -4,8 +4,6 @@ import (
"context"
"fmt"
"os"
"path/filepath"
go_runtime "runtime"
"sync/atomic"
"time"
@@ -55,6 +53,9 @@ type Instance struct {
cancelCtx context.CancelFunc
serviceGroup *mgr.Group
binDir string
dataDir string
exitCode atomic.Int32
database *dbmodule.DBModule
@@ -105,83 +106,27 @@ type Instance struct {
ShouldRestart bool
}
func getCurrentBinaryFolder() (string, error) {
// Get the path of the currently running executable
exePath, err := os.Executable()
if err != nil {
return "", fmt.Errorf("failed to get executable path: %w", err)
}
// Get the absolute path
absPath, err := filepath.Abs(exePath)
if err != nil {
return "", fmt.Errorf("failed to get absolute path: %w", err)
}
// Get the directory of the executable
installDir := filepath.Dir(absPath)
return installDir, nil
}
// New returns a new Portmaster service instance.
func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
var binaryUpdateIndex updates.Config
var intelUpdateIndex updates.Config
if go_runtime.GOOS == "windows" {
binaryFolder, err := getCurrentBinaryFolder()
if err != nil {
return nil, err
}
binaryUpdateIndex = updates.Config{
Directory: binaryFolder, // Default: C:/Program Files/Portmaster
DownloadDirectory: os.ExpandEnv("$ProgramData/Portmaster/new_binary"),
PurgeDirectory: filepath.Join(binaryFolder, "old_binary"), // Default: C:/Program Files/Portmaster/old_binary
Ignore: []string{"databases", "intel", "config.json"},
IndexURLs: []string{"http://192.168.88.11:8000/test-binary.json"},
IndexFile: "bin-index.json",
AutoApply: false,
NeedsRestart: true,
}
// Initialize config.
err := svcCfg.Init()
if err != nil {
return nil, fmt.Errorf("internal service config error: %w", err)
}
intelUpdateIndex = updates.Config{
Directory: os.ExpandEnv("$ProgramData/Portmaster/intel"),
DownloadDirectory: os.ExpandEnv("$ProgramData/Portmaster/new_intel"),
PurgeDirectory: os.ExpandEnv("$ProgramData/Portmaster/old_intel"),
IndexURLs: []string{"http://192.168.88.11:8000/test-intel.json"},
IndexFile: "intel-index.json",
AutoApply: true,
NeedsRestart: false,
}
} else if go_runtime.GOOS == "linux" {
binaryUpdateIndex = updates.Config{
Directory: "/usr/lib/portmaster",
DownloadDirectory: "/var/lib/portmaster/new_bin",
PurgeDirectory: "/var/lib/portmaster/old_bin",
Ignore: []string{"databases", "intel", "config.json"},
IndexURLs: []string{"http://localhost:8000/test-binary.json"},
IndexFile: "bin-index.json",
AutoApply: false,
NeedsRestart: true,
}
intelUpdateIndex = updates.Config{
Directory: "/var/lib/portmaster/intel",
DownloadDirectory: "/var/lib/portmaster/new_intel",
PurgeDirectory: "/var/lib/portmaster/intel_bin",
IndexURLs: []string{"http://localhost:8000/test-intel.json"},
IndexFile: "intel-index.json",
AutoApply: true,
NeedsRestart: false,
}
// Make sure data dir exists, so that child directories don't dictate the permissions.
err = os.MkdirAll(svcCfg.DataDir, 0o0755)
if err != nil {
return nil, fmt.Errorf("data directory %s is not accessible: %w", svcCfg.DataDir, err)
}
// Create instance to pass it to modules.
instance := &Instance{}
instance := &Instance{
binDir: svcCfg.BinDir,
dataDir: svcCfg.DataDir,
}
instance.ctx, instance.cancelCtx = context.WithCancel(context.Background())
var err error
// Base modules
instance.base, err = base.New(instance)
if err != nil {
@@ -221,11 +166,15 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
if err != nil {
return instance, fmt.Errorf("create core module: %w", err)
}
instance.binaryUpdates, err = updates.New(instance, "Binary Updater", binaryUpdateIndex)
binaryUpdateConfig, intelUpdateConfig, err := MakeUpdateConfigs(svcCfg)
if err != nil {
return instance, fmt.Errorf("create updates config: %w", err)
}
instance.binaryUpdates, err = updates.New(instance, "Binary Updater", *binaryUpdateConfig)
if err != nil {
return instance, fmt.Errorf("create updates module: %w", err)
}
instance.intelUpdates, err = updates.New(instance, "Intel Updater", intelUpdateIndex)
instance.intelUpdates, err = updates.New(instance, "Intel Updater", *intelUpdateConfig)
if err != nil {
return instance, fmt.Errorf("create updates module: %w", err)
}
@@ -413,6 +362,18 @@ func (i *Instance) SetSleep(enabled bool) {
}
}
// BinDir returns the directory for binaries.
// This directory may be read-only.
func (i *Instance) BinDir() string {
return i.binDir
}
// DataDir returns the directory for variable data.
// This directory is expected to be read/writeable.
func (i *Instance) DataDir() string {
return i.dataDir
}
// Database returns the database module.
func (i *Instance) Database() *dbmodule.DBModule {
return i.database

View File

@@ -39,9 +39,9 @@ var (
filterListLock sync.RWMutex
// Updater files for tracking upgrades.
baseFile *updates.File
intermediateFile *updates.File
urgentFile *updates.File
baseFile *updates.Artifact
intermediateFile *updates.Artifact
urgentFile *updates.Artifact
filterListsLoaded chan struct{}
)
@@ -77,7 +77,7 @@ func isLoaded() bool {
// processListFile opens the latest version of file and decodes it's DSDL
// content. It calls processEntry for each decoded filterlists entry.
func processListFile(ctx context.Context, filter *scopedBloom, file *updates.File) error {
func processListFile(ctx context.Context, filter *scopedBloom, file *updates.Artifact) error {
f, err := os.Open(file.Path())
if err != nil {
return err

View File

@@ -162,7 +162,7 @@ func getListIndexFromCache() (*ListIndexFile, error) {
var (
// listIndexUpdate must only be used by updateListIndex.
listIndexUpdate *updates.File
listIndexUpdate *updates.Artifact
listIndexUpdateLock sync.Mutex
)

View File

@@ -63,7 +63,7 @@ func performUpdate(ctx context.Context) error {
// First, update the list index.
err := updateListIndex()
if err != nil {
log.Errorf("intel/filterlists: failed update list index: %s", err)
log.Warningf("intel/filterlists: failed update list index: %s", err)
}
upgradables, err := getUpgradableFiles()
@@ -83,7 +83,7 @@ func performUpdate(ctx context.Context) error {
// perform the actual upgrade by processing each file
// in the returned order.
for idx, file := range upgradables {
log.Debugf("intel/filterlists: applying update (%d) %s version %s", idx, file.Identifier(), file.Version())
log.Debugf("intel/filterlists: applying update (%d) %s version %s", idx, file.Filename, file.Version)
if file == baseFile {
if idx != 0 {
@@ -101,7 +101,7 @@ func performUpdate(ctx context.Context) error {
}
if err := processListFile(ctx, filterToUpdate, file); err != nil {
return fmt.Errorf("failed to process upgrade %s: %w", file.Identifier(), err)
return fmt.Errorf("failed to process upgrade %s version %s: %w", file.Filename, file.Version, err)
}
}
@@ -145,10 +145,10 @@ func performUpdate(ctx context.Context) error {
// try to save the highest version of our files.
highestVersion := upgradables[len(upgradables)-1]
if err := setCacheDatabaseVersion(highestVersion.Version()); err != nil {
if err := setCacheDatabaseVersion(highestVersion.Version); err != nil {
log.Errorf("intel/filterlists: failed to save cache database version: %s", err)
} else {
log.Infof("intel/filterlists: successfully migrated cache database to %s", highestVersion.Version())
log.Infof("intel/filterlists: successfully migrated cache database to %s", highestVersion.Version)
}
// The list update succeeded, resolve any states.
@@ -174,51 +174,51 @@ func removeAllObsoleteFilterEntries(wc *mgr.WorkerCtx) error {
// getUpgradableFiles returns a slice of filterlists files
// that should be updated. The files MUST be updated and
// processed in the returned order!
func getUpgradableFiles() ([]*updates.File, error) {
var updateOrder []*updates.File
func getUpgradableFiles() ([]*updates.Artifact, error) {
var updateOrder []*updates.Artifact
// cacheDBInUse := isLoaded()
cacheDBInUse := isLoaded()
// if baseFile == nil || !cacheDBInUse { // TODO(vladimir): || baseFile.UpgradeAvailable()
// var err error
// baseFile, err = module.instance.Updates().GetFile(baseListFilePath)
// if err != nil {
// return nil, err
// }
// log.Tracef("intel/filterlists: base file needs update, selected version %s", baseFile.Version())
// updateOrder = append(updateOrder, baseFile)
// }
newBaseFile, err := module.instance.IntelUpdates().GetFile(baseListFilePath)
if err != nil {
log.Warningf("intel/filterlists: failed to get base update: %s", err)
} else if newer, _ := newBaseFile.IsNewerThan(baseFile); newer || !cacheDBInUse {
log.Tracef("intel/filterlists: base file needs update to version %s", newBaseFile.Version)
if newBaseFile.SemVer() == nil {
log.Warningf("intel/filterlists: base file needs update to version %s, but semver is invalid", newBaseFile.Version)
} else {
updateOrder = append(updateOrder, newBaseFile)
}
}
// if intermediateFile == nil || intermediateFile.UpgradeAvailable() || !cacheDBInUse {
// var err error
// intermediateFile, err = getFile(intermediateListFilePath)
// if err != nil && !errors.Is(err, updater.ErrNotFound) {
// return nil, err
// }
newIntermediateFile, err := module.instance.IntelUpdates().GetFile(intermediateListFilePath)
if err != nil {
log.Warningf("intel/filterlists: failed to get intermediate update: %s", err)
} else if newer, _ := newIntermediateFile.IsNewerThan(intermediateFile); newer || !cacheDBInUse {
log.Tracef("intel/filterlists: intermediate file needs update to version %s", newIntermediateFile.Version)
if newIntermediateFile.SemVer() == nil {
log.Warningf("intel/filterlists: intermediate file needs update to version %s, but semver is invalid", newIntermediateFile.Version)
} else {
updateOrder = append(updateOrder, newIntermediateFile)
}
}
// if err == nil {
// log.Tracef("intel/filterlists: intermediate file needs update, selected version %s", intermediateFile.Version())
// updateOrder = append(updateOrder, intermediateFile)
// }
// }
// if urgentFile == nil || urgentFile.UpgradeAvailable() || !cacheDBInUse {
// var err error
// urgentFile, err = getFile(urgentListFilePath)
// if err != nil && !errors.Is(err, updater.ErrNotFound) {
// return nil, err
// }
// if err == nil {
// log.Tracef("intel/filterlists: urgent file needs update, selected version %s", urgentFile.Version())
// updateOrder = append(updateOrder, urgentFile)
// }
// }
newUrgentFile, err := module.instance.IntelUpdates().GetFile(urgentListFilePath)
if err != nil {
log.Warningf("intel/filterlists: failed to get urgent update: %s", err)
} else if newer, _ := newUrgentFile.IsNewerThan(urgentFile); newer || !cacheDBInUse {
log.Tracef("intel/filterlists: urgent file needs update to version %s", newUrgentFile.Version)
if newUrgentFile.SemVer() == nil {
log.Warningf("intel/filterlists: urgent file needs update to version %s, but semver is invalid", newUrgentFile.Version)
} else {
updateOrder = append(updateOrder, newUrgentFile)
}
}
return resolveUpdateOrder(updateOrder)
}
func resolveUpdateOrder(updateOrder []*updates.File) ([]*updates.File, error) {
func resolveUpdateOrder(updateOrder []*updates.Artifact) ([]*updates.Artifact, error) {
// sort the update order by ascending version
sort.Sort(byAscVersion(updateOrder))
log.Tracef("intel/filterlists: order of updates: %v", updateOrder)
@@ -239,9 +239,8 @@ func resolveUpdateOrder(updateOrder []*updates.File) ([]*updates.File, error) {
startAtIdx := -1
for idx, file := range updateOrder {
ver, _ := version.NewSemver(file.Version())
log.Tracef("intel/filterlists: checking file with version %s against %s", ver, cacheDBVersion)
if ver.GreaterThan(cacheDBVersion) && (startAtIdx == -1 || file == baseFile) {
log.Tracef("intel/filterlists: checking file with version %s against %s", file.SemVer(), cacheDBVersion)
if file.SemVer().GreaterThan(cacheDBVersion) && (startAtIdx == -1 || file == baseFile) {
startAtIdx = idx
}
}
@@ -258,15 +257,12 @@ func resolveUpdateOrder(updateOrder []*updates.File) ([]*updates.File, error) {
return updateOrder[startAtIdx:], nil
}
type byAscVersion []*updates.File
type byAscVersion []*updates.Artifact
func (fs byAscVersion) Len() int { return len(fs) }
func (fs byAscVersion) Less(i, j int) bool {
vi, _ := version.NewSemver(fs[i].Version())
vj, _ := version.NewSemver(fs[j].Version())
return vi.LessThan(vj)
return fs[i].SemVer().LessThan(fs[j].SemVer())
}
func (fs byAscVersion) Swap(i, j int) {

View File

@@ -17,6 +17,12 @@ var worker *updateWorker
func init() {
worker = &updateWorker{
trigger: make(chan struct{}),
v4: updateBroadcaster{
dbName: v4MMDBResource,
},
v6: updateBroadcaster{
dbName: v6MMDBResource,
},
}
}
@@ -27,26 +33,50 @@ const (
type geoIPDB struct {
*maxminddb.Reader
file *updates.File
update *updates.Artifact
}
// updateBroadcaster stores a geoIPDB and provides synchronized
// access to the MMDB reader. It also supports broadcasting to
// multiple waiters when a new database becomes available.
type updateBroadcaster struct {
rw sync.RWMutex
db *geoIPDB
rw sync.RWMutex
db *geoIPDB
dbName string
waiter chan struct{}
}
// NeedsUpdate returns true if the current broadcaster needs a
// database update.
func (ub *updateBroadcaster) NeedsUpdate() bool {
// AvailableUpdate returns a new update artifact if the current broadcaster
// needs a database update.
func (ub *updateBroadcaster) AvailableUpdate() *updates.Artifact {
ub.rw.RLock()
defer ub.rw.RUnlock()
return ub.db == nil // TODO(vladimir) is this needed: || ub.db.file.UpgradeAvailable()
// Get artifact.
artifact, err := module.instance.IntelUpdates().GetFile(ub.dbName)
if err != nil {
// Check if the geoip database is included in the binary index instead.
// TODO: Remove when intelhub builds the geoip database.
if artifact2, err2 := module.instance.BinaryUpdates().GetFile(ub.dbName); err2 == nil {
artifact = artifact2
err = nil
} else {
log.Warningf("geoip: failed to get geoip update: %s", err)
return nil
}
}
// Return artifact if not yet initialized.
if ub.db == nil {
return artifact
}
// Compare and return artifact only when confirmed newer.
if newer, _ := artifact.IsNewerThan(ub.db.update); newer {
return artifact
}
return nil
}
// ReplaceDatabase replaces (or initially sets) the mmdb database.
@@ -153,16 +183,18 @@ func (upd *updateWorker) start() {
func (upd *updateWorker) run(ctx *mgr.WorkerCtx) error {
for {
if upd.v4.NeedsUpdate() {
if v4, err := getGeoIPDB(v4MMDBResource); err == nil {
update := upd.v4.AvailableUpdate()
if update != nil {
if v4, err := getGeoIPDB(update); err == nil {
upd.v4.ReplaceDatabase(v4)
} else {
log.Warningf("geoip: failed to get v4 database: %s", err)
}
}
if upd.v6.NeedsUpdate() {
if v6, err := getGeoIPDB(v6MMDBResource); err == nil {
update = upd.v6.AvailableUpdate()
if update != nil {
if v6, err := getGeoIPDB(update); err == nil {
upd.v6.ReplaceDatabase(v6)
} else {
log.Warningf("geoip: failed to get v6 database: %s", err)
@@ -177,36 +209,17 @@ func (upd *updateWorker) run(ctx *mgr.WorkerCtx) error {
}
}
func getGeoIPDB(resource string) (*geoIPDB, error) {
log.Debugf("geoip: opening database %s", resource)
func getGeoIPDB(update *updates.Artifact) (*geoIPDB, error) {
log.Debugf("geoip: opening database %s", update.Path())
file, err := open(resource)
if err != nil {
return nil, err
}
reader, err := maxminddb.Open(file.Path())
reader, err := maxminddb.Open(update.Path())
if err != nil {
return nil, fmt.Errorf("failed to open: %w", err)
}
log.Debugf("geoip: successfully opened database %s", resource)
log.Debugf("geoip: successfully opened database %s", update.Filename)
return &geoIPDB{
Reader: reader,
file: file,
update: update,
}, nil
}
func open(resource string) (*updates.File, error) {
f, err := module.instance.IntelUpdates().GetFile(resource)
if err != nil {
return nil, fmt.Errorf("getting file: %w", err)
}
// unpacked, err := f.Unpack(".gz", updater.UnpackGZIP)
// if err != nil {
// return nil, "", fmt.Errorf("unpacking file: %w", err)
// }
return f, nil
}

View File

@@ -66,5 +66,6 @@ func New(instance instance) (*GeoIP, error) {
}
type instance interface {
BinaryUpdates() *updates.Updater
IntelUpdates() *updates.Updater
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
@@ -17,7 +18,6 @@ import (
"zombiezen.com/go/sqlite/sqlitex"
"github.com/safing/portmaster/base/config"
"github.com/safing/portmaster/base/dataroot"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/service/netquery/orm"
"github.com/safing/portmaster/service/network"
@@ -127,13 +127,13 @@ type (
// Note that write connections are serialized by the Database object before being
// handed over to SQLite.
func New(dbPath string) (*Database, error) {
historyParentDir := dataroot.Root().ChildDir("databases", 0o700)
if err := historyParentDir.Ensure(); err != nil {
historyParentDir := filepath.Join(module.instance.DataDir(), "databases")
if err := os.MkdirAll(historyParentDir, 0o0700); err != nil {
return nil, fmt.Errorf("failed to ensure database directory exists: %w", err)
}
// Get file location of history database.
historyFile := filepath.Join(historyParentDir.Path, "history.db")
historyFile := filepath.Join(historyParentDir, "history.db")
// Convert to SQLite URI path.
historyURI := "file:///" + strings.TrimPrefix(filepath.ToSlash(historyFile), "/")
@@ -225,13 +225,13 @@ func (db *Database) Close() error {
// VacuumHistory rewrites the history database in order to purge deleted records.
func VacuumHistory(ctx context.Context) (err error) {
historyParentDir := dataroot.Root().ChildDir("databases", 0o700)
if err := historyParentDir.Ensure(); err != nil {
historyParentDir := filepath.Join(module.instance.DataDir(), "databases")
if err := os.MkdirAll(historyParentDir, 0o0700); err != nil {
return fmt.Errorf("failed to ensure database directory exists: %w", err)
}
// Get file location of history database.
historyFile := filepath.Join(historyParentDir.Path, "history.db")
historyFile := filepath.Join(historyParentDir, "history.db")
// Convert to SQLite URI path.
historyURI := "file:///" + strings.TrimPrefix(filepath.ToSlash(historyFile), "/")

View File

@@ -310,5 +310,6 @@ func NewModule(instance instance) (*NetQuery, error) {
}
type instance interface {
DataDir() string
Profile() *profile.ProfileModule
}

View File

@@ -3,12 +3,13 @@ package profile
import (
"errors"
"fmt"
"os"
"path/filepath"
"sync/atomic"
"github.com/safing/portmaster/base/config"
"github.com/safing/portmaster/base/database"
"github.com/safing/portmaster/base/database/migration"
"github.com/safing/portmaster/base/dataroot"
"github.com/safing/portmaster/base/log"
_ "github.com/safing/portmaster/service/core/base"
"github.com/safing/portmaster/service/mgr"
@@ -65,11 +66,11 @@ func prep() error {
}
// Setup icon storage location.
iconsDir := dataroot.Root().ChildDir("databases", 0o0700).ChildDir("icons", 0o0700)
if err := iconsDir.Ensure(); err != nil {
iconsDir := filepath.Join(module.instance.DataDir(), "databases", "icons")
if err := os.MkdirAll(iconsDir, 0o0700); err != nil {
return fmt.Errorf("failed to create/check icons directory: %w", err)
}
binmeta.ProfileIconStoragePath = iconsDir.Path
binmeta.ProfileIconStoragePath = iconsDir
return nil
}
@@ -151,5 +152,6 @@ func NewModule(instance instance) (*ProfileModule, error) {
}
type instance interface {
DataDir() string
Config() *config.Config
}

View File

@@ -197,7 +197,7 @@ func (profile *Profile) parseConfig() error {
if ok {
profile.filterListIDs, err = filterlists.ResolveListIDs(list)
if err != nil {
lastErr = err
log.Warningf("profiles: failed to resolve filter list IDs: %s", err)
} else {
profile.filterListsSet = true
}

View File

@@ -2,10 +2,11 @@ package ui
import (
"errors"
"os"
"path/filepath"
"sync/atomic"
"github.com/safing/portmaster/base/api"
"github.com/safing/portmaster/base/dataroot"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/service/updates"
@@ -28,7 +29,8 @@ func start() error {
// may seem dangerous, but proper permission on the parent directory provide
// (some) protection.
// Processes must _never_ read from this directory.
err := dataroot.Root().ChildDir("exec", 0o0777).Ensure()
execDir := filepath.Join(module.instance.DataDir(), "exec")
err := os.MkdirAll(execDir, 0o0777) //nolint:gosec // This is intentional.
if err != nil {
log.Warningf("ui: failed to create safe exec dir: %s", err)
}
@@ -81,6 +83,7 @@ func New(instance instance) (*UI, error) {
}
type instance interface {
DataDir() string
API() *api.API
BinaryUpdates() *updates.Updater
}

106
service/updates.go Normal file
View File

@@ -0,0 +1,106 @@
package service
import (
"path/filepath"
go_runtime "runtime"
"github.com/safing/jess"
"github.com/safing/portmaster/service/updates"
)
var (
DefaultBinaryIndexURLs = []string{
"https://updates.safing.io/stable.v3.json",
}
DefaultIntelIndexURLs = []string{
"https://updates.safing.io/intel.v3.json",
}
// BinarySigningKeys holds the signing keys in text format.
BinarySigningKeys = []string{
// Safing Code Signing Key #1
"recipient:public-ed25519-key:safing-code-signing-key-1:92bgBLneQUWrhYLPpBDjqHbpFPuNVCPAaivQ951A4aq72HcTiw7R1QmPJwFM1mdePAvEVDjkeb8S4fp2pmRCsRa8HrCvWQEjd88rfZ6TznJMfY4g7P8ioGFjfpyx2ZJ8WCZJG5Qt4Z9nkabhxo2Nbi3iywBTYDLSbP5CXqi7jryW7BufWWuaRVufFFzhwUC2ryWFWMdkUmsAZcvXwde4KLN9FrkWAy61fGaJ8GCwGnGCSitANnU2cQrsGBXZzxmzxwrYD",
// Safing Code Signing Key #2
"recipient:public-ed25519-key:safing-code-signing-key-2:92bgBLneQUWrhYLPpBDjqHbPC2d1o5JMyZFdavWBNVtdvbPfzDewLW95ScXfYPHd3QvWHSWCtB4xpthaYWxSkK1kYiGp68DPa2HaU8yQ5dZhaAUuV4Kzv42pJcWkCeVnBYqgGBXobuz52rFqhDJy3rz7soXEmYhJEJWwLwMeioK3VzN3QmGSYXXjosHMMNC76rjufSoLNtUQUWZDSnHmqbuxbKMCCsjFXUGGhtZVyb7bnu7QLTLk6SKHBJDMB6zdL9sw3",
}
// BinarySigningTrustStore is an in-memory trust store with the signing keys.
BinarySigningTrustStore = jess.NewMemTrustStore()
)
func init() {
for _, signingKey := range BinarySigningKeys {
rcpt, err := jess.RecipientFromTextFormat(signingKey)
if err != nil {
panic(err)
}
err = BinarySigningTrustStore.StoreSignet(rcpt)
if err != nil {
panic(err)
}
}
}
func MakeUpdateConfigs(svcCfg *ServiceConfig) (binaryUpdateConfig, intelUpdateConfig *updates.Config, err error) {
switch go_runtime.GOOS {
case "windows":
binaryUpdateConfig = &updates.Config{
Name: "binaries",
Directory: svcCfg.BinDir,
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"),
PurgeDirectory: filepath.Join(svcCfg.BinDir, "upgrade_obsolete_binaries"),
Ignore: []string{"databases", "intel", "config.json"},
IndexURLs: svcCfg.BinariesIndexURLs,
IndexFile: "index.json",
Verify: svcCfg.VerifyBinaryUpdates,
AutoDownload: false,
AutoApply: false,
NeedsRestart: true,
Notify: true,
}
intelUpdateConfig = &updates.Config{
Name: "intel",
Directory: filepath.Join(svcCfg.DataDir, "intel"),
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"),
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"),
IndexURLs: svcCfg.IntelIndexURLs,
IndexFile: "index.json",
Verify: svcCfg.VerifyIntelUpdates,
AutoDownload: true,
AutoApply: true,
NeedsRestart: false,
Notify: false,
}
case "linux":
binaryUpdateConfig = &updates.Config{
Name: "binaries",
Directory: svcCfg.BinDir,
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"),
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_binaries"),
Ignore: []string{"databases", "intel", "config.json"},
IndexURLs: svcCfg.BinariesIndexURLs,
IndexFile: "index.json",
Verify: svcCfg.VerifyBinaryUpdates,
AutoDownload: false,
AutoApply: false,
NeedsRestart: true,
Notify: true,
}
intelUpdateConfig = &updates.Config{
Name: "intel",
Directory: filepath.Join(svcCfg.DataDir, "intel"),
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"),
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"),
IndexURLs: svcCfg.IntelIndexURLs,
IndexFile: "index.json",
Verify: svcCfg.VerifyIntelUpdates,
AutoDownload: true,
AutoApply: true,
NeedsRestart: false,
Notify: false,
}
}
return
}

View File

@@ -55,7 +55,7 @@ func (d *Downloader) updateIndex(ctx context.Context) error {
break
}
log.Warningf("updates: failed to update index from %q: %s", url, err)
log.Warningf("updates/%s: failed to update index from %q: %s", d.u.cfg.Name, url, err)
err = fmt.Errorf("update index file from %q: %s", url, err)
}
if err != nil {
@@ -111,7 +111,7 @@ func (d *Downloader) gatherExistingFiles(dir string) error {
// Read full file.
fileData, err := os.ReadFile(fullpath)
if err != nil {
log.Debugf("updates: failed to read file %q while searching for existing files: %w", fullpath, err)
log.Debugf("updates/%s: failed to read file %q while searching for existing files: %w", d.u.cfg.Name, fullpath, err)
return fmt.Errorf("failed to read file %s: %w", fullpath, err)
}
@@ -150,7 +150,7 @@ artifacts:
if err == nil {
continue artifacts
}
log.Debugf("updates: failed to copy existing file %s: %w", artifact.Filename, err)
log.Debugf("updates/%s: failed to copy existing file %s: %w", d.u.cfg.Name, artifact.Filename, err)
}
// Try to download the artifact from one of the URLs.
@@ -182,7 +182,7 @@ artifacts:
return fmt.Errorf("rename %s after write: %w", artifact.Filename, err)
}
log.Infof("updates: downloaded and verified %s", artifact.Filename)
log.Infof("updates/%s: downloaded and verified %s", d.u.cfg.Name, artifact.Filename)
}
return nil
}

View File

@@ -35,7 +35,8 @@ type Artifact struct {
Unpack string `json:"Unpack,omitempty"`
Version string `json:"Version,omitempty"`
localFile string
localFile string
versionNum *semver.Version
}
// GetFileMode returns the required filesystem permission for the artifact.
@@ -52,6 +53,67 @@ func (a *Artifact) GetFileMode() os.FileMode {
return defaultFileMode
}
// Path returns the absolute path to the local file.
func (a *Artifact) Path() string {
return a.localFile
}
// SemVer returns the version of the artifact.
func (a *Artifact) SemVer() *semver.Version {
return a.versionNum
}
// IsNewerThan returns whether the artifact is newer than the given artifact.
// Returns true if the given artifact is nil.
// The second return value "ok" is false when version could not be compared.
// In this case, it is up to the caller to decide how to proceed.
func (a *Artifact) IsNewerThan(b *Artifact) (newer, ok bool) {
switch {
case a == nil:
return false, false
case b == nil:
return true, true
case a.versionNum == nil:
return false, false
case b.versionNum == nil:
return false, false
case a.versionNum.GreaterThan(b.versionNum):
return true, true
default:
return false, true
}
}
func (a *Artifact) export(dir string, indexVersion *semver.Version) *Artifact {
copy := &Artifact{
Filename: a.Filename,
SHA256: a.SHA256,
URLs: a.URLs,
Platform: a.Platform,
Unpack: a.Unpack,
Version: a.Version,
localFile: filepath.Join(dir, a.Filename),
versionNum: a.versionNum,
}
// Make sure we have a version number.
switch {
case copy.versionNum != nil:
// Version already parsed.
case copy.Version != "":
// Need to parse version.
v, err := semver.NewVersion(copy.Version)
if err == nil {
copy.versionNum = v
}
default:
// No version defined, inherit index version.
copy.versionNum = indexVersion
}
return copy
}
// Index represents a collection of artifacts with metadata.
type Index struct {
Name string `json:"Name"`
@@ -90,16 +152,26 @@ func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error)
return nil, fmt.Errorf("parse index: %w", err)
}
// Initialize data.
err = index.init()
if err != nil {
return nil, err
}
return &index, nil
}
func (index *Index) init() error {
// Parse version number, if set.
if index.Version != "" {
versionNum, err := semver.NewVersion(index.Version)
if err != nil {
return nil, fmt.Errorf("invalid index version %q: %w", index.Version, err)
return fmt.Errorf("invalid index version %q: %w", index.Version, err)
}
index.versionNum = versionNum
}
// Filter artifacts by currnet platform.
// Filter artifacts by current platform.
filtered := make([]Artifact, 0)
for _, a := range index.Artifacts {
if a.Platform == "" || a.Platform == currentPlatform {
@@ -108,7 +180,19 @@ func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error)
}
index.Artifacts = filtered
return &index, nil
// Parse artifact version numbers.
for _, a := range index.Artifacts {
if a.Version != "" {
v, err := semver.NewVersion(a.Version)
if err == nil {
a.versionNum = v
}
} else {
a.versionNum = index.versionNum
}
}
return nil
}
// CanDoUpgrades returns whether the index is able to follow a secure upgrade path.

View File

@@ -45,6 +45,8 @@ var (
// Config holds the configuration for the updates module.
type Config struct {
// Name of the updater.
Name string
// Directory is the main directory where the currently to-be-used artifacts live.
Directory string
// DownloadDirectory is the directory where new artifacts are downloaded to and prepared for upgrading.
@@ -80,6 +82,8 @@ type Config struct {
func (cfg *Config) Check() error {
// Check if required fields are set.
switch {
case cfg.Name == "":
return errors.New("name must be set")
case cfg.Directory == "":
return errors.New("directory must be set")
case cfg.DownloadDirectory == "":
@@ -157,19 +161,22 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
// Load index.
index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Verify)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
log.Errorf("updates: invalid index file, falling back to dir scan: %w", err)
}
// Fall back to scanning the directory.
index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"})
if err != nil {
return nil, fmt.Errorf("updates index load and dir scan failed: %w", err)
}
if err == nil {
module.index = index
return module, nil
}
module.index = index
// Fall back to scanning the directory.
if !errors.Is(err, os.ErrNotExist) {
log.Errorf("updates/%s: invalid index file, falling back to dir scan: %w", cfg.Name, err)
}
index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"})
if err == nil && index.init() == nil {
module.index = index
return module, nil
}
// Fall back to empty index.
return module, nil
}
@@ -207,7 +214,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
u.indexLock.Unlock()
// Check with local pointer to index.
if err := index.ShouldUpgradeTo(downloader.index); err != nil {
log.Infof("updates: no new or eligible update: %s", err)
log.Infof("updates/%s: no new or eligible update: %s", u.cfg.Name, err)
if u.cfg.Notify && u.instance.Notifications() != nil {
u.instance.Notifications().NotifyInfo(
noNewUpdateNotificationID,
@@ -247,12 +254,12 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
// Download any remaining needed files.
// If everything is already found in the download directory, then this is a no-op.
log.Infof("updates: downloading new version: %s %s", downloader.index.Name, downloader.index.Version)
log.Infof("updates/%s: downloading new version: %s %s", u.cfg.Name, downloader.index.Name, downloader.index.Version)
err = downloader.downloadArtifacts(w.Ctx())
if err != nil {
log.Errorf("updates: failed to download update: %s", err)
log.Errorf("updates/%s: failed to download update: %s", u.cfg.Name, err)
if err := u.deleteUnfinishedFiles(u.cfg.DownloadDirectory); err != nil {
log.Debugf("updates: failed to delete unfinished files in download directory %s", u.cfg.DownloadDirectory)
log.Debugf("updates/%s: failed to delete unfinished files in download directory %s", u.cfg.Name, u.cfg.DownloadDirectory)
}
return fmt.Errorf("downloading failed: %w", err)
}
@@ -282,7 +289,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
err = u.upgrade(downloader, ignoreVersion)
if err != nil {
if err := u.deleteUnfinishedFiles(u.cfg.PurgeDirectory); err != nil {
log.Debugf("updates: failed to delete unfinished files in purge directory %s", u.cfg.PurgeDirectory)
log.Debugf("updates/%s: failed to delete unfinished files in purge directory %s", u.cfg.Name, u.cfg.PurgeDirectory)
}
return err
}
@@ -334,6 +341,14 @@ func (u *Updater) upgradeWorker(w *mgr.WorkerCtx) error {
return nil
}
// ForceUpdate executes a forced update and upgrade directly and synchronously
// and is intended to be used only within a tool, not a service.
func (u *Updater) ForceUpdate() error {
return u.m.Do("update and upgrade", func(w *mgr.WorkerCtx) error {
return u.updateAndUpgrade(w, u.cfg.IndexURLs, true, true)
})
}
// UpdateFromURL installs an update from the provided url.
func (u *Updater) UpdateFromURL(url string) error {
u.m.Go("custom update from url", func(w *mgr.WorkerCtx) error {
@@ -383,10 +398,15 @@ func (u *Updater) GetMainDir() string {
}
// GetFile returns the path of a file given the name. Returns ErrNotFound if file is not found.
func (u *Updater) GetFile(name string) (string, error) {
func (u *Updater) GetFile(name string) (*Artifact, error) {
u.indexLock.Lock()
defer u.indexLock.Unlock()
// Check if any index is active.
if u.index == nil {
return nil, ErrNotFound
}
for _, artifact := range u.index.Artifacts {
switch {
case artifact.Filename != name:
@@ -396,11 +416,11 @@ func (u *Updater) GetFile(name string) (string, error) {
// Platforms are usually pre-filtered, but just to be sure.
default:
// Artifact matches!
return filepath.Join(u.cfg.Directory, artifact.Filename), nil
return artifact.export(u.cfg.Directory, u.index.versionNum), nil
}
}
return "", ErrNotFound
return nil, ErrNotFound
}
// Stop stops the module.

View File

@@ -43,7 +43,7 @@ func (u *Updater) upgrade(downloader *Downloader, ignoreVersion bool) error {
}
// Recovery failed too.
return fmt.Errorf("upgrade (including recovery) failed: %s", upgradeError)
return fmt.Errorf("upgrade (including recovery) failed: %s", u.cfg.Name, upgradeError)
}
func (u *Updater) upgradeMoveFiles(downloader *Downloader, ignoreVersion bool) error {
@@ -60,7 +60,9 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader, ignoreVersion bool) e
}
// Move current version files into purge folder.
log.Debugf("updates: removing the old version (v%s from %s)", u.index.Version, u.index.Published)
if u.index != nil {
log.Debugf("updates/%s: removing the old version (v%s from %s)", u.cfg.Name, u.index.Version, u.index.Published)
}
files, err := os.ReadDir(u.cfg.Directory)
if err != nil {
return fmt.Errorf("read current directory: %w", err)
@@ -74,17 +76,17 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader, ignoreVersion bool) e
// Otherwise, move file to purge dir.
src := filepath.Join(u.cfg.Directory, file.Name())
dst := filepath.Join(u.cfg.PurgeDirectory, file.Name())
err := moveFile(src, dst, "", file.Type().Perm())
err := u.moveFile(src, dst, "", file.Type().Perm())
if err != nil {
return fmt.Errorf("failed to move current file %s to purge dir: %w", file.Name(), err)
}
}
// Move the new index file into main directory.
log.Debugf("updates: installing the new version (v%s from %s)", u.index.Version, u.index.Published)
log.Debugf("updates/%s: installing the new version (v%s from %s)", u.cfg.Name, downloader.index.Version, downloader.index.Published)
src := filepath.Join(u.cfg.DownloadDirectory, u.cfg.IndexFile)
dst := filepath.Join(u.cfg.Directory, u.cfg.IndexFile)
err = moveFile(src, dst, "", defaultFileMode)
err = u.moveFile(src, dst, "", defaultFileMode)
if err != nil {
return fmt.Errorf("failed to move index file to %s: %w", dst, err)
}
@@ -93,30 +95,30 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader, ignoreVersion bool) e
for _, artifact := range downloader.index.Artifacts {
src = filepath.Join(u.cfg.DownloadDirectory, artifact.Filename)
dst = filepath.Join(u.cfg.Directory, artifact.Filename)
err = moveFile(src, dst, artifact.SHA256, artifact.GetFileMode())
err = u.moveFile(src, dst, artifact.SHA256, artifact.GetFileMode())
if err != nil {
return fmt.Errorf("failed to move file %s: %w", artifact.Filename, err)
} else {
log.Debugf("updates: %s moved", artifact.Filename)
log.Debugf("updates/%s: %s moved", u.cfg.Name, artifact.Filename)
}
}
// Set new index on module.
u.index = downloader.index
log.Infof("updates: update complete (v%s from %s)", u.index.Version, u.index.Published)
log.Infof("updates/%s: update complete (v%s from %s)", u.cfg.Name, u.index.Version, u.index.Published)
return nil
}
// moveFile moves a file and falls back to copying if it fails.
func moveFile(currentPath, newPath string, sha256sum string, fileMode fs.FileMode) error {
func (u *Updater) moveFile(currentPath, newPath string, sha256sum string, fileMode fs.FileMode) error {
// Try to simply move file.
err := os.Rename(currentPath, newPath)
if err == nil {
// Moving was successful, return.
return nil
}
log.Tracef("updates: failed to move to %q, falling back to copy+delete: %w", newPath, err)
log.Tracef("updates/%s: failed to move to %q, falling back to copy+delete: %w", u.cfg.Name, newPath, err)
// Copy and check the checksum while we are at it.
err = copyAndCheckSHA256Sum(currentPath, newPath, sha256sum, fileMode)
@@ -139,10 +141,10 @@ func (u *Updater) recoverFromFailedUpgrade() error {
for _, file := range files {
purgedFile := filepath.Join(u.cfg.PurgeDirectory, file.Name())
activeFile := filepath.Join(u.cfg.Directory, file.Name())
err := moveFile(purgedFile, activeFile, "", file.Type().Perm())
err := u.moveFile(purgedFile, activeFile, "", file.Type().Perm())
if err != nil {
// Only warn and continue to recover as many files as possible.
log.Warningf("updates: failed to roll back file %s: %w", file.Name(), err)
log.Warningf("updates/%s: failed to roll back file %s: %w", u.cfg.Name, file.Name(), err)
}
}
@@ -176,18 +178,18 @@ func (u *Updater) deleteUnfinishedFiles(dir string) error {
case strings.HasSuffix(e.Name(), ".download"):
path := filepath.Join(dir, e.Name())
log.Warningf("updates: deleting unfinished download file: %s\n", path)
log.Warningf("updates/%s: deleting unfinished download file: %s", u.cfg.Name, path)
err := os.Remove(path)
if err != nil {
log.Errorf("updates: failed to delete unfinished download file %s: %s", path, err)
log.Errorf("updates/%s: failed to delete unfinished download file %s: %s", u.cfg.Name, path, err)
}
case strings.HasSuffix(e.Name(), ".copy"):
path := filepath.Join(dir, e.Name())
log.Warningf("updates: deleting unfinished copied file: %s\n", path)
log.Warningf("updates/%s: deleting unfinished copied file: %s", u.cfg.Name, path)
err := os.Remove(path)
if err != nil {
log.Errorf("updates: failed to delete unfinished copied file %s: %s", path, err)
log.Errorf("updates/%s: failed to delete unfinished copied file %s: %s", u.cfg.Name, path, err)
}
}
}