Files
portmaster/base/updater/updating.go
2024-09-05 10:25:57 +03:00

360 lines
11 KiB
Go

package updater
// import (
// "context"
// "fmt"
// "net/http"
// "os"
// "path"
// "path/filepath"
// "strings"
// "golang.org/x/exp/slices"
// "github.com/safing/jess/filesig"
// "github.com/safing/jess/lhash"
// "github.com/safing/portmaster/base/log"
// "github.com/safing/portmaster/base/utils"
// )
// // UpdateIndexes downloads all indexes. An error is only returned when all
// // indexes fail to update.
// func (reg *ResourceRegistry) UpdateIndexes(ctx context.Context) error {
// var lastErr error
// var anySuccess bool
// // Start registry operation.
// reg.state.StartOperation(StateChecking)
// defer reg.state.EndOperation()
// client := &http.Client{}
// for _, idx := range reg.getIndexes() {
// if err := reg.downloadIndex(ctx, client, idx); err != nil {
// lastErr = err
// log.Warningf("%s: failed to update index %s: %s", reg.Name, idx.Path, err)
// } else {
// anySuccess = true
// }
// }
// // If all indexes failed to update, fail.
// if !anySuccess {
// err := fmt.Errorf("failed to update all indexes, last error was: %w", lastErr)
// reg.state.ReportUpdateCheck(nil, err)
// return err
// }
// // Get pending resources and update status.
// pendingResourceVersions, _ := reg.GetPendingDownloads(true, false)
// reg.state.ReportUpdateCheck(
// humanInfoFromResourceVersions(pendingResourceVersions),
// nil,
// )
// return nil
// }
// func (reg *ResourceRegistry) downloadIndex(ctx context.Context, client *http.Client, idx *Index) error {
// var (
// // Index.
// indexErr error
// indexData []byte
// downloadURL string
// // Signature.
// sigErr error
// verifiedHash *lhash.LabeledHash
// sigFileData []byte
// verifOpts = reg.GetVerificationOptions(idx.Path)
// )
// // Upgrade to v2 index if verification is enabled.
// downloadIndexPath := idx.Path
// if verifOpts != nil {
// downloadIndexPath = strings.TrimSuffix(downloadIndexPath, baseIndexExtension) + v2IndexExtension
// }
// // Download new index and signature.
// for tries := range 3 {
// // Index and signature need to be fetched together, so that they are
// // fetched from the same source. One source should always have a matching
// // index and signature. Backup sources may be behind a little.
// // If the signature verification fails, another source should be tried.
// // Get index data.
// indexData, downloadURL, indexErr = reg.fetchData(ctx, client, downloadIndexPath, tries)
// if indexErr != nil {
// log.Debugf("%s: failed to fetch index %s: %s", reg.Name, downloadURL, indexErr)
// continue
// }
// // Get signature and verify it.
// if verifOpts != nil {
// verifiedHash, sigFileData, sigErr = reg.fetchAndVerifySigFile(
// ctx, client,
// verifOpts, downloadIndexPath+filesig.Extension, nil,
// tries,
// )
// if sigErr != nil {
// log.Debugf("%s: failed to verify signature of %s: %s", reg.Name, downloadURL, sigErr)
// continue
// }
// // Check if the index matches the verified hash.
// if verifiedHash.Matches(indexData) {
// log.Infof("%s: verified signature of %s", reg.Name, downloadURL)
// } else {
// sigErr = ErrIndexChecksumMismatch
// log.Debugf("%s: checksum does not match file from %s", reg.Name, downloadURL)
// continue
// }
// }
// break
// }
// if indexErr != nil {
// return fmt.Errorf("failed to fetch index %s: %w", downloadIndexPath, indexErr)
// }
// if sigErr != nil {
// return fmt.Errorf("failed to fetch or verify index %s signature: %w", downloadIndexPath, sigErr)
// }
// // Parse the index file.
// indexFile, err := ParseIndexFile(indexData, idx.Channel, idx.LastRelease)
// if err != nil {
// return fmt.Errorf("failed to parse index %s: %w", idx.Path, err)
// }
// // Add index data to registry.
// if len(indexFile.Releases) > 0 {
// // Check if all resources are within the indexes' authority.
// authoritativePath := path.Dir(idx.Path) + "/"
// if authoritativePath == "./" {
// // Fix path for indexes at the storage root.
// authoritativePath = ""
// }
// cleanedData := make(map[string]string, len(indexFile.Releases))
// for key, version := range indexFile.Releases {
// if strings.HasPrefix(key, authoritativePath) {
// cleanedData[key] = version
// } else {
// log.Warningf("%s: index %s oversteps it's authority by defining version for %s", reg.Name, idx.Path, key)
// }
// }
// // add resources to registry
// err = reg.AddResources(cleanedData, idx, false, true, idx.PreRelease)
// if err != nil {
// log.Warningf("%s: failed to add resources: %s", reg.Name, err)
// }
// } else {
// log.Debugf("%s: index %s is empty", reg.Name, idx.Path)
// }
// // Check if dest dir exists.
// indexDir := filepath.FromSlash(path.Dir(idx.Path))
// err = reg.storageDir.EnsureRelPath(indexDir)
// if err != nil {
// log.Warningf("%s: failed to ensure directory for updated index %s: %s", reg.Name, idx.Path, err)
// }
// // Index files must be readable by portmaster-staert with user permissions in order to load the index.
// err = os.WriteFile( //nolint:gosec
// filepath.Join(reg.storageDir.Path, filepath.FromSlash(idx.Path)),
// indexData, 0o0644,
// )
// if err != nil {
// log.Warningf("%s: failed to save updated index %s: %s", reg.Name, idx.Path, err)
// }
// // Write signature file, if we have one.
// if len(sigFileData) > 0 {
// err = os.WriteFile( //nolint:gosec
// filepath.Join(reg.storageDir.Path, filepath.FromSlash(idx.Path)+filesig.Extension),
// sigFileData, 0o0644,
// )
// if err != nil {
// log.Warningf("%s: failed to save updated index signature %s: %s", reg.Name, idx.Path+filesig.Extension, err)
// }
// }
// log.Infof("%s: updated index %s with %d entries", reg.Name, idx.Path, len(indexFile.Releases))
// return nil
// }
// // DownloadUpdates checks if updates are available and downloads updates of used components.
// func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context, includeManual bool) error {
// // Start registry operation.
// reg.state.StartOperation(StateDownloading)
// defer reg.state.EndOperation()
// // Get pending updates.
// toUpdate, missingSigs := reg.GetPendingDownloads(includeManual, true)
// downloadDetailsResources := humanInfoFromResourceVersions(toUpdate)
// reg.state.UpdateOperationDetails(&StateDownloadingDetails{
// Resources: downloadDetailsResources,
// })
// // nothing to update
// if len(toUpdate) == 0 && len(missingSigs) == 0 {
// log.Infof("%s: everything up to date", reg.Name)
// return nil
// }
// // check download dir
// if err := reg.tmpDir.Ensure(); err != nil {
// return fmt.Errorf("could not prepare tmp directory for download: %w", err)
// }
// // download updates
// log.Infof("%s: starting to download %d updates", reg.Name, len(toUpdate))
// client := &http.Client{}
// var reportError error
// for i, rv := range toUpdate {
// log.Infof(
// "%s: downloading update [%d/%d]: %s version %s",
// reg.Name,
// i+1, len(toUpdate),
// rv.resource.Identifier, rv.VersionNumber,
// )
// var err error
// for tries := range 3 {
// err = reg.fetchFile(ctx, client, rv, tries)
// if err == nil {
// // Update resource version state.
// rv.resource.Lock()
// rv.Available = true
// if rv.resource.VerificationOptions != nil {
// rv.SigAvailable = true
// }
// rv.resource.Unlock()
// break
// }
// }
// if err != nil {
// reportError := fmt.Errorf("failed to download %s version %s: %w", rv.resource.Identifier, rv.VersionNumber, err)
// log.Warningf("%s: %s", reg.Name, reportError)
// }
// reg.state.UpdateOperationDetails(&StateDownloadingDetails{
// Resources: downloadDetailsResources,
// FinishedUpTo: i + 1,
// })
// }
// if len(missingSigs) > 0 {
// log.Infof("%s: downloading %d missing signatures", reg.Name, len(missingSigs))
// for _, rv := range missingSigs {
// var err error
// for tries := range 3 {
// err = reg.fetchMissingSig(ctx, client, rv, tries)
// if err == nil {
// // Update resource version state.
// rv.resource.Lock()
// rv.SigAvailable = true
// rv.resource.Unlock()
// break
// }
// }
// if err != nil {
// reportError := fmt.Errorf("failed to download missing sig of %s version %s: %w", rv.resource.Identifier, rv.VersionNumber, err)
// log.Warningf("%s: %s", reg.Name, reportError)
// }
// }
// }
// reg.state.ReportDownloads(
// downloadDetailsResources,
// reportError,
// )
// log.Infof("%s: finished downloading updates", reg.Name)
// return nil
// }
// // DownloadUpdates checks if updates are available and downloads updates of used components.
// // GetPendingDownloads returns the list of pending downloads.
// // If manual is set, indexes with AutoDownload=false will be checked.
// // If auto is set, indexes with AutoDownload=true will be checked.
// func (reg *ResourceRegistry) GetPendingDownloads(manual, auto bool) (resources, sigs []*ResourceVersion) {
// reg.RLock()
// defer reg.RUnlock()
// // create list of downloads
// var toUpdate []*ResourceVersion
// var missingSigs []*ResourceVersion
// for _, res := range reg.resources {
// func() {
// res.Lock()
// defer res.Unlock()
// // Skip resources without index or indexes that should not be reported
// // according to parameters.
// switch {
// case res.Index == nil:
// // Cannot download if resource is not part of an index.
// return
// case manual && !res.Index.AutoDownload:
// // Manual update report and index is not auto-download.
// case auto && res.Index.AutoDownload:
// // Auto update report and index is auto-download.
// default:
// // Resource should not be reported.
// return
// }
// // Skip resources we don't need.
// switch {
// case res.inUse():
// // Update if resource is in use.
// case res.available():
// // Update if resource is available locally, ie. was used in the past.
// case utils.StringInSlice(reg.MandatoryUpdates, res.Identifier):
// // Update is set as mandatory.
// default:
// // Resource does not need to be updated.
// return
// }
// // Go through all versions until we find versions that need updating.
// for _, rv := range res.Versions {
// switch {
// case !rv.CurrentRelease:
// // We are not interested in older releases.
// case !rv.Available:
// // File not available locally, download!
// toUpdate = append(toUpdate, rv)
// case !rv.SigAvailable && res.VerificationOptions != nil:
// // File signature is not available and verification is enabled, download signature!
// missingSigs = append(missingSigs, rv)
// }
// }
// }()
// }
// slices.SortFunc(toUpdate, func(a, b *ResourceVersion) int {
// return strings.Compare(a.resource.Identifier, b.resource.Identifier)
// })
// slices.SortFunc(missingSigs, func(a, b *ResourceVersion) int {
// return strings.Compare(a.resource.Identifier, b.resource.Identifier)
// })
// return toUpdate, missingSigs
// }
// func humanInfoFromResourceVersions(resourceVersions []*ResourceVersion) []string {
// identifiers := make([]string, len(resourceVersions))
// for i, rv := range resourceVersions {
// identifiers[i] = fmt.Sprintf("%s v%s", rv.resource.Identifier, rv.VersionNumber)
// }
// return identifiers
// }