mirror of
https://github.com/containers/skopeo.git
synced 2025-09-22 02:18:41 +00:00
prompt-less signing via passphrase file
To support signing images without prompting the user, add CLI flags for providing a passphrase file. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
29
vendor/github.com/sylabs/sif/v2/LICENSE.md
generated
vendored
Normal file
29
vendor/github.com/sylabs/sif/v2/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# LICENSE
|
||||
|
||||
Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
69
vendor/github.com/sylabs/sif/v2/pkg/sif/arch.go
generated
vendored
Normal file
69
vendor/github.com/sylabs/sif/v2/pkg/sif/arch.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2021, Sylabs Inc. 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
|
||||
|
||||
var (
|
||||
hdrArchUnknown archType = [...]byte{'0', '0', '\x00'}
|
||||
hdrArch386 archType = [...]byte{'0', '1', '\x00'}
|
||||
hdrArchAMD64 archType = [...]byte{'0', '2', '\x00'}
|
||||
hdrArchARM archType = [...]byte{'0', '3', '\x00'}
|
||||
hdrArchARM64 archType = [...]byte{'0', '4', '\x00'}
|
||||
hdrArchPPC64 archType = [...]byte{'0', '5', '\x00'}
|
||||
hdrArchPPC64le archType = [...]byte{'0', '6', '\x00'}
|
||||
hdrArchMIPS archType = [...]byte{'0', '7', '\x00'}
|
||||
hdrArchMIPSle archType = [...]byte{'0', '8', '\x00'}
|
||||
hdrArchMIPS64 archType = [...]byte{'0', '9', '\x00'}
|
||||
hdrArchMIPS64le archType = [...]byte{'1', '0', '\x00'}
|
||||
hdrArchS390x archType = [...]byte{'1', '1', '\x00'}
|
||||
)
|
||||
|
||||
type archType [3]byte
|
||||
|
||||
// getSIFArch returns the archType corresponding to go runtime arch.
|
||||
func getSIFArch(arch string) archType {
|
||||
archMap := map[string]archType{
|
||||
"386": hdrArch386,
|
||||
"amd64": hdrArchAMD64,
|
||||
"arm": hdrArchARM,
|
||||
"arm64": hdrArchARM64,
|
||||
"ppc64": hdrArchPPC64,
|
||||
"ppc64le": hdrArchPPC64le,
|
||||
"mips": hdrArchMIPS,
|
||||
"mipsle": hdrArchMIPSle,
|
||||
"mips64": hdrArchMIPS64,
|
||||
"mips64le": hdrArchMIPS64le,
|
||||
"s390x": hdrArchS390x,
|
||||
}
|
||||
|
||||
t, ok := archMap[arch]
|
||||
if !ok {
|
||||
return hdrArchUnknown
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// GoArch returns the go runtime arch corresponding to t.
|
||||
func (t archType) GoArch() string {
|
||||
archMap := map[archType]string{
|
||||
hdrArch386: "386",
|
||||
hdrArchAMD64: "amd64",
|
||||
hdrArchARM: "arm",
|
||||
hdrArchARM64: "arm64",
|
||||
hdrArchPPC64: "ppc64",
|
||||
hdrArchPPC64le: "ppc64le",
|
||||
hdrArchMIPS: "mips",
|
||||
hdrArchMIPSle: "mipsle",
|
||||
hdrArchMIPS64: "mips64",
|
||||
hdrArchMIPS64le: "mips64le",
|
||||
hdrArchS390x: "s390x",
|
||||
}
|
||||
|
||||
arch, ok := archMap[t]
|
||||
if !ok {
|
||||
arch = "unknown"
|
||||
}
|
||||
return arch
|
||||
}
|
103
vendor/github.com/sylabs/sif/v2/pkg/sif/buffer.go
generated
vendored
Normal file
103
vendor/github.com/sylabs/sif/v2/pkg/sif/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2021, Sylabs Inc. 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 (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// A Buffer is a variable-sized buffer of bytes that implements the sif.ReadWriter interface. The
|
||||
// zero value for Buffer is an empty buffer ready to use.
|
||||
type Buffer struct {
|
||||
buf []byte
|
||||
pos int64
|
||||
}
|
||||
|
||||
// NewBuffer creates and initializes a new Buffer using buf as its initial contents.
|
||||
func NewBuffer(buf []byte) *Buffer {
|
||||
return &Buffer{buf: buf}
|
||||
}
|
||||
|
||||
var errNegativeOffset = errors.New("negative offset")
|
||||
|
||||
// ReadAt implements the io.ReaderAt interface.
|
||||
func (b *Buffer) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
if off < 0 {
|
||||
return 0, errNegativeOffset
|
||||
}
|
||||
|
||||
if off >= int64(len(b.buf)) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n = copy(p, b.buf[off:])
|
||||
if n < len(p) {
|
||||
err = io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
var errNegativePosition = errors.New("negative position")
|
||||
|
||||
// Write implements the io.Writer interface.
|
||||
func (b *Buffer) Write(p []byte) (n int, err error) {
|
||||
if b.pos < 0 {
|
||||
return 0, errNegativePosition
|
||||
}
|
||||
|
||||
if have, need := int64(len(b.buf))-b.pos, int64(len(p)); have < need {
|
||||
b.buf = append(b.buf, make([]byte, need-have)...)
|
||||
}
|
||||
|
||||
n = copy(b.buf[b.pos:], p)
|
||||
b.pos += int64(n)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var errInvalidWhence = errors.New("invalid whence")
|
||||
|
||||
// Seek implements the io.Seeker interface.
|
||||
func (b *Buffer) Seek(offset int64, whence int) (int64, error) {
|
||||
var abs int64
|
||||
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
abs = offset
|
||||
case io.SeekCurrent:
|
||||
abs = b.pos + offset
|
||||
case io.SeekEnd:
|
||||
abs = int64(len(b.buf)) + offset
|
||||
default:
|
||||
return 0, errInvalidWhence
|
||||
}
|
||||
|
||||
if abs < 0 {
|
||||
return 0, errNegativePosition
|
||||
}
|
||||
|
||||
b.pos = abs
|
||||
return abs, nil
|
||||
}
|
||||
|
||||
var errTruncateRange = errors.New("truncation out of range")
|
||||
|
||||
// Truncate discards all but the first n bytes from the buffer.
|
||||
func (b *Buffer) Truncate(n int64) error {
|
||||
if n < 0 || n > int64(len(b.buf)) {
|
||||
return errTruncateRange
|
||||
}
|
||||
|
||||
b.buf = b.buf[:n]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bytes returns the contents of the buffer. The slice is valid for use only until the next buffer
|
||||
// modification (that is, only until the next call to a method like ReadAt, Write, or Truncate).
|
||||
func (b *Buffer) Bytes() []byte { return b.buf }
|
||||
|
||||
// Len returns the number of bytes in the buffer.
|
||||
func (b *Buffer) Len() int64 { return int64(len(b.buf)) }
|
680
vendor/github.com/sylabs/sif/v2/pkg/sif/create.go
generated
vendored
Normal file
680
vendor/github.com/sylabs/sif/v2/pkg/sif/create.go
generated
vendored
Normal file
@@ -0,0 +1,680 @@
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// nextAligned finds the next offset that satisfies alignment.
|
||||
func nextAligned(offset int64, alignment int) int64 {
|
||||
align64 := uint64(alignment)
|
||||
offset64 := uint64(offset)
|
||||
|
||||
if align64 != 0 && offset64%align64 != 0 {
|
||||
offset64 = (offset64 & ^(align64 - 1)) + align64
|
||||
}
|
||||
|
||||
return int64(offset64)
|
||||
}
|
||||
|
||||
// writeDataObjectAt writes the data object described by di to ws, using time t, recording details
|
||||
// in d. The object is written at the first position that satisfies the alignment requirements
|
||||
// described by di following offsetUnaligned.
|
||||
func writeDataObjectAt(ws io.WriteSeeker, offsetUnaligned int64, di DescriptorInput, t time.Time, d *rawDescriptor) error { //nolint:lll
|
||||
offset, err := ws.Seek(nextAligned(offsetUnaligned, di.opts.alignment), io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := io.Copy(ws, di.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := di.fillDescriptor(t, d); err != nil {
|
||||
return err
|
||||
}
|
||||
d.Used = true
|
||||
d.Offset = offset
|
||||
d.Size = n
|
||||
d.SizeWithPadding = offset - offsetUnaligned + n
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
errInsufficientCapacity = errors.New("insufficient descriptor capacity to add data object(s) to image")
|
||||
errPrimaryPartition = errors.New("image already contains a primary partition")
|
||||
)
|
||||
|
||||
// writeDataObject writes the data object described by di to f, using time t, recording details in
|
||||
// the descriptor at index i.
|
||||
func (f *FileImage) writeDataObject(i int, di DescriptorInput, t time.Time) error {
|
||||
if i >= len(f.rds) {
|
||||
return errInsufficientCapacity
|
||||
}
|
||||
|
||||
// If this is a primary partition, verify there isn't another primary partition, and update the
|
||||
// architecture in the global header.
|
||||
if p, ok := di.opts.extra.(partition); ok && p.Parttype == PartPrimSys {
|
||||
if ds, err := f.GetDescriptors(WithPartitionType(PartPrimSys)); err == nil && len(ds) > 0 {
|
||||
return errPrimaryPartition
|
||||
}
|
||||
|
||||
f.h.Arch = p.Arch
|
||||
}
|
||||
|
||||
d := &f.rds[i]
|
||||
d.ID = uint32(i) + 1
|
||||
|
||||
if err := writeDataObjectAt(f.rw, f.h.DataOffset+f.h.DataSize, di, t, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update minimum object ID map.
|
||||
if minID, ok := f.minIDs[d.GroupID]; !ok || d.ID < minID {
|
||||
f.minIDs[d.GroupID] = d.ID
|
||||
}
|
||||
|
||||
f.h.DescriptorsFree--
|
||||
f.h.DataSize += d.SizeWithPadding
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeDescriptors writes the descriptors in f to backing storage.
|
||||
func (f *FileImage) writeDescriptors() error {
|
||||
if _, err := f.rw.Seek(f.h.DescriptorsOffset, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binary.Write(f.rw, binary.LittleEndian, f.rds)
|
||||
}
|
||||
|
||||
// writeHeader writes the the global header in f to backing storage.
|
||||
func (f *FileImage) writeHeader() error {
|
||||
if _, err := f.rw.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binary.Write(f.rw, binary.LittleEndian, f.h)
|
||||
}
|
||||
|
||||
// createOpts accumulates container creation options.
|
||||
type createOpts struct {
|
||||
launchScript [hdrLaunchLen]byte
|
||||
id uuid.UUID
|
||||
descriptorsOffset int64
|
||||
descriptorCapacity int64
|
||||
dis []DescriptorInput
|
||||
t time.Time
|
||||
closeOnUnload bool
|
||||
}
|
||||
|
||||
// CreateOpt are used to specify container creation options.
|
||||
type CreateOpt func(*createOpts) error
|
||||
|
||||
var errLaunchScriptLen = errors.New("launch script too large")
|
||||
|
||||
// OptCreateWithLaunchScript specifies s as the launch script.
|
||||
func OptCreateWithLaunchScript(s string) CreateOpt {
|
||||
return func(co *createOpts) error {
|
||||
b := []byte(s)
|
||||
|
||||
if len(b) >= len(co.launchScript) {
|
||||
return errLaunchScriptLen
|
||||
}
|
||||
|
||||
copy(co.launchScript[:], b)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptCreateDeterministic sets header/descriptor fields to values that support deterministic
|
||||
// creation of images.
|
||||
func OptCreateDeterministic() CreateOpt {
|
||||
return func(co *createOpts) error {
|
||||
co.id = uuid.Nil
|
||||
co.t = time.Time{}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptCreateWithID specifies id as the unique ID.
|
||||
func OptCreateWithID(id string) CreateOpt {
|
||||
return func(co *createOpts) error {
|
||||
id, err := uuid.Parse(id)
|
||||
co.id = id
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// OptCreateWithDescriptorCapacity specifies that the created image should have the capacity for a
|
||||
// maximum of n descriptors.
|
||||
func OptCreateWithDescriptorCapacity(n int64) CreateOpt {
|
||||
return func(co *createOpts) error {
|
||||
co.descriptorCapacity = n
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptCreateWithDescriptors appends dis to the list of descriptors.
|
||||
func OptCreateWithDescriptors(dis ...DescriptorInput) CreateOpt {
|
||||
return func(co *createOpts) error {
|
||||
co.dis = append(co.dis, dis...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptCreateWithTime specifies t as the image creation time.
|
||||
func OptCreateWithTime(t time.Time) CreateOpt {
|
||||
return func(co *createOpts) error {
|
||||
co.t = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptCreateWithCloseOnUnload specifies whether the ReadWriter should be closed by UnloadContainer.
|
||||
// By default, the ReadWriter will be closed if it implements the io.Closer interface.
|
||||
func OptCreateWithCloseOnUnload(b bool) CreateOpt {
|
||||
return func(co *createOpts) error {
|
||||
co.closeOnUnload = b
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// createContainer creates a new SIF container file in rw, according to opts.
|
||||
func createContainer(rw ReadWriter, co createOpts) (*FileImage, error) {
|
||||
rds := make([]rawDescriptor, co.descriptorCapacity)
|
||||
rdsSize := int64(binary.Size(rds))
|
||||
|
||||
h := header{
|
||||
LaunchScript: co.launchScript,
|
||||
Magic: hdrMagic,
|
||||
Version: CurrentVersion.bytes(),
|
||||
Arch: hdrArchUnknown,
|
||||
ID: co.id,
|
||||
CreatedAt: co.t.Unix(),
|
||||
ModifiedAt: co.t.Unix(),
|
||||
DescriptorsFree: co.descriptorCapacity,
|
||||
DescriptorsTotal: co.descriptorCapacity,
|
||||
DescriptorsOffset: co.descriptorsOffset,
|
||||
DescriptorsSize: rdsSize,
|
||||
DataOffset: co.descriptorsOffset + rdsSize,
|
||||
}
|
||||
|
||||
f := &FileImage{
|
||||
rw: rw,
|
||||
h: h,
|
||||
rds: rds,
|
||||
minIDs: make(map[uint32]uint32),
|
||||
}
|
||||
|
||||
for i, di := range co.dis {
|
||||
if err := f.writeDataObject(i, di, co.t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.writeDescriptors(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := f.writeHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// CreateContainer creates a new SIF container in rw, according to opts. One or more data objects
|
||||
// can optionally be specified using OptCreateWithDescriptors.
|
||||
//
|
||||
// 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 OptCreateWithCloseOnUnload.
|
||||
//
|
||||
// By default, the image ID is set to a randomly generated value. To override this, consider using
|
||||
// OptCreateDeterministic or OptCreateWithID.
|
||||
//
|
||||
// By default, the image creation time is set to time.Now(). To override this, consider using
|
||||
// OptCreateDeterministic or OptCreateWithTime.
|
||||
//
|
||||
// By default, the image will support a maximum of 48 descriptors. To change this, consider using
|
||||
// OptCreateWithDescriptorCapacity.
|
||||
//
|
||||
// A launch script can optionally be set using OptCreateWithLaunchScript.
|
||||
func CreateContainer(rw ReadWriter, opts ...CreateOpt) (*FileImage, error) {
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
co := createOpts{
|
||||
id: id,
|
||||
descriptorsOffset: 4096,
|
||||
descriptorCapacity: 48,
|
||||
t: time.Now(),
|
||||
closeOnUnload: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(&co); err != nil {
|
||||
return nil, fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
f, err := createContainer(rw, co)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
f.closeOnUnload = co.closeOnUnload
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// CreateContainerAtPath creates a new SIF container file at path, according to opts. One or more
|
||||
// data objects can optionally be specified using OptCreateWithDescriptors.
|
||||
//
|
||||
// On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources
|
||||
// are released.
|
||||
//
|
||||
// By default, the image ID is set to a randomly generated value. To override this, consider using
|
||||
// OptCreateDeterministic or OptCreateWithID.
|
||||
//
|
||||
// By default, the image creation time is set to time.Now(). To override this, consider using
|
||||
// OptCreateDeterministic or OptCreateWithTime.
|
||||
//
|
||||
// By default, the image will support a maximum of 48 descriptors. To change this, consider using
|
||||
// OptCreateWithDescriptorCapacity.
|
||||
//
|
||||
// A launch script can optionally be set using OptCreateWithLaunchScript.
|
||||
func CreateContainerAtPath(path string, opts ...CreateOpt) (*FileImage, error) {
|
||||
fp, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
f, err := CreateContainer(fp, opts...)
|
||||
if err != nil {
|
||||
fp.Close()
|
||||
os.Remove(fp.Name())
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.closeOnUnload = true
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func zeroData(fimg *FileImage, descr *rawDescriptor) error {
|
||||
// first, move to data object offset
|
||||
if _, err := fimg.rw.Seek(descr.Offset, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var zero [4096]byte
|
||||
n := descr.Size
|
||||
upbound := int64(4096)
|
||||
for {
|
||||
if n < 4096 {
|
||||
upbound = n
|
||||
}
|
||||
|
||||
if _, err := fimg.rw.Write(zero[:upbound]); err != nil {
|
||||
return err
|
||||
}
|
||||
n -= 4096
|
||||
if n <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetDescriptor(fimg *FileImage, index int) error {
|
||||
// If we remove the primary partition, set the global header Arch field to HdrArchUnknown
|
||||
// to indicate that the SIF file doesn't include a primary partition and no dependency
|
||||
// on any architecture exists.
|
||||
if fimg.rds[index].isPartitionOfType(PartPrimSys) {
|
||||
fimg.h.Arch = hdrArchUnknown
|
||||
}
|
||||
|
||||
offset := fimg.h.DescriptorsOffset + int64(index)*int64(binary.Size(fimg.rds[0]))
|
||||
|
||||
// first, move to descriptor offset
|
||||
if _, err := fimg.rw.Seek(offset, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var emptyDesc rawDescriptor
|
||||
return binary.Write(fimg.rw, binary.LittleEndian, emptyDesc)
|
||||
}
|
||||
|
||||
// addOpts accumulates object add options.
|
||||
type addOpts struct {
|
||||
t time.Time
|
||||
}
|
||||
|
||||
// AddOpt are used to specify object add options.
|
||||
type AddOpt func(*addOpts) error
|
||||
|
||||
// OptAddDeterministic sets header/descriptor fields to values that support deterministic
|
||||
// modification of images.
|
||||
func OptAddDeterministic() AddOpt {
|
||||
return func(ao *addOpts) error {
|
||||
ao.t = time.Time{}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptAddWithTime specifies t as the image modification time.
|
||||
func OptAddWithTime(t time.Time) AddOpt {
|
||||
return func(ao *addOpts) error {
|
||||
ao.t = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AddObject adds a new data object and its descriptor into the specified SIF file.
|
||||
//
|
||||
// By default, the image modification time is set to the current time. To override this, consider
|
||||
// using OptAddDeterministic or OptAddWithTime.
|
||||
func (f *FileImage) AddObject(di DescriptorInput, opts ...AddOpt) error {
|
||||
ao := addOpts{
|
||||
t: time.Now(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(&ao); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Find an unused descriptor.
|
||||
i := 0
|
||||
for _, rd := range f.rds {
|
||||
if !rd.Used {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if err := f.writeDataObject(i, di, ao.t); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
if err := f.writeDescriptors(); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
f.h.ModifiedAt = ao.t.Unix()
|
||||
|
||||
if err := f.writeHeader(); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isLast return true if the data object associated with d is the last in f.
|
||||
func (f *FileImage) isLast(d *rawDescriptor) bool {
|
||||
isLast := true
|
||||
|
||||
end := d.Offset + d.Size
|
||||
f.WithDescriptors(func(d Descriptor) bool {
|
||||
isLast = d.Offset()+d.Size() <= end
|
||||
return !isLast
|
||||
})
|
||||
|
||||
return isLast
|
||||
}
|
||||
|
||||
// truncateAt truncates f at the start of the padded data object described by d.
|
||||
func (f *FileImage) truncateAt(d *rawDescriptor) error {
|
||||
start := d.Offset + d.Size - d.SizeWithPadding
|
||||
|
||||
if err := f.rw.Truncate(start); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteOpts accumulates object deletion options.
|
||||
type deleteOpts struct {
|
||||
zero bool
|
||||
compact bool
|
||||
t time.Time
|
||||
}
|
||||
|
||||
// DeleteOpt are used to specify object deletion options.
|
||||
type DeleteOpt func(*deleteOpts) error
|
||||
|
||||
// OptDeleteZero specifies whether the deleted object should be zeroed.
|
||||
func OptDeleteZero(b bool) DeleteOpt {
|
||||
return func(do *deleteOpts) error {
|
||||
do.zero = b
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptDeleteCompact specifies whether the image should be compacted following object deletion.
|
||||
func OptDeleteCompact(b bool) DeleteOpt {
|
||||
return func(do *deleteOpts) error {
|
||||
do.compact = b
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptDeleteDeterministic sets header/descriptor fields to values that support deterministic
|
||||
// modification of images.
|
||||
func OptDeleteDeterministic() DeleteOpt {
|
||||
return func(do *deleteOpts) error {
|
||||
do.t = time.Time{}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptDeleteWithTime specifies t as the image modification time.
|
||||
func OptDeleteWithTime(t time.Time) DeleteOpt {
|
||||
return func(do *deleteOpts) error {
|
||||
do.t = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var errCompactNotImplemented = errors.New("compact not implemented for non-last object")
|
||||
|
||||
// DeleteObject deletes the data object with id, according to opts.
|
||||
//
|
||||
// To zero the data region of the deleted object, use OptDeleteZero. To compact the file following
|
||||
// object deletion, use OptDeleteCompact.
|
||||
//
|
||||
// By default, the image modification time is set to time.Now(). To override this, consider using
|
||||
// OptDeleteDeterministic or OptDeleteWithTime.
|
||||
func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error {
|
||||
do := deleteOpts{
|
||||
t: time.Now(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(&do); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
d, err := f.getDescriptor(WithID(id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
if do.compact && !f.isLast(d) {
|
||||
return fmt.Errorf("%w", errCompactNotImplemented)
|
||||
}
|
||||
|
||||
if do.zero {
|
||||
if err := zeroData(f, d); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if do.compact {
|
||||
if err := f.truncateAt(d); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
f.h.DataSize -= d.SizeWithPadding
|
||||
}
|
||||
|
||||
f.h.DescriptorsFree++
|
||||
f.h.ModifiedAt = do.t.Unix()
|
||||
|
||||
index := 0
|
||||
for i, od := range f.rds {
|
||||
if od.ID == id {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := resetDescriptor(f, index); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
if err := f.writeHeader(); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setOpts accumulates object set options.
|
||||
type setOpts struct {
|
||||
t time.Time
|
||||
}
|
||||
|
||||
// SetOpt are used to specify object set options.
|
||||
type SetOpt func(*setOpts) error
|
||||
|
||||
// OptSetDeterministic sets header/descriptor fields to values that support deterministic
|
||||
// modification of images.
|
||||
func OptSetDeterministic() SetOpt {
|
||||
return func(so *setOpts) error {
|
||||
so.t = time.Time{}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptSetWithTime specifies t as the image/object modification time.
|
||||
func OptSetWithTime(t time.Time) SetOpt {
|
||||
return func(so *setOpts) error {
|
||||
so.t = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errNotPartition = errors.New("data object not a partition")
|
||||
errNotSystem = errors.New("data object not a system partition")
|
||||
)
|
||||
|
||||
// SetPrimPart sets the specified system partition to be the primary one.
|
||||
//
|
||||
// By default, the image/object modification times are set to time.Now(). To override this,
|
||||
// consider using OptSetDeterministic or OptSetWithTime.
|
||||
func (f *FileImage) SetPrimPart(id uint32, opts ...SetOpt) error {
|
||||
so := setOpts{
|
||||
t: time.Now(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(&so); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
descr, err := f.getDescriptor(WithID(id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
if descr.DataType != DataPartition {
|
||||
return fmt.Errorf("%w", errNotPartition)
|
||||
}
|
||||
|
||||
fs, pt, arch, err := descr.getPartitionMetadata()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
// if already primary system partition, nothing to do
|
||||
if pt == PartPrimSys {
|
||||
return nil
|
||||
}
|
||||
|
||||
if pt != PartSystem {
|
||||
return fmt.Errorf("%w", errNotSystem)
|
||||
}
|
||||
|
||||
olddescr, err := f.getDescriptor(WithPartitionType(PartPrimSys))
|
||||
if err != nil && !errors.Is(err, ErrObjectNotFound) {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
f.h.Arch = getSIFArch(arch)
|
||||
|
||||
extra := partition{
|
||||
Fstype: fs,
|
||||
Parttype: PartPrimSys,
|
||||
}
|
||||
copy(extra.Arch[:], arch)
|
||||
|
||||
if err := descr.setExtra(extra); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
if olddescr != nil {
|
||||
oldfs, _, oldarch, err := olddescr.getPartitionMetadata()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
oldextra := partition{
|
||||
Fstype: oldfs,
|
||||
Parttype: PartSystem,
|
||||
Arch: getSIFArch(oldarch),
|
||||
}
|
||||
|
||||
if err := olddescr.setExtra(oldextra); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.writeDescriptors(); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
f.h.ModifiedAt = so.t.Unix()
|
||||
|
||||
if err := f.writeHeader(); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
267
vendor/github.com/sylabs/sif/v2/pkg/sif/descriptor.go
generated
vendored
Normal file
267
vendor/github.com/sylabs/sif/v2/pkg/sif/descriptor.go
generated
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// rawDescriptor represents an on-disk object descriptor.
|
||||
type rawDescriptor struct {
|
||||
DataType DataType
|
||||
Used bool
|
||||
ID uint32
|
||||
GroupID uint32
|
||||
LinkedID uint32
|
||||
Offset int64
|
||||
Size int64
|
||||
SizeWithPadding int64
|
||||
|
||||
CreatedAt int64
|
||||
ModifiedAt int64
|
||||
UID int64 // Deprecated: UID exists for historical compatibility and should not be used.
|
||||
GID int64 // Deprecated: GID exists for historical compatibility and should not be used.
|
||||
Name [descrNameLen]byte
|
||||
Extra [descrMaxPrivLen]byte
|
||||
}
|
||||
|
||||
// partition represents the SIF partition data object descriptor.
|
||||
type partition struct {
|
||||
Fstype FSType
|
||||
Parttype PartType
|
||||
Arch archType
|
||||
}
|
||||
|
||||
// signature represents the SIF signature data object descriptor.
|
||||
type signature struct {
|
||||
Hashtype hashType
|
||||
Entity [descrEntityLen]byte
|
||||
}
|
||||
|
||||
// cryptoMessage represents the SIF crypto message object descriptor.
|
||||
type cryptoMessage struct {
|
||||
Formattype FormatType
|
||||
Messagetype MessageType
|
||||
}
|
||||
|
||||
var errNameTooLarge = errors.New("name value too large")
|
||||
|
||||
// setName encodes name into the name field of d.
|
||||
func (d *rawDescriptor) setName(name string) error {
|
||||
if len(name) > len(d.Name) {
|
||||
return errNameTooLarge
|
||||
}
|
||||
|
||||
for i := copy(d.Name[:], name); i < len(d.Name); i++ {
|
||||
d.Name[i] = 0
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var errExtraTooLarge = errors.New("extra value too large")
|
||||
|
||||
// setExtra encodes v into the extra field of d.
|
||||
func (d *rawDescriptor) setExtra(v interface{}) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if binary.Size(v) > len(d.Extra) {
|
||||
return errExtraTooLarge
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
if err := binary.Write(b, binary.LittleEndian, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := copy(d.Extra[:], b.Bytes()); i < len(d.Extra); i++ {
|
||||
d.Extra[i] = 0
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPartitionMetadata gets metadata for a partition data object.
|
||||
func (d rawDescriptor) getPartitionMetadata() (fs FSType, pt PartType, arch string, err error) {
|
||||
if got, want := d.DataType, DataPartition; got != want {
|
||||
return 0, 0, "", &unexpectedDataTypeError{got, []DataType{want}}
|
||||
}
|
||||
|
||||
var p partition
|
||||
|
||||
b := bytes.NewReader(d.Extra[:])
|
||||
if err := binary.Read(b, binary.LittleEndian, &p); err != nil {
|
||||
return 0, 0, "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
return p.Fstype, p.Parttype, p.Arch.GoArch(), nil
|
||||
}
|
||||
|
||||
// isPartitionOfType returns true if d is a partition data object of type pt.
|
||||
func (d rawDescriptor) isPartitionOfType(pt PartType) bool {
|
||||
_, t, _, err := d.getPartitionMetadata()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return t == pt
|
||||
}
|
||||
|
||||
// Descriptor represents the SIF descriptor type.
|
||||
type Descriptor struct {
|
||||
r io.ReaderAt // Backing storage.
|
||||
|
||||
raw rawDescriptor // Raw descriptor from image.
|
||||
|
||||
relativeID uint32 // ID relative to minimum ID of object group.
|
||||
}
|
||||
|
||||
// DataType returns the type of data object.
|
||||
func (d Descriptor) DataType() DataType { return d.raw.DataType }
|
||||
|
||||
// ID returns the data object ID of d.
|
||||
func (d Descriptor) ID() uint32 { return d.raw.ID }
|
||||
|
||||
// GroupID returns the data object group ID of d, or zero if d is not part of a data object
|
||||
// group.
|
||||
func (d Descriptor) GroupID() uint32 { return d.raw.GroupID &^ descrGroupMask }
|
||||
|
||||
// LinkedID returns the object/group ID d is linked to, or zero if d does not contain a linked
|
||||
// ID. If isGroup is true, the returned id is an object group ID. Otherwise, the returned id is a
|
||||
// data object ID.
|
||||
func (d Descriptor) LinkedID() (id uint32, isGroup bool) {
|
||||
return d.raw.LinkedID &^ descrGroupMask, d.raw.LinkedID&descrGroupMask == descrGroupMask
|
||||
}
|
||||
|
||||
// Offset returns the offset of the data object.
|
||||
func (d Descriptor) Offset() int64 { return d.raw.Offset }
|
||||
|
||||
// Size returns the data object size.
|
||||
func (d Descriptor) Size() int64 { return d.raw.Size }
|
||||
|
||||
// CreatedAt returns the creation time of the data object.
|
||||
func (d Descriptor) CreatedAt() time.Time { return time.Unix(d.raw.CreatedAt, 0) }
|
||||
|
||||
// ModifiedAt returns the modification time of the data object.
|
||||
func (d Descriptor) ModifiedAt() time.Time { return time.Unix(d.raw.ModifiedAt, 0) }
|
||||
|
||||
// Name returns the name of the data object.
|
||||
func (d Descriptor) Name() string { return strings.TrimRight(string(d.raw.Name[:]), "\000") }
|
||||
|
||||
// PartitionMetadata gets metadata for a partition data object.
|
||||
func (d Descriptor) PartitionMetadata() (fs FSType, pt PartType, arch string, err error) {
|
||||
return d.raw.getPartitionMetadata()
|
||||
}
|
||||
|
||||
var errHashUnsupported = errors.New("hash algorithm unsupported")
|
||||
|
||||
// getHashType converts ht into a crypto.Hash.
|
||||
func getHashType(ht hashType) (crypto.Hash, error) {
|
||||
switch ht {
|
||||
case hashSHA256:
|
||||
return crypto.SHA256, nil
|
||||
case hashSHA384:
|
||||
return crypto.SHA384, nil
|
||||
case hashSHA512:
|
||||
return crypto.SHA512, nil
|
||||
case hashBLAKE2S:
|
||||
return crypto.BLAKE2s_256, nil
|
||||
case hashBLAKE2B:
|
||||
return crypto.BLAKE2b_256, nil
|
||||
}
|
||||
return 0, errHashUnsupported
|
||||
}
|
||||
|
||||
// SignatureMetadata gets metadata for a signature data object.
|
||||
func (d Descriptor) SignatureMetadata() (ht crypto.Hash, fp []byte, err error) {
|
||||
if got, want := d.raw.DataType, DataSignature; got != want {
|
||||
return ht, fp, &unexpectedDataTypeError{got, []DataType{want}}
|
||||
}
|
||||
|
||||
var s signature
|
||||
|
||||
b := bytes.NewReader(d.raw.Extra[:])
|
||||
if err := binary.Read(b, binary.LittleEndian, &s); err != nil {
|
||||
return ht, fp, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
if ht, err = getHashType(s.Hashtype); err != nil {
|
||||
return ht, fp, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
fp = make([]byte, 20)
|
||||
copy(fp, s.Entity[:])
|
||||
|
||||
return ht, fp, nil
|
||||
}
|
||||
|
||||
// CryptoMessageMetadata gets metadata for a crypto message data object.
|
||||
func (d Descriptor) CryptoMessageMetadata() (FormatType, MessageType, error) {
|
||||
if got, want := d.raw.DataType, DataCryptoMessage; got != want {
|
||||
return 0, 0, &unexpectedDataTypeError{got, []DataType{want}}
|
||||
}
|
||||
|
||||
var m cryptoMessage
|
||||
|
||||
b := bytes.NewReader(d.raw.Extra[:])
|
||||
if err := binary.Read(b, binary.LittleEndian, &m); err != nil {
|
||||
return 0, 0, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
return m.Formattype, m.Messagetype, nil
|
||||
}
|
||||
|
||||
// GetData returns the data object associated with descriptor d.
|
||||
func (d Descriptor) GetData() ([]byte, error) {
|
||||
b := make([]byte, d.raw.Size)
|
||||
if _, err := io.ReadFull(d.GetReader(), b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// GetReader returns a io.Reader that reads the data object associated with descriptor d.
|
||||
func (d Descriptor) GetReader() io.Reader {
|
||||
return io.NewSectionReader(d.r, d.raw.Offset, d.raw.Size)
|
||||
}
|
||||
|
||||
// GetIntegrityReader returns an io.Reader that reads the integrity-protected fields from d.
|
||||
func (d Descriptor) GetIntegrityReader() io.Reader {
|
||||
fields := []interface{}{
|
||||
d.raw.DataType,
|
||||
d.raw.Used,
|
||||
d.relativeID,
|
||||
d.raw.LinkedID,
|
||||
d.raw.Size,
|
||||
d.raw.CreatedAt,
|
||||
d.raw.UID,
|
||||
d.raw.GID,
|
||||
}
|
||||
|
||||
// Encode endian-sensitive fields.
|
||||
data := bytes.Buffer{}
|
||||
for _, f := range fields {
|
||||
if err := binary.Write(&data, binary.LittleEndian, f); err != nil {
|
||||
panic(err) // (*bytes.Buffer).Write() is documented as always returning a nil error.
|
||||
}
|
||||
}
|
||||
|
||||
return io.MultiReader(
|
||||
&data,
|
||||
bytes.NewReader(d.raw.Name[:]),
|
||||
bytes.NewReader(d.raw.Extra[:]),
|
||||
)
|
||||
}
|
300
vendor/github.com/sylabs/sif/v2/pkg/sif/descriptor_input.go
generated
vendored
Normal file
300
vendor/github.com/sylabs/sif/v2/pkg/sif/descriptor_input.go
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
// Copyright (c) 2021, Sylabs Inc. 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 (
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// descriptorOpts accumulates data object options.
|
||||
type descriptorOpts struct {
|
||||
groupID uint32
|
||||
linkID uint32
|
||||
alignment int
|
||||
name string
|
||||
extra interface{}
|
||||
t time.Time
|
||||
}
|
||||
|
||||
// DescriptorInputOpt are used to specify data object options.
|
||||
type DescriptorInputOpt func(DataType, *descriptorOpts) error
|
||||
|
||||
// OptNoGroup specifies the data object is not contained within a data object group.
|
||||
func OptNoGroup() DescriptorInputOpt {
|
||||
return func(_ DataType, opts *descriptorOpts) error {
|
||||
opts.groupID = 0
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptGroupID specifies groupID as data object group ID.
|
||||
func OptGroupID(groupID uint32) DescriptorInputOpt {
|
||||
return func(_ DataType, opts *descriptorOpts) error {
|
||||
if groupID == 0 {
|
||||
return ErrInvalidGroupID
|
||||
}
|
||||
opts.groupID = groupID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptLinkedID specifies that the data object is linked to the data object with the specified ID.
|
||||
func OptLinkedID(id uint32) DescriptorInputOpt {
|
||||
return func(_ DataType, opts *descriptorOpts) error {
|
||||
if id == 0 {
|
||||
return ErrInvalidObjectID
|
||||
}
|
||||
opts.linkID = id
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptLinkedGroupID specifies that the data object is linked to the data object group with the
|
||||
// specified groupID.
|
||||
func OptLinkedGroupID(groupID uint32) DescriptorInputOpt {
|
||||
return func(_ DataType, opts *descriptorOpts) error {
|
||||
if groupID == 0 {
|
||||
return ErrInvalidGroupID
|
||||
}
|
||||
opts.linkID = groupID | descrGroupMask
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptObjectAlignment specifies n as the data alignment requirement.
|
||||
func OptObjectAlignment(n int) DescriptorInputOpt {
|
||||
return func(_ DataType, opts *descriptorOpts) error {
|
||||
opts.alignment = n
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptObjectName specifies name as the data object name.
|
||||
func OptObjectName(name string) DescriptorInputOpt {
|
||||
return func(_ DataType, opts *descriptorOpts) error {
|
||||
opts.name = name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptObjectTime specifies t as the data object creation time.
|
||||
func OptObjectTime(t time.Time) DescriptorInputOpt {
|
||||
return func(_ DataType, opts *descriptorOpts) error {
|
||||
opts.t = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type unexpectedDataTypeError struct {
|
||||
got DataType
|
||||
want []DataType
|
||||
}
|
||||
|
||||
func (e *unexpectedDataTypeError) Error() string {
|
||||
return fmt.Sprintf("unexpected data type %v, expected one of: %v", e.got, e.want)
|
||||
}
|
||||
|
||||
func (e *unexpectedDataTypeError) Is(target error) bool {
|
||||
//nolint:errorlint // don't compare wrapped errors in Is()
|
||||
t, ok := target.(*unexpectedDataTypeError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(t.want) > 0 {
|
||||
// Use a map to check that the "want" errors in e and t contain the same values, ignoring
|
||||
// any ordering differences.
|
||||
acc := make(map[DataType]int, len(t.want))
|
||||
|
||||
// Increment counter for each data type in e.
|
||||
for _, dt := range e.want {
|
||||
if _, ok := acc[dt]; !ok {
|
||||
acc[dt] = 0
|
||||
}
|
||||
acc[dt]++
|
||||
}
|
||||
|
||||
// Decrement counter for each data type in e.
|
||||
for _, dt := range t.want {
|
||||
if _, ok := acc[dt]; !ok {
|
||||
return false
|
||||
}
|
||||
acc[dt]--
|
||||
}
|
||||
|
||||
// If the "want" errors in e and t are equivalent, all counters should be zero.
|
||||
for _, n := range acc {
|
||||
if n != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (e.got == t.got || t.got == 0)
|
||||
}
|
||||
|
||||
// OptCryptoMessageMetadata sets metadata for a crypto message data object. The format type is set
|
||||
// to ft, and the message type is set to mt.
|
||||
//
|
||||
// If this option is applied to a data object with an incompatible type, an error is returned.
|
||||
func OptCryptoMessageMetadata(ft FormatType, mt MessageType) DescriptorInputOpt {
|
||||
return func(t DataType, opts *descriptorOpts) error {
|
||||
if got, want := t, DataCryptoMessage; got != want {
|
||||
return &unexpectedDataTypeError{got, []DataType{want}}
|
||||
}
|
||||
|
||||
m := cryptoMessage{
|
||||
Formattype: ft,
|
||||
Messagetype: mt,
|
||||
}
|
||||
|
||||
opts.extra = m
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var errUnknownArchitcture = errors.New("unknown architecture")
|
||||
|
||||
// OptPartitionMetadata sets metadata for a partition data object. The filesystem type is set to
|
||||
// fs, the partition type is set to pt, and the CPU architecture is set to arch. The value of arch
|
||||
// should be the architecture as represented by the Go runtime.
|
||||
//
|
||||
// If this option is applied to a data object with an incompatible type, an error is returned.
|
||||
func OptPartitionMetadata(fs FSType, pt PartType, arch string) DescriptorInputOpt {
|
||||
return func(t DataType, opts *descriptorOpts) error {
|
||||
if got, want := t, DataPartition; got != want {
|
||||
return &unexpectedDataTypeError{got, []DataType{want}}
|
||||
}
|
||||
|
||||
sifarch := getSIFArch(arch)
|
||||
if sifarch == hdrArchUnknown {
|
||||
return fmt.Errorf("%w: %v", errUnknownArchitcture, arch)
|
||||
}
|
||||
|
||||
p := partition{
|
||||
Fstype: fs,
|
||||
Parttype: pt,
|
||||
Arch: sifarch,
|
||||
}
|
||||
|
||||
opts.extra = p
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// sifHashType converts h into a HashType.
|
||||
func sifHashType(h crypto.Hash) hashType {
|
||||
switch h {
|
||||
case crypto.SHA256:
|
||||
return hashSHA256
|
||||
case crypto.SHA384:
|
||||
return hashSHA384
|
||||
case crypto.SHA512:
|
||||
return hashSHA512
|
||||
case crypto.BLAKE2s_256:
|
||||
return hashBLAKE2S
|
||||
case crypto.BLAKE2b_256:
|
||||
return hashBLAKE2B
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// OptSignatureMetadata sets metadata for a signature data object. The hash type is set to ht, and
|
||||
// the signing entity fingerprint is set to fp.
|
||||
//
|
||||
// If this option is applied to a data object with an incompatible type, an error is returned.
|
||||
func OptSignatureMetadata(ht crypto.Hash, fp []byte) DescriptorInputOpt {
|
||||
return func(t DataType, opts *descriptorOpts) error {
|
||||
if got, want := t, DataSignature; got != want {
|
||||
return &unexpectedDataTypeError{got, []DataType{want}}
|
||||
}
|
||||
|
||||
s := signature{
|
||||
Hashtype: sifHashType(ht),
|
||||
}
|
||||
copy(s.Entity[:], fp)
|
||||
|
||||
opts.extra = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DescriptorInput describes a new data object.
|
||||
type DescriptorInput struct {
|
||||
dt DataType
|
||||
r io.Reader
|
||||
opts descriptorOpts
|
||||
}
|
||||
|
||||
// DefaultObjectGroup is the default group that data objects are placed in.
|
||||
const DefaultObjectGroup = 1
|
||||
|
||||
// NewDescriptorInput returns a DescriptorInput representing a data object of type t, with contents
|
||||
// read from r, configured according to opts.
|
||||
//
|
||||
// It is possible (and often necessary) to store additional metadata related to certain types of
|
||||
// data objects. Consider supplying options such as OptCryptoMessageMetadata, OptPartitionMetadata,
|
||||
// and OptSignatureMetadata for this purpose.
|
||||
//
|
||||
// By default, the data object will be placed in the default data object group (1). To override
|
||||
// this behavior, use OptNoGroup or OptGroupID. To link this data object, use OptLinkedID or
|
||||
// OptLinkedGroupID.
|
||||
//
|
||||
// By default, the data object will be aligned according to the system's memory page size. To
|
||||
// override this behavior, consider using OptObjectAlignment.
|
||||
//
|
||||
// By default, no name is set for data object. To set a name, use OptObjectName.
|
||||
//
|
||||
// When creating a new image, data object creation/modification times are set to the image creation
|
||||
// time. When modifying an existing image, the data object creation/modification time is set to the
|
||||
// image modification time. To override this behavior, consider using OptObjectTime.
|
||||
func NewDescriptorInput(t DataType, r io.Reader, opts ...DescriptorInputOpt) (DescriptorInput, error) {
|
||||
dopts := descriptorOpts{
|
||||
groupID: DefaultObjectGroup,
|
||||
alignment: os.Getpagesize(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(t, &dopts); err != nil {
|
||||
return DescriptorInput{}, fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
di := DescriptorInput{
|
||||
dt: t,
|
||||
r: r,
|
||||
opts: dopts,
|
||||
}
|
||||
|
||||
return di, nil
|
||||
}
|
||||
|
||||
// fillDescriptor fills d according to di. If di does not explicitly specify a time value, use t.
|
||||
func (di DescriptorInput) fillDescriptor(t time.Time, d *rawDescriptor) error {
|
||||
d.DataType = di.dt
|
||||
d.GroupID = di.opts.groupID | descrGroupMask
|
||||
d.LinkedID = di.opts.linkID
|
||||
|
||||
if !di.opts.t.IsZero() {
|
||||
t = di.opts.t
|
||||
}
|
||||
d.CreatedAt = t.Unix()
|
||||
d.ModifiedAt = t.Unix()
|
||||
|
||||
d.UID = 0
|
||||
d.GID = 0
|
||||
|
||||
if err := d.setName(di.opts.name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.setExtra(di.opts.extra)
|
||||
}
|
174
vendor/github.com/sylabs/sif/v2/pkg/sif/load.go
generated
vendored
Normal file
174
vendor/github.com/sylabs/sif/v2/pkg/sif/load.go
generated
vendored
Normal file
@@ -0,0 +1,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
|
||||
}
|
210
vendor/github.com/sylabs/sif/v2/pkg/sif/select.go
generated
vendored
Normal file
210
vendor/github.com/sylabs/sif/v2/pkg/sif/select.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright (c) 2021, Sylabs Inc. 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrNoObjects is the error returned when an image contains no data objects.
|
||||
var ErrNoObjects = errors.New("no objects in image")
|
||||
|
||||
// ErrObjectNotFound is the error returned when a data object is not found.
|
||||
var ErrObjectNotFound = errors.New("object not found")
|
||||
|
||||
// ErrMultipleObjectsFound is the error returned when multiple data objects are found.
|
||||
var ErrMultipleObjectsFound = errors.New("multiple objects found")
|
||||
|
||||
// ErrInvalidObjectID is the error returned when an invalid object ID is supplied.
|
||||
var ErrInvalidObjectID = errors.New("invalid object ID")
|
||||
|
||||
// ErrInvalidGroupID is the error returned when an invalid group ID is supplied.
|
||||
var ErrInvalidGroupID = errors.New("invalid group ID")
|
||||
|
||||
// DescriptorSelectorFunc returns true if d matches, and false otherwise.
|
||||
type DescriptorSelectorFunc func(d Descriptor) (bool, error)
|
||||
|
||||
// WithDataType selects descriptors that have data type dt.
|
||||
func WithDataType(dt DataType) DescriptorSelectorFunc {
|
||||
return func(d Descriptor) (bool, error) {
|
||||
return d.DataType() == dt, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithID selects descriptors with a matching ID.
|
||||
func WithID(id uint32) DescriptorSelectorFunc {
|
||||
return func(d Descriptor) (bool, error) {
|
||||
if id == 0 {
|
||||
return false, ErrInvalidObjectID
|
||||
}
|
||||
return d.ID() == id, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoGroup selects descriptors that are not contained within an object group.
|
||||
func WithNoGroup() DescriptorSelectorFunc {
|
||||
return func(d Descriptor) (bool, error) {
|
||||
return d.GroupID() == 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithGroupID returns a selector func that selects descriptors with a matching groupID.
|
||||
func WithGroupID(groupID uint32) DescriptorSelectorFunc {
|
||||
return func(d Descriptor) (bool, error) {
|
||||
if groupID == 0 {
|
||||
return false, ErrInvalidGroupID
|
||||
}
|
||||
return d.GroupID() == groupID, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLinkedID selects descriptors that are linked to the data object with specified ID.
|
||||
func WithLinkedID(id uint32) DescriptorSelectorFunc {
|
||||
return func(d Descriptor) (bool, error) {
|
||||
if id == 0 {
|
||||
return false, ErrInvalidObjectID
|
||||
}
|
||||
linkedID, isGroup := d.LinkedID()
|
||||
return !isGroup && linkedID == id, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLinkedGroupID selects descriptors that are linked to the data object group with specified
|
||||
// ID.
|
||||
func WithLinkedGroupID(groupID uint32) DescriptorSelectorFunc {
|
||||
return func(d Descriptor) (bool, error) {
|
||||
if groupID == 0 {
|
||||
return false, ErrInvalidGroupID
|
||||
}
|
||||
linkedID, isGroup := d.LinkedID()
|
||||
return isGroup && linkedID == groupID, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPartitionType selects descriptors containing a partition of type pt.
|
||||
func WithPartitionType(pt PartType) DescriptorSelectorFunc {
|
||||
return func(d Descriptor) (bool, error) {
|
||||
return d.raw.isPartitionOfType(pt), nil
|
||||
}
|
||||
}
|
||||
|
||||
// descriptorFromRaw populates a Descriptor from rd.
|
||||
func (f *FileImage) descriptorFromRaw(rd *rawDescriptor) Descriptor {
|
||||
return Descriptor{
|
||||
raw: *rd,
|
||||
r: f.rw,
|
||||
relativeID: rd.ID - f.minIDs[rd.GroupID],
|
||||
}
|
||||
}
|
||||
|
||||
// GetDescriptors returns a slice of in-use descriptors for which all selector funcs return true.
|
||||
// If the image contains no data objects, an error wrapping ErrNoObjects is returned.
|
||||
func (f *FileImage) GetDescriptors(fns ...DescriptorSelectorFunc) ([]Descriptor, error) {
|
||||
if f.DescriptorsFree() == f.DescriptorsTotal() {
|
||||
return nil, fmt.Errorf("%w", ErrNoObjects)
|
||||
}
|
||||
|
||||
var ds []Descriptor
|
||||
|
||||
err := f.withDescriptors(multiSelectorFunc(fns...), func(d *rawDescriptor) error {
|
||||
ds = append(ds, f.descriptorFromRaw(d))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
// getDescriptor returns a pointer to the in-use descriptor selected by fns. If no descriptor is
|
||||
// selected by fns, ErrObjectNotFound is returned. If multiple descriptors are selected by fns,
|
||||
// ErrMultipleObjectsFound is returned.
|
||||
func (f *FileImage) getDescriptor(fns ...DescriptorSelectorFunc) (*rawDescriptor, error) {
|
||||
var d *rawDescriptor
|
||||
|
||||
err := f.withDescriptors(multiSelectorFunc(fns...), func(found *rawDescriptor) error {
|
||||
if d != nil {
|
||||
return ErrMultipleObjectsFound
|
||||
}
|
||||
d = found
|
||||
return nil
|
||||
})
|
||||
|
||||
if err == nil && d == nil {
|
||||
err = ErrObjectNotFound
|
||||
}
|
||||
|
||||
return d, err
|
||||
}
|
||||
|
||||
// GetDescriptor returns the in-use descriptor selected by fns. If the image contains no data
|
||||
// objects, an error wrapping ErrNoObjects is returned. If no descriptor is selected by fns, an
|
||||
// error wrapping ErrObjectNotFound is returned. If multiple descriptors are selected by fns, an
|
||||
// error wrapping ErrMultipleObjectsFound is returned.
|
||||
func (f *FileImage) GetDescriptor(fns ...DescriptorSelectorFunc) (Descriptor, error) {
|
||||
if f.DescriptorsFree() == f.DescriptorsTotal() {
|
||||
return Descriptor{}, fmt.Errorf("%w", ErrNoObjects)
|
||||
}
|
||||
|
||||
d, err := f.getDescriptor(fns...)
|
||||
if err != nil {
|
||||
return Descriptor{}, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
return f.descriptorFromRaw(d), nil
|
||||
}
|
||||
|
||||
// multiSelectorFunc returns a DescriptorSelectorFunc that selects a descriptor iff all of fns
|
||||
// select the descriptor.
|
||||
func multiSelectorFunc(fns ...DescriptorSelectorFunc) DescriptorSelectorFunc {
|
||||
return func(d Descriptor) (bool, error) {
|
||||
for _, fn := range fns {
|
||||
if ok, err := fn(d); !ok || err != nil {
|
||||
return ok, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// withDescriptors calls onMatchFn with each in-use descriptor in f for which selectFn returns
|
||||
// true. If selectFn or onMatchFn return a non-nil error, the iteration halts, and the error is
|
||||
// returned to the caller.
|
||||
func (f *FileImage) withDescriptors(selectFn DescriptorSelectorFunc, onMatchFn func(*rawDescriptor) error) error {
|
||||
for i, d := range f.rds {
|
||||
if !d.Used {
|
||||
continue
|
||||
}
|
||||
|
||||
if ok, err := selectFn(f.descriptorFromRaw(&f.rds[i])); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := onMatchFn(&f.rds[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var errAbort = errors.New("abort")
|
||||
|
||||
// abortOnMatch is a semantic convenience function that always returns a non-nil error, which can
|
||||
// be used as a no-op matchFn.
|
||||
func abortOnMatch(*rawDescriptor) error { return errAbort }
|
||||
|
||||
// WithDescriptors calls fn with each in-use descriptor in f, until fn returns true.
|
||||
func (f *FileImage) WithDescriptors(fn func(d Descriptor) bool) {
|
||||
selectFn := func(d Descriptor) (bool, error) {
|
||||
return fn(d), nil
|
||||
}
|
||||
_ = f.withDescriptors(selectFn, abortOnMatch)
|
||||
}
|
364
vendor/github.com/sylabs/sif/v2/pkg/sif/sif.go
generated
vendored
Normal file
364
vendor/github.com/sylabs/sif/v2/pkg/sif/sif.go
generated
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
// 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 implements data structures and routines to create
|
||||
// and access SIF files.
|
||||
//
|
||||
// Layout of a SIF file (example):
|
||||
//
|
||||
// .================================================.
|
||||
// | GLOBAL HEADER: Sifheader |
|
||||
// | - launch: "#!/usr/bin/env..." |
|
||||
// | - magic: "SIF_MAGIC" |
|
||||
// | - version: "1" |
|
||||
// | - arch: "4" |
|
||||
// | - uuid: b2659d4e-bd50-4ea5-bd17-eec5e54f918e |
|
||||
// | - ctime: 1504657553 |
|
||||
// | - mtime: 1504657653 |
|
||||
// | - ndescr: 3 |
|
||||
// | - descroff: 120 | --.
|
||||
// | - descrlen: 432 | |
|
||||
// | - dataoff: 4096 | |
|
||||
// | - datalen: 619362 | |
|
||||
// |------------------------------------------------| <-'
|
||||
// | DESCR[0]: Sifdeffile |
|
||||
// | - Sifcommon |
|
||||
// | - datatype: DATA_DEFFILE |
|
||||
// | - id: 1 |
|
||||
// | - groupid: 1 |
|
||||
// | - link: NONE |
|
||||
// | - fileoff: 4096 | --.
|
||||
// | - filelen: 222 | |
|
||||
// |------------------------------------------------| <-----.
|
||||
// | DESCR[1]: Sifpartition | | |
|
||||
// | - Sifcommon | | |
|
||||
// | - datatype: DATA_PARTITION | | |
|
||||
// | - id: 2 | | |
|
||||
// | - groupid: 1 | | |
|
||||
// | - link: NONE | | |
|
||||
// | - fileoff: 4318 | ----. |
|
||||
// | - filelen: 618496 | | | |
|
||||
// | - fstype: Squashfs | | | |
|
||||
// | - parttype: System | | | |
|
||||
// | - content: Linux | | | |
|
||||
// |------------------------------------------------| | | |
|
||||
// | DESCR[2]: Sifsignature | | | |
|
||||
// | - Sifcommon | | | |
|
||||
// | - datatype: DATA_SIGNATURE | | | |
|
||||
// | - id: 3 | | | |
|
||||
// | - groupid: NONE | | | |
|
||||
// | - link: 2 | ------'
|
||||
// | - fileoff: 622814 | ------.
|
||||
// | - filelen: 644 | | | |
|
||||
// | - hashtype: SHA384 | | | |
|
||||
// | - entity: @ | | | |
|
||||
// |------------------------------------------------| <-' | |
|
||||
// | Definition file data | | |
|
||||
// | . | | |
|
||||
// | . | | |
|
||||
// | . | | |
|
||||
// |------------------------------------------------| <---' |
|
||||
// | File system partition image | |
|
||||
// | . | |
|
||||
// | . | |
|
||||
// | . | |
|
||||
// |------------------------------------------------| <-----'
|
||||
// | Signed verification data |
|
||||
// | . |
|
||||
// | . |
|
||||
// | . |
|
||||
// `================================================'
|
||||
//
|
||||
package sif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// SIF header constants and quantities.
|
||||
const (
|
||||
hdrLaunchLen = 32 // len("#!/usr/bin/env... ")
|
||||
hdrMagicLen = 10 // len("SIF_MAGIC")
|
||||
hdrVersionLen = 3 // len("99")
|
||||
)
|
||||
|
||||
var hdrMagic = [...]byte{'S', 'I', 'F', '_', 'M', 'A', 'G', 'I', 'C', '\x00'}
|
||||
|
||||
// SpecVersion specifies a SIF specification version.
|
||||
type SpecVersion uint8
|
||||
|
||||
func (v SpecVersion) String() string { return fmt.Sprintf("%02d", v) }
|
||||
|
||||
// bytes returns the value of b, formatted for direct inclusion in a SIF header.
|
||||
func (v SpecVersion) bytes() [hdrVersionLen]byte {
|
||||
var b [3]byte
|
||||
copy(b[:], fmt.Sprintf("%02d", v))
|
||||
return b
|
||||
}
|
||||
|
||||
// SIF specification versions.
|
||||
const (
|
||||
version01 SpecVersion = iota + 1
|
||||
)
|
||||
|
||||
// CurrentVersion specifies the current SIF specification version.
|
||||
const CurrentVersion = version01
|
||||
|
||||
const (
|
||||
descrGroupMask = 0xf0000000 // groups start at that offset
|
||||
descrEntityLen = 256 // len("Joe Bloe <jbloe@gmail.com>...")
|
||||
descrNameLen = 128 // descriptor name (string identifier)
|
||||
descrMaxPrivLen = 384 // size reserved for descriptor specific data
|
||||
)
|
||||
|
||||
// DataType represents the different SIF data object types stored in the image.
|
||||
type DataType int32
|
||||
|
||||
// List of supported SIF data types.
|
||||
const (
|
||||
DataDeffile DataType = iota + 0x4001 // definition file data object
|
||||
DataEnvVar // environment variables data object
|
||||
DataLabels // JSON labels data object
|
||||
DataPartition // file system data object
|
||||
DataSignature // signing/verification data object
|
||||
DataGenericJSON // generic JSON meta-data
|
||||
DataGeneric // generic / raw data
|
||||
DataCryptoMessage // cryptographic message data object
|
||||
)
|
||||
|
||||
// String returns a human-readable representation of t.
|
||||
func (t DataType) String() string {
|
||||
switch t {
|
||||
case DataDeffile:
|
||||
return "Def.FILE"
|
||||
case DataEnvVar:
|
||||
return "Env.Vars"
|
||||
case DataLabels:
|
||||
return "JSON.Labels"
|
||||
case DataPartition:
|
||||
return "FS"
|
||||
case DataSignature:
|
||||
return "Signature"
|
||||
case DataGenericJSON:
|
||||
return "JSON.Generic"
|
||||
case DataGeneric:
|
||||
return "Generic/Raw"
|
||||
case DataCryptoMessage:
|
||||
return "Cryptographic Message"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// FSType represents the different SIF file system types found in partition data objects.
|
||||
type FSType int32
|
||||
|
||||
// List of supported file systems.
|
||||
const (
|
||||
FsSquash FSType = iota + 1 // Squashfs file system, RDONLY
|
||||
FsExt3 // EXT3 file system, RDWR (deprecated)
|
||||
FsImmuObj // immutable data object archive
|
||||
FsRaw // raw data
|
||||
FsEncryptedSquashfs // Encrypted Squashfs file system, RDONLY
|
||||
)
|
||||
|
||||
// String returns a human-readable representation of t.
|
||||
func (t FSType) String() string {
|
||||
switch t {
|
||||
case FsSquash:
|
||||
return "Squashfs"
|
||||
case FsExt3:
|
||||
return "Ext3"
|
||||
case FsImmuObj:
|
||||
return "Archive"
|
||||
case FsRaw:
|
||||
return "Raw"
|
||||
case FsEncryptedSquashfs:
|
||||
return "Encrypted squashfs"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// PartType represents the different SIF container partition types (system and data).
|
||||
type PartType int32
|
||||
|
||||
// List of supported partition types.
|
||||
const (
|
||||
PartSystem PartType = iota + 1 // partition hosts an operating system
|
||||
PartPrimSys // partition hosts the primary operating system
|
||||
PartData // partition hosts data only
|
||||
PartOverlay // partition hosts an overlay
|
||||
)
|
||||
|
||||
// String returns a human-readable representation of t.
|
||||
func (t PartType) String() string {
|
||||
switch t {
|
||||
case PartSystem:
|
||||
return "System"
|
||||
case PartPrimSys:
|
||||
return "*System"
|
||||
case PartData:
|
||||
return "Data"
|
||||
case PartOverlay:
|
||||
return "Overlay"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// hashType represents the different SIF hashing function types used to fingerprint data objects.
|
||||
type hashType int32
|
||||
|
||||
// List of supported hash functions.
|
||||
const (
|
||||
hashSHA256 hashType = iota + 1
|
||||
hashSHA384
|
||||
hashSHA512
|
||||
hashBLAKE2S
|
||||
hashBLAKE2B
|
||||
)
|
||||
|
||||
// FormatType represents the different formats used to store cryptographic message objects.
|
||||
type FormatType int32
|
||||
|
||||
// List of supported cryptographic message formats.
|
||||
const (
|
||||
FormatOpenPGP FormatType = iota + 1
|
||||
FormatPEM
|
||||
)
|
||||
|
||||
// String returns a human-readable representation of t.
|
||||
func (t FormatType) String() string {
|
||||
switch t {
|
||||
case FormatOpenPGP:
|
||||
return "OpenPGP"
|
||||
case FormatPEM:
|
||||
return "PEM"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// MessageType represents the different messages stored within cryptographic message objects.
|
||||
type MessageType int32
|
||||
|
||||
// List of supported cryptographic message formats.
|
||||
const (
|
||||
// openPGP formatted messages.
|
||||
MessageClearSignature MessageType = 0x100
|
||||
|
||||
// PEM formatted messages.
|
||||
MessageRSAOAEP MessageType = 0x200
|
||||
)
|
||||
|
||||
// String returns a human-readable representation of t.
|
||||
func (t MessageType) String() string {
|
||||
switch t {
|
||||
case MessageClearSignature:
|
||||
return "Clear Signature"
|
||||
case MessageRSAOAEP:
|
||||
return "RSA-OAEP"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// header describes a loaded SIF file.
|
||||
type header struct {
|
||||
LaunchScript [hdrLaunchLen]byte
|
||||
|
||||
Magic [hdrMagicLen]byte
|
||||
Version [hdrVersionLen]byte
|
||||
Arch archType
|
||||
ID uuid.UUID
|
||||
|
||||
CreatedAt int64
|
||||
ModifiedAt int64
|
||||
|
||||
DescriptorsFree int64
|
||||
DescriptorsTotal int64
|
||||
DescriptorsOffset int64
|
||||
DescriptorsSize int64
|
||||
DataOffset int64
|
||||
DataSize int64
|
||||
}
|
||||
|
||||
// GetIntegrityReader returns an io.Reader that reads the integrity-protected fields from h.
|
||||
func (h header) GetIntegrityReader() io.Reader {
|
||||
return io.MultiReader(
|
||||
bytes.NewReader(h.LaunchScript[:]),
|
||||
bytes.NewReader(h.Magic[:]),
|
||||
bytes.NewReader(h.Version[:]),
|
||||
bytes.NewReader(h.ID[:]),
|
||||
)
|
||||
}
|
||||
|
||||
// ReadWriter describes the interface required to read and write SIF images.
|
||||
type ReadWriter interface {
|
||||
io.ReaderAt
|
||||
io.WriteSeeker
|
||||
Truncate(int64) error
|
||||
}
|
||||
|
||||
// FileImage describes the representation of a SIF file in memory.
|
||||
type FileImage struct {
|
||||
rw ReadWriter // Backing storage for image.
|
||||
|
||||
h header // Raw global header from image.
|
||||
rds []rawDescriptor // Raw descriptors from image.
|
||||
|
||||
closeOnUnload bool // Close rw on Unload.
|
||||
minIDs map[uint32]uint32 // Minimum object IDs for each group ID.
|
||||
}
|
||||
|
||||
// LaunchScript returns the image launch script.
|
||||
func (f *FileImage) LaunchScript() string {
|
||||
return string(bytes.TrimRight(f.h.LaunchScript[:], "\x00"))
|
||||
}
|
||||
|
||||
// Version returns the SIF specification version of the image.
|
||||
func (f *FileImage) Version() string {
|
||||
return string(bytes.TrimRight(f.h.Version[:], "\x00"))
|
||||
}
|
||||
|
||||
// PrimaryArch returns the primary CPU architecture of the image, or "unknown" if the primary CPU
|
||||
// architecture cannot be determined.
|
||||
func (f *FileImage) PrimaryArch() string { return f.h.Arch.GoArch() }
|
||||
|
||||
// ID returns the ID of the image.
|
||||
func (f *FileImage) ID() string { return f.h.ID.String() }
|
||||
|
||||
// CreatedAt returns the creation time of the image.
|
||||
func (f *FileImage) CreatedAt() time.Time { return time.Unix(f.h.CreatedAt, 0) }
|
||||
|
||||
// ModifiedAt returns the last modification time of the image.
|
||||
func (f *FileImage) ModifiedAt() time.Time { return time.Unix(f.h.ModifiedAt, 0) }
|
||||
|
||||
// DescriptorsFree returns the number of free descriptors in the image.
|
||||
func (f *FileImage) DescriptorsFree() int64 { return f.h.DescriptorsFree }
|
||||
|
||||
// DescriptorsTotal returns the total number of descriptors in the image.
|
||||
func (f *FileImage) DescriptorsTotal() int64 { return f.h.DescriptorsTotal }
|
||||
|
||||
// DescriptorsOffset returns the offset (in bytes) of the descriptors section in the image.
|
||||
func (f *FileImage) DescriptorsOffset() int64 { return f.h.DescriptorsOffset }
|
||||
|
||||
// DescriptorsSize returns the size (in bytes) of the descriptors section in the image.
|
||||
func (f *FileImage) DescriptorsSize() int64 { return f.h.DescriptorsSize }
|
||||
|
||||
// DataOffset returns the offset (in bytes) of the data section in the image.
|
||||
func (f *FileImage) DataOffset() int64 { return f.h.DataOffset }
|
||||
|
||||
// DataSize returns the size (in bytes) of the data section in the image.
|
||||
func (f *FileImage) DataSize() int64 { return f.h.DataSize }
|
||||
|
||||
// GetHeaderIntegrityReader returns an io.Reader that reads the integrity-protected fields from the
|
||||
// header of the image.
|
||||
func (f *FileImage) GetHeaderIntegrityReader() io.Reader {
|
||||
return f.h.GetIntegrityReader()
|
||||
}
|
Reference in New Issue
Block a user