internal/pkg: archive unpack artifact
All checks were successful
Test / Create distribution (push) Successful in 4m0s
Test / Sandbox (push) Successful in 9m28s
Test / Sandbox (race detector) (push) Successful in 13m22s
Test / Hakurei (push) Successful in 13m27s
Test / ShareFS (push) Successful in 13m57s
Test / Hakurei (race detector) (push) Successful in 15m52s
Test / Flake checks (push) Successful in 2m37s
All checks were successful
Test / Create distribution (push) Successful in 4m0s
Test / Sandbox (push) Successful in 9m28s
Test / Sandbox (race detector) (push) Successful in 13m22s
Test / Hakurei (push) Successful in 13m27s
Test / ShareFS (push) Successful in 13m57s
Test / Hakurei (race detector) (push) Successful in 15m52s
Test / Flake checks (push) Successful in 2m37s
This unpacks an internal/pkg archive stream used in the upcoming mirror service. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -277,3 +277,117 @@ func SumFS(buf *Checksum, fsys fs.FS, root string) error {
|
|||||||
func SumDir(buf *Checksum, pathname *check.Absolute) error {
|
func SumDir(buf *Checksum, pathname *check.Absolute) error {
|
||||||
return SumFS(buf, os.DirFS(pathname.String()), ".")
|
return SumFS(buf, os.DirFS(pathname.String()), ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// archiveArtifact is an [Artifact] unpacking an archive supported by [Reader]
|
||||||
|
// backed by a [FileArtifact].
|
||||||
|
type archiveArtifact struct {
|
||||||
|
// Caller-supplied backing archive.
|
||||||
|
f Artifact
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArchive returns a new [Artifact] backed by the supplied [Artifact]. The
|
||||||
|
// source [Artifact] must be a [FileArtifact] and produce a stream compatible
|
||||||
|
// with [Reader].
|
||||||
|
func NewArchive(a Artifact) Artifact {
|
||||||
|
return archiveArtifact{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind returns the hardcoded [Kind] constant.
|
||||||
|
func (archiveArtifact) Kind() Kind { return KindArchive }
|
||||||
|
|
||||||
|
// Params is a noop.
|
||||||
|
func (archiveArtifact) Params(*IContext) {}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register(KindArchive, func(r *IRReader) Artifact {
|
||||||
|
a := NewArchive(r.Next())
|
||||||
|
if _, ok := r.Finalise(); ok {
|
||||||
|
panic(ErrUnexpectedChecksum)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dependencies returns a slice containing the backing file.
|
||||||
|
func (a archiveArtifact) Dependencies() []Artifact {
|
||||||
|
return []Artifact{a.f}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExclusive returns false: [Reader] is fully sequential.
|
||||||
|
func (archiveArtifact) IsExclusive() bool { return false }
|
||||||
|
|
||||||
|
// Cure cures the [Artifact], producing a directory located at work.
|
||||||
|
func (a archiveArtifact) Cure(t *TContext) (err error) {
|
||||||
|
var r io.ReadCloser
|
||||||
|
if r, err = t.Open(a.f); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
closeErr := r.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = os.MkdirAll(t.GetWorkDir().String(), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var root *os.Root
|
||||||
|
if root, err = os.OpenRoot(t.GetWorkDir().String()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeErr := root.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var header *ArchiveHeader
|
||||||
|
ar := NewReader(r)
|
||||||
|
for header, err = ar.Next(); err == nil; header, err = ar.Next() {
|
||||||
|
if header.Mode.IsRegular() {
|
||||||
|
var f *os.File
|
||||||
|
if f, err = root.OpenFile(
|
||||||
|
header.Path,
|
||||||
|
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
||||||
|
header.Mode.Perm(),
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(f, ar); err != nil {
|
||||||
|
_ = f.Close()
|
||||||
|
return
|
||||||
|
} else if err = f.Close(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if header.Mode&fs.ModeSymlink != 0 {
|
||||||
|
var p []byte
|
||||||
|
if p, err = io.ReadAll(ar); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = root.Symlink(
|
||||||
|
unsafe.String(unsafe.SliceData(p), len(p)),
|
||||||
|
header.Path,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if header.Mode.IsDir() {
|
||||||
|
if header.Path == "." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = root.Mkdir(header.Path, header.Mode.Perm()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return InvalidFileModeError(header.Mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"maps"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,6 +170,46 @@ var archiveTestdata = fstest.MapFS{
|
|||||||
"block/uevent": {Mode: 0600},
|
"block/uevent": {Mode: 0600},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArchiveArtifact(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
want := maps.Clone(archiveTestdata)
|
||||||
|
want["."].Mode = fs.ModeDir | 0500
|
||||||
|
|
||||||
|
checkWithCache(t, []cacheTestCase{
|
||||||
|
{"unpack", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cureMany(t, c, []cureStep{
|
||||||
|
{"sample", pkg.NewArchive(
|
||||||
|
pkg.NewFile("", buf.Bytes()),
|
||||||
|
), ignorePathname, expectsFS(want), nil},
|
||||||
|
})
|
||||||
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block/uevent": {Mode: 0600},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/empty": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/3oYyAbRJ_we7AgWo1BRcRcnxXFk3mAQ0Qui2nGQMi8GIJNJQtvUC6P2IeoA5mbjD": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkArchiveRead(b *testing.B) {
|
func BenchmarkArchiveRead(b *testing.B) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
{"file", pkg.NewFile("stub", []byte("stub"))},
|
{"file", pkg.NewFile("stub", []byte("stub"))},
|
||||||
|
|
||||||
{"decompress", pkg.NewDecompress(pkg.NewFile("", []byte{0}), pkg.Bzip2)},
|
{"decompress", pkg.NewDecompress(pkg.NewFile("", []byte{0}), pkg.Bzip2)},
|
||||||
|
|
||||||
|
{"archive", pkg.NewArchive(pkg.NewFile("", []byte{0}))},
|
||||||
}
|
}
|
||||||
testCasesCache := make([]cacheTestCase, len(testCases))
|
testCasesCache := make([]cacheTestCase, len(testCases))
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
|
|||||||
@@ -510,6 +510,8 @@ const (
|
|||||||
KindFile
|
KindFile
|
||||||
// KindDecompress is the kind of [Artifact] returned by [NewDecompress].
|
// KindDecompress is the kind of [Artifact] returned by [NewDecompress].
|
||||||
KindDecompress
|
KindDecompress
|
||||||
|
// KindArchive is the kind of [Artifact] returned by [NewArchive].
|
||||||
|
KindArchive
|
||||||
|
|
||||||
// _kindEnd is the total number of kinds and does not denote a kind.
|
// _kindEnd is the total number of kinds and does not denote a kind.
|
||||||
_kindEnd
|
_kindEnd
|
||||||
|
|||||||
Reference in New Issue
Block a user