Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: improve compaction logic #373

Merged
merged 2 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion pkg/sif/create.go
Original file line number Diff line number Diff line change
@@ -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 <[email protected]> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
Expand Down Expand Up @@ -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")
Expand All @@ -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
}
Expand Down
49 changes: 10 additions & 39 deletions pkg/sif/delete.go
Original file line number Diff line number Diff line change
@@ -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 <[email protected]> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
Expand All @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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()

Expand All @@ -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)
}
Expand Down
90 changes: 76 additions & 14 deletions pkg/sif/delete_test.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,7 +17,7 @@ func TestDeleteObject(t *testing.T) {
tests := []struct {
name string
createOpts []CreateOpt
id uint32
ids []uint32
opts []DeleteOpt
wantErr error
}{
Expand All @@ -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),
Expand All @@ -78,7 +138,7 @@ func TestDeleteObject(t *testing.T) {
),
OptCreateWithTime(time.Unix(946702800, 0)),
},
id: 1,
ids: []uint32{1},
opts: []DeleteOpt{
OptDeleteDeterministic(),
},
Expand All @@ -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)),
},
Expand All @@ -106,7 +166,7 @@ func TestDeleteObject(t *testing.T) {
),
),
},
id: 1,
ids: []uint32{1},
},
}

Expand All @@ -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 {
Expand Down
Binary file not shown.
Binary file added pkg/sif/testdata/TestDeleteObject/OneZero.golden
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added pkg/sif/testdata/TestDeleteObject/TwoZero.golden
Binary file not shown.
Binary file not shown.
Binary file modified pkg/sif/testdata/TestDeleteObjectAndAddObject/NoCompact.golden
Binary file not shown.
Binary file modified pkg/sif/testdata/TestDeleteObjectAndAddObject/Zero.golden
Binary file not shown.