From 68683b40cd73f9d9238bafef5908679ad7b1969e Mon Sep 17 00:00:00 2001 From: Adam Hughes <9903835+tri-adam@users.noreply.github.com> Date: Thu, 4 Jul 2024 20:50:36 +0000 Subject: [PATCH] feat: add DeleteObjects Add DeleteObjects, which allows the caller to delete one or more data objects that match a DescriptorSelectorFunc. --- pkg/sif/delete.go | 64 ++++++++---- pkg/sif/delete_test.go | 93 ++++++++++++++++++ pkg/sif/select.go | 8 +- .../TestDeleteObjects/DataType.golden | Bin 0 -> 32180 bytes .../TestDeleteObjects/DataTypeCompact.golden | Bin 0 -> 32176 bytes .../ErrObjectNotFound.golden | Bin 0 -> 32176 bytes .../TestDeleteObjects/NilSelectFunc.golden | Bin 0 -> 32180 bytes .../PrimaryPartitionCompact.golden | Bin 0 -> 32176 bytes 8 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 pkg/sif/testdata/TestDeleteObjects/DataType.golden create mode 100644 pkg/sif/testdata/TestDeleteObjects/DataTypeCompact.golden create mode 100644 pkg/sif/testdata/TestDeleteObjects/ErrObjectNotFound.golden create mode 100644 pkg/sif/testdata/TestDeleteObjects/NilSelectFunc.golden create mode 100644 pkg/sif/testdata/TestDeleteObjects/PrimaryPartitionCompact.golden diff --git a/pkg/sif/delete.go b/pkg/sif/delete.go index 41c0a726..23d9e7d9 100644 --- a/pkg/sif/delete.go +++ b/pkg/sif/delete.go @@ -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() { @@ -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() diff --git a/pkg/sif/delete_test.go b/pkg/sif/delete_test.go index a7c2a165..652dc1bb 100644 --- a/pkg/sif/delete_test.go +++ b/pkg/sif/delete_test.go @@ -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 diff --git a/pkg/sif/select.go b/pkg/sif/select.go index ee7892f7..fd29a409 100644 --- a/pkg/sif/select.go +++ b/pkg/sif/select.go @@ -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. @@ -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 diff --git a/pkg/sif/testdata/TestDeleteObjects/DataType.golden b/pkg/sif/testdata/TestDeleteObjects/DataType.golden new file mode 100644 index 0000000000000000000000000000000000000000..7a656c7a97df4e2c586e7849535e3db3acf2c849 GIT binary patch literal 32180 zcmeIup$&sj5C-5Am^wyz9ibtiL7+MS6SN!DGY*rmNd*?cLja0|r2aeeCzt