diff --git a/pkg/sif/create.go b/pkg/sif/create.go index 0f41e0a9..0eb1e1d1 100644 --- a/pkg/sif/create.go +++ b/pkg/sif/create.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved. +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the @@ -55,6 +55,20 @@ func writeDataObjectAt(ws io.WriteSeeker, offsetUnaligned int64, di DescriptorIn return nil } +// calculatedDataSize calculates the size of the data section based on the in-use descriptors. +func (f *FileImage) calculatedDataSize() int64 { + dataEnd := f.DataOffset() + + f.WithDescriptors(func(d Descriptor) bool { + if objectEnd := d.Offset() + d.Size(); dataEnd < objectEnd { + dataEnd = objectEnd + } + return false + }) + + return dataEnd - f.DataOffset() +} + var ( errInsufficientCapacity = errors.New("insufficient descriptor capacity to add data object(s) to image") errPrimaryPartition = errors.New("image already contains a primary partition") @@ -80,6 +94,8 @@ func (f *FileImage) writeDataObject(i int, di DescriptorInput, t time.Time) erro d := &f.rds[i] d.ID = uint32(i) + 1 + f.h.DataSize = f.calculatedDataSize() + if err := writeDataObjectAt(f.rw, f.h.DataOffset+f.h.DataSize, di, t, d); err != nil { return err } diff --git a/pkg/sif/delete.go b/pkg/sif/delete.go index c0f0aba3..41c0a726 100644 --- a/pkg/sif/delete.go +++ b/pkg/sif/delete.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved. +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the @@ -8,32 +8,16 @@ package sif import ( - "errors" "fmt" "io" "time" ) -// 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 -} - // zeroReader is an io.Reader that returns a stream of zero-bytes. type zeroReader struct{} func (zeroReader) Read(b []byte) (int, error) { - for i := range b { - b[i] = 0 - } + clear(b) return len(b), nil } @@ -47,13 +31,6 @@ func (f *FileImage) zero(d *rawDescriptor) error { return err } -// 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 - - return f.rw.Truncate(start) -} - // deleteOpts accumulates object deletion options. type deleteOpts struct { zero bool @@ -97,8 +74,6 @@ func OptDeleteWithTime(t time.Time) DeleteOpt { } } -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 @@ -125,24 +100,12 @@ func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error { return fmt.Errorf("%w", err) } - if do.compact && !f.isLast(d) { - return fmt.Errorf("%w", errCompactNotImplemented) - } - if do.zero { if err := f.zero(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() @@ -156,6 +119,14 @@ func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error { // Reset rawDescripter with empty struct *d = rawDescriptor{} + if do.compact { + f.h.DataSize = f.calculatedDataSize() + + if err := f.rw.Truncate(f.h.DataOffset + f.h.DataSize); err != nil { + return fmt.Errorf("%w", err) + } + } + if err := f.writeDescriptors(); err != nil { return fmt.Errorf("%w", err) } diff --git a/pkg/sif/delete_test.go b/pkg/sif/delete_test.go index 683e00dd..a7c2a165 100644 --- a/pkg/sif/delete_test.go +++ b/pkg/sif/delete_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved. +// Copyright (c) 2018-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. @@ -17,7 +17,7 @@ func TestDeleteObject(t *testing.T) { tests := []struct { name string createOpts []CreateOpt - id uint32 + ids []uint32 opts []DeleteOpt wantErr error }{ @@ -26,44 +26,104 @@ func TestDeleteObject(t *testing.T) { createOpts: []CreateOpt{ OptCreateDeterministic(), }, - id: 1, + ids: []uint32{1}, wantErr: ErrObjectNotFound, }, { - name: "Zero", + name: "Compact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{1, 2}, + opts: []DeleteOpt{ + OptDeleteCompact(true), + }, + }, + { + name: "OneZero", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), ), }, - id: 1, + ids: []uint32{1}, opts: []DeleteOpt{ OptDeleteZero(true), }, }, { - name: "Compact", + name: "OneCompact", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), ), }, - id: 1, + ids: []uint32{1}, opts: []DeleteOpt{ OptDeleteCompact(true), }, }, { - name: "ZeroCompact", + name: "OneZeroCompact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{1}, + opts: []DeleteOpt{ + OptDeleteZero(true), + OptDeleteCompact(true), + }, + }, + { + name: "TwoZero", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{2}, + opts: []DeleteOpt{ + OptDeleteZero(true), + }, + }, + { + name: "TwoCompact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{2}, + opts: []DeleteOpt{ + OptDeleteCompact(true), + }, + }, + { + name: "TwoZeroCompact", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), ), }, - id: 1, + ids: []uint32{2}, opts: []DeleteOpt{ OptDeleteZero(true), OptDeleteCompact(true), @@ -78,7 +138,7 @@ func TestDeleteObject(t *testing.T) { ), OptCreateWithTime(time.Unix(946702800, 0)), }, - id: 1, + ids: []uint32{1}, opts: []DeleteOpt{ OptDeleteDeterministic(), }, @@ -91,7 +151,7 @@ func TestDeleteObject(t *testing.T) { getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, - id: 1, + ids: []uint32{1}, opts: []DeleteOpt{ OptDeleteWithTime(time.Unix(946702800, 0)), }, @@ -106,7 +166,7 @@ func TestDeleteObject(t *testing.T) { ), ), }, - id: 1, + ids: []uint32{1}, }, } @@ -119,8 +179,10 @@ func TestDeleteObject(t *testing.T) { t.Fatal(err) } - if got, want := f.DeleteObject(tt.id, tt.opts...), tt.wantErr; !errors.Is(got, want) { - t.Errorf("got error %v, want %v", got, want) + for _, id := range tt.ids { + if got, want := f.DeleteObject(id, tt.opts...), tt.wantErr; !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } } if err := f.UnloadContainer(); err != nil { diff --git a/pkg/sif/testdata/TestDeleteObject/OneCompact.golden b/pkg/sif/testdata/TestDeleteObject/OneCompact.golden new file mode 100644 index 00000000..cb86f62d Binary files /dev/null and b/pkg/sif/testdata/TestDeleteObject/OneCompact.golden differ diff --git a/pkg/sif/testdata/TestDeleteObject/OneZero.golden b/pkg/sif/testdata/TestDeleteObject/OneZero.golden new file mode 100644 index 00000000..ec7f18be Binary files /dev/null and b/pkg/sif/testdata/TestDeleteObject/OneZero.golden differ diff --git a/pkg/sif/testdata/TestDeleteObject/OneZeroCompact.golden b/pkg/sif/testdata/TestDeleteObject/OneZeroCompact.golden new file mode 100644 index 00000000..ec7f18be Binary files /dev/null and b/pkg/sif/testdata/TestDeleteObject/OneZeroCompact.golden differ diff --git a/pkg/sif/testdata/TestDeleteObject/Zero.golden b/pkg/sif/testdata/TestDeleteObject/TwoCompact.golden similarity index 99% rename from pkg/sif/testdata/TestDeleteObject/Zero.golden rename to pkg/sif/testdata/TestDeleteObject/TwoCompact.golden index 036b3690..74b6489c 100644 Binary files a/pkg/sif/testdata/TestDeleteObject/Zero.golden and b/pkg/sif/testdata/TestDeleteObject/TwoCompact.golden differ diff --git a/pkg/sif/testdata/TestDeleteObject/TwoZero.golden b/pkg/sif/testdata/TestDeleteObject/TwoZero.golden new file mode 100644 index 00000000..f57ae8c2 Binary files /dev/null and b/pkg/sif/testdata/TestDeleteObject/TwoZero.golden differ diff --git a/pkg/sif/testdata/TestDeleteObject/ZeroCompact.golden b/pkg/sif/testdata/TestDeleteObject/TwoZeroCompact.golden similarity index 99% rename from pkg/sif/testdata/TestDeleteObject/ZeroCompact.golden rename to pkg/sif/testdata/TestDeleteObject/TwoZeroCompact.golden index 01584e24..74b6489c 100644 Binary files a/pkg/sif/testdata/TestDeleteObject/ZeroCompact.golden and b/pkg/sif/testdata/TestDeleteObject/TwoZeroCompact.golden differ diff --git a/pkg/sif/testdata/TestDeleteObjectAndAddObject/NoCompact.golden b/pkg/sif/testdata/TestDeleteObjectAndAddObject/NoCompact.golden index 7042b4ac..483ef82e 100644 Binary files a/pkg/sif/testdata/TestDeleteObjectAndAddObject/NoCompact.golden and b/pkg/sif/testdata/TestDeleteObjectAndAddObject/NoCompact.golden differ diff --git a/pkg/sif/testdata/TestDeleteObjectAndAddObject/Zero.golden b/pkg/sif/testdata/TestDeleteObjectAndAddObject/Zero.golden index bacfe7af..483ef82e 100644 Binary files a/pkg/sif/testdata/TestDeleteObjectAndAddObject/Zero.golden and b/pkg/sif/testdata/TestDeleteObjectAndAddObject/Zero.golden differ