Skip to content

Commit

Permalink
feat: add DeleteObjects
Browse files Browse the repository at this point in the history
Add DeleteObjects, which allows the caller to delete one or more data
objects that match a DescriptorSelectorFunc.
  • Loading branch information
tri-adam committed Jul 9, 2024
1 parent 94b0b65 commit 68683b4
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 21 deletions.
64 changes: 44 additions & 20 deletions pkg/sif/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,29 @@ func OptDeleteWithTime(t time.Time) DeleteOpt {
}
}

// DeleteObject deletes the data object with id, according to opts.
// DeleteObject deletes the data object with id, according to opts. If no matching descriptor is
// found, an error wrapping ErrObjectNotFound is returned.
//
// To zero the data region of the deleted object, use OptDeleteZero. To compact the file following
// object deletion, use OptDeleteCompact.
// To zero the data region of the deleted object, use OptDeleteZero. To remove unused space at the
// end of the FileImage following object deletion, use OptDeleteCompact.
//
// By default, the image modification time is set to the current time for non-deterministic images,
// and unset otherwise. To override this, consider using OptDeleteDeterministic or
// OptDeleteWithTime.
func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error {
return f.DeleteObjects(WithID(id), opts...)
}

// DeleteObjects deletes the data objects selected by fn, according to opts. If no descriptors are
// selected by fns, an error wrapping ErrObjectNotFound is returned.
//
// To zero the data region of the deleted object, use OptDeleteZero. To remove unused space at the
// end of the FileImage following object deletion, use OptDeleteCompact.
//
// By default, the image modification time is set to the current time for non-deterministic images,
// and unset otherwise. To override this, consider using OptDeleteDeterministic or
// OptDeleteWithTime.
func (f *FileImage) DeleteObjects(fn DescriptorSelectorFunc, opts ...DeleteOpt) error {
do := deleteOpts{}

if !f.isDeterministic() {
Expand All @@ -95,29 +109,39 @@ func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error {
}
}

d, err := f.getDescriptor(WithID(id))
if err != nil {
return fmt.Errorf("%w", err)
}
var selected bool

if do.zero {
if err := f.zero(d); err != nil {
return fmt.Errorf("%w", err)
if err := f.withDescriptors(fn, func(d *rawDescriptor) error {
selected = true

if do.zero {
if err := f.zero(d); err != nil {
return fmt.Errorf("%w", err)
}
}
}

f.h.DescriptorsFree++
f.h.ModifiedAt = do.t.Unix()
f.h.DescriptorsFree++

// 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 d.isPartitionOfType(PartPrimSys) {
f.h.Arch = hdrArchUnknown
}

// Reset rawDescripter with empty struct
*d = rawDescriptor{}

// 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 d.isPartitionOfType(PartPrimSys) {
f.h.Arch = hdrArchUnknown
return nil
}); err != nil {
return fmt.Errorf("%w", err)
}

// Reset rawDescripter with empty struct
*d = rawDescriptor{}
if !selected {
return fmt.Errorf("%w", ErrObjectNotFound)
}

f.h.ModifiedAt = do.t.Unix()

if do.compact {
f.h.DataSize = f.calculatedDataSize()
Expand Down
93 changes: 93 additions & 0 deletions pkg/sif/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,99 @@ func TestDeleteObject(t *testing.T) {
}
}

func TestDeleteObjects(t *testing.T) {
tests := []struct {
name string
createOpts []CreateOpt
fn DescriptorSelectorFunc
opts []DeleteOpt
wantErr error
}{
{
name: "ErrObjectNotFound",
createOpts: []CreateOpt{
OptCreateDeterministic(),
},
fn: WithID(1),
wantErr: ErrObjectNotFound,
},
{
name: "NilSelectFunc",
createOpts: []CreateOpt{
OptCreateDeterministic(),
OptCreateWithDescriptors(
getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}),
getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}),
),
},
fn: nil,
wantErr: errNilSelectFunc,
},
{
name: "DataType",
createOpts: []CreateOpt{
OptCreateDeterministic(),
OptCreateWithDescriptors(
getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}),
getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}),
),
},
fn: WithDataType(DataGeneric),
},
{
name: "DataTypeCompact",
createOpts: []CreateOpt{
OptCreateDeterministic(),
OptCreateWithDescriptors(
getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}),
getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}),
),
},
fn: WithDataType(DataGeneric),
opts: []DeleteOpt{
OptDeleteCompact(true),
},
},
{
name: "PrimaryPartitionCompact",
createOpts: []CreateOpt{
OptCreateDeterministic(),
OptCreateWithDescriptors(
getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce},
OptPartitionMetadata(FsSquash, PartPrimSys, "386"),
),
),
},
fn: WithPartitionType(PartPrimSys),
opts: []DeleteOpt{
OptDeleteCompact(true),
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var b Buffer

f, err := CreateContainer(&b, tt.createOpts...)
if err != nil {
t.Fatal(err)
}

if got, want := f.DeleteObjects(tt.fn, tt.opts...), tt.wantErr; !errors.Is(got, want) {
t.Errorf("got error %v, want %v", got, want)
}

if err := f.UnloadContainer(); err != nil {
t.Error(err)
}

g := goldie.New(t, goldie.WithTestNameForDir(true))
g.Assert(t, tt.name, b.Bytes())
})
}
}

func TestDeleteObjectAndAddObject(t *testing.T) {
tests := []struct {
name string
Expand Down
8 changes: 7 additions & 1 deletion pkg/sif/select.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021-2023, Sylabs Inc. All rights reserved.
// Copyright (c) 2021-2024, 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.
Expand Down Expand Up @@ -184,10 +184,16 @@ func multiSelectorFunc(fns ...DescriptorSelectorFunc) DescriptorSelectorFunc {
}
}

var errNilSelectFunc = errors.New("descriptor selector func must not be 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 {
if selectFn == nil {
return errNilSelectFunc
}

for i, d := range f.rds {
if !d.Used {
continue
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 68683b4

Please sign in to comment.