1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package sif
import (
"encoding/binary"
"errors"
"fmt"
"io"
"os"
)
var (
errInvalidMagic = errors.New("invalid SIF magic")
errIncompatibleVersion = errors.New("incompatible SIF version")
)
// isValidSif looks at key fields from the global header to assess SIF validity.
func isValidSif(f *FileImage) error {
if f.h.Magic != hdrMagic {
return errInvalidMagic
}
if f.h.Version != CurrentVersion.bytes() {
return errIncompatibleVersion
}
return nil
}
// populateMinIDs populates the minIDs field of f.
func (f *FileImage) populateMinIDs() {
f.minIDs = make(map[uint32]uint32)
f.WithDescriptors(func(d Descriptor) bool {
if minID, ok := f.minIDs[d.raw.GroupID]; !ok || d.ID() < minID {
f.minIDs[d.raw.GroupID] = d.ID()
}
return false
})
}
// loadContainer loads a SIF image from rw.
func loadContainer(rw ReadWriter) (*FileImage, error) {
f := FileImage{rw: rw}
// Read global header.
err := binary.Read(
io.NewSectionReader(rw, 0, int64(binary.Size(f.h))),
binary.LittleEndian,
&f.h,
)
if err != nil {
return nil, fmt.Errorf("reading global header: %w", err)
}
if err := isValidSif(&f); err != nil {
return nil, err
}
// Read descriptors.
f.rds = make([]rawDescriptor, f.h.DescriptorsTotal)
err = binary.Read(
io.NewSectionReader(rw, f.h.DescriptorsOffset, f.h.DescriptorsSize),
binary.LittleEndian,
&f.rds,
)
if err != nil {
return nil, fmt.Errorf("reading descriptors: %w", err)
}
f.populateMinIDs()
return &f, nil
}
// loadOpts accumulates container loading options.
type loadOpts struct {
flag int
closeOnUnload bool
}
// LoadOpt are used to specify container loading options.
type LoadOpt func(*loadOpts) error
// OptLoadWithFlag specifies flag (os.O_RDONLY etc.) to be used when opening the container file.
func OptLoadWithFlag(flag int) LoadOpt {
return func(lo *loadOpts) error {
lo.flag = flag
return nil
}
}
// OptLoadWithCloseOnUnload specifies whether the ReadWriter should be closed by UnloadContainer.
// By default, the ReadWriter will be closed if it implements the io.Closer interface.
func OptLoadWithCloseOnUnload(b bool) LoadOpt {
return func(lo *loadOpts) error {
lo.closeOnUnload = b
return nil
}
}
// LoadContainerFromPath loads a new SIF container from path, according to opts.
//
// On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources
// are released.
//
// By default, the file is opened for read and write access. To change this behavior, consider
// using OptLoadWithFlag.
func LoadContainerFromPath(path string, opts ...LoadOpt) (*FileImage, error) {
lo := loadOpts{
flag: os.O_RDWR,
}
for _, opt := range opts {
if err := opt(&lo); err != nil {
return nil, fmt.Errorf("%w", err)
}
}
fp, err := os.OpenFile(path, lo.flag, 0)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
f, err := loadContainer(fp)
if err != nil {
fp.Close()
return nil, fmt.Errorf("%w", err)
}
f.closeOnUnload = true
return f, nil
}
// LoadContainer loads a new SIF container from rw, according to opts.
//
// On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources
// are released. By default, UnloadContainer will close rw if it implements the io.Closer
// interface. To change this behavior, consider using OptLoadWithCloseOnUnload.
func LoadContainer(rw ReadWriter, opts ...LoadOpt) (*FileImage, error) {
lo := loadOpts{
closeOnUnload: true,
}
for _, opt := range opts {
if err := opt(&lo); err != nil {
return nil, fmt.Errorf("%w", err)
}
}
f, err := loadContainer(rw)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
f.closeOnUnload = lo.closeOnUnload
return f, nil
}
// UnloadContainer unloads f, releasing associated resources.
func (f *FileImage) UnloadContainer() error {
if c, ok := f.rw.(io.Closer); ok && f.closeOnUnload {
if err := c.Close(); err != nil {
return fmt.Errorf("%w", err)
}
}
return nil
}
|