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 }