github.com/safing/portbase@v0.19.5/updater/file.go (about)

     1  package updater
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/fs"
     7  	"os"
     8  	"strings"
     9  
    10  	semver "github.com/hashicorp/go-version"
    11  
    12  	"github.com/safing/jess/filesig"
    13  	"github.com/safing/portbase/log"
    14  	"github.com/safing/portbase/utils"
    15  )
    16  
    17  // File represents a file from the update system.
    18  type File struct {
    19  	resource      *Resource
    20  	version       *ResourceVersion
    21  	notifier      *notifier
    22  	versionedPath string
    23  	storagePath   string
    24  }
    25  
    26  // Identifier returns the identifier of the file.
    27  func (file *File) Identifier() string {
    28  	return file.resource.Identifier
    29  }
    30  
    31  // Version returns the version of the file.
    32  func (file *File) Version() string {
    33  	return file.version.VersionNumber
    34  }
    35  
    36  // SemVer returns the semantic version of the file.
    37  func (file *File) SemVer() *semver.Version {
    38  	return file.version.semVer
    39  }
    40  
    41  // EqualsVersion normalizes the given version and checks equality with semver.
    42  func (file *File) EqualsVersion(version string) bool {
    43  	return file.version.EqualsVersion(version)
    44  }
    45  
    46  // Path returns the absolute filepath of the file.
    47  func (file *File) Path() string {
    48  	return file.storagePath
    49  }
    50  
    51  // SigningMetadata returns the metadata to be included in signatures.
    52  func (file *File) SigningMetadata() map[string]string {
    53  	return map[string]string{
    54  		"id":      file.Identifier(),
    55  		"version": file.Version(),
    56  	}
    57  }
    58  
    59  // Verify verifies the given file.
    60  func (file *File) Verify() ([]*filesig.FileData, error) {
    61  	// Check if verification is configured.
    62  	if file.resource.VerificationOptions == nil {
    63  		return nil, ErrVerificationNotConfigured
    64  	}
    65  
    66  	// Verify file.
    67  	fileData, err := filesig.VerifyFile(
    68  		file.storagePath,
    69  		file.storagePath+filesig.Extension,
    70  		file.SigningMetadata(),
    71  		file.resource.VerificationOptions.TrustStore,
    72  	)
    73  	if err != nil {
    74  		switch file.resource.VerificationOptions.DiskLoadPolicy {
    75  		case SignaturePolicyRequire:
    76  			return nil, err
    77  		case SignaturePolicyWarn:
    78  			log.Warningf("%s: failed to verify %s: %s", file.resource.registry.Name, file.storagePath, err)
    79  		case SignaturePolicyDisable:
    80  			log.Debugf("%s: failed to verify %s: %s", file.resource.registry.Name, file.storagePath, err)
    81  		}
    82  	}
    83  
    84  	return fileData, nil
    85  }
    86  
    87  // Blacklist notifies the update system that this file is somehow broken, and should be ignored from now on, until restarted.
    88  func (file *File) Blacklist() error {
    89  	return file.resource.Blacklist(file.version.VersionNumber)
    90  }
    91  
    92  // markActiveWithLocking marks the file as active, locking the resource in the process.
    93  func (file *File) markActiveWithLocking() {
    94  	file.resource.Lock()
    95  	defer file.resource.Unlock()
    96  
    97  	// update last used version
    98  	if file.resource.ActiveVersion != file.version {
    99  		log.Debugf("updater: setting active version of resource %s from %s to %s", file.resource.Identifier, file.resource.ActiveVersion, file.version.VersionNumber)
   100  		file.resource.ActiveVersion = file.version
   101  	}
   102  }
   103  
   104  // Unpacker describes the function that is passed to
   105  // File.Unpack. It receives a reader to the compressed/packed
   106  // file and should return a reader that provides
   107  // unpacked file contents. If the returned reader implements
   108  // io.Closer it's close method is invoked when an error
   109  // or io.EOF is returned from Read().
   110  type Unpacker func(io.Reader) (io.Reader, error)
   111  
   112  // Unpack returns the path to the unpacked version of file and
   113  // unpacks it on demand using unpacker.
   114  func (file *File) Unpack(suffix string, unpacker Unpacker) (string, error) {
   115  	path := strings.TrimSuffix(file.Path(), suffix)
   116  
   117  	if suffix == "" {
   118  		path += "-unpacked"
   119  	}
   120  
   121  	_, err := os.Stat(path)
   122  	if err == nil {
   123  		return path, nil
   124  	}
   125  
   126  	if !errors.Is(err, fs.ErrNotExist) {
   127  		return "", err
   128  	}
   129  
   130  	f, err := os.Open(file.Path())
   131  	if err != nil {
   132  		return "", err
   133  	}
   134  	defer func() {
   135  		_ = f.Close()
   136  	}()
   137  
   138  	r, err := unpacker(f)
   139  	if err != nil {
   140  		return "", err
   141  	}
   142  
   143  	ioErr := utils.CreateAtomic(path, r, &utils.AtomicFileOptions{
   144  		TempDir: file.resource.registry.TmpDir().Path,
   145  	})
   146  
   147  	if c, ok := r.(io.Closer); ok {
   148  		if err := c.Close(); err != nil && ioErr == nil {
   149  			// if ioErr is already set we ignore the error from
   150  			// closing the unpacker.
   151  			ioErr = err
   152  		}
   153  	}
   154  
   155  	return path, ioErr
   156  }