All checks were successful
Test / Create distribution (push) Successful in 1m30s
Test / Sandbox (push) Successful in 5m13s
Test / Hakurei (push) Successful in 7m56s
Test / ShareFS (push) Successful in 7m57s
Test / Hakurei (race detector) (push) Successful in 10m12s
Test / Sandbox (race detector) (push) Successful in 3m3s
Test / Flake checks (push) Successful in 1m47s
This is replaced by decompressArtifact and is no longer necessary. Signed-off-by: Ophestra <cat@gensokyo.uk>
214 lines
4.5 KiB
Go
214 lines
4.5 KiB
Go
package pkg
|
|
|
|
import (
|
|
"archive/tar"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// A tarArtifact is an [Artifact] unpacking a tarball backed by a [FileArtifact].
|
|
type tarArtifact struct {
|
|
// Caller-supplied backing tarball.
|
|
f Artifact
|
|
}
|
|
|
|
// tarArtifactNamed embeds tarArtifact for a [fmt.Stringer] tarball.
|
|
type tarArtifactNamed struct {
|
|
tarArtifact
|
|
// Copied from tarArtifact.f.
|
|
name string
|
|
}
|
|
|
|
var _ fmt.Stringer = new(tarArtifactNamed)
|
|
|
|
// String returns the name of the underlying [Artifact] suffixed with unpack.
|
|
func (a *tarArtifactNamed) String() string { return a.name + "-unpack" }
|
|
|
|
// NewTar returns a new [Artifact] unpacking the tar stream produced by the
|
|
// backing [Artifact]. The source [Artifact] must be a [FileArtifact].
|
|
func NewTar(a Artifact) Artifact {
|
|
ta := tarArtifact{a}
|
|
if s, ok := a.(fmt.Stringer); ok {
|
|
if name := s.String(); name != "" {
|
|
return &tarArtifactNamed{ta, name}
|
|
}
|
|
}
|
|
return &ta
|
|
}
|
|
|
|
// Kind returns the hardcoded [Kind] constant.
|
|
func (a *tarArtifact) Kind() Kind { return KindTar }
|
|
|
|
// Params is a noop.
|
|
func (a *tarArtifact) Params(*IContext) {}
|
|
|
|
func init() {
|
|
register(KindTar, func(r *IRReader) Artifact {
|
|
a := NewTar(r.Next())
|
|
if _, ok := r.Finalise(); ok {
|
|
panic(ErrUnexpectedChecksum)
|
|
}
|
|
return a
|
|
})
|
|
}
|
|
|
|
// Dependencies returns a slice containing the backing file.
|
|
func (a *tarArtifact) Dependencies() []Artifact {
|
|
return []Artifact{a.f}
|
|
}
|
|
|
|
// IsExclusive returns false: decompressor and tar reader are fully sequential.
|
|
func (a *tarArtifact) IsExclusive() bool { return false }
|
|
|
|
// A DisallowedTypeflagError describes a disallowed typeflag encountered while
|
|
// unpacking a tarball.
|
|
type DisallowedTypeflagError byte
|
|
|
|
func (e DisallowedTypeflagError) Error() string {
|
|
return "disallowed typeflag '" + string(e) + "'"
|
|
}
|
|
|
|
// Cure cures the [Artifact], producing a directory located at work.
|
|
func (a *tarArtifact) 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
|
|
}
|
|
}()
|
|
|
|
type dirTargetPerm struct {
|
|
path string
|
|
mode fs.FileMode
|
|
}
|
|
var madeDirectories []dirTargetPerm
|
|
|
|
if err = os.MkdirAll(t.GetTempDir().String(), 0700); err != nil {
|
|
return
|
|
}
|
|
var root *os.Root
|
|
if root, err = os.OpenRoot(t.GetTempDir().String()); err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
closeErr := root.Close()
|
|
if err == nil {
|
|
err = closeErr
|
|
}
|
|
}()
|
|
|
|
var header *tar.Header
|
|
tr := tar.NewReader(r)
|
|
for header, err = tr.Next(); err == nil; header, err = tr.Next() {
|
|
typeflag := header.Typeflag
|
|
if typeflag == 0 {
|
|
if len(header.Name) > 0 && header.Name[len(header.Name)-1] == '/' {
|
|
typeflag = tar.TypeDir
|
|
} else {
|
|
typeflag = tar.TypeReg
|
|
}
|
|
}
|
|
|
|
if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir {
|
|
if err = root.MkdirAll(filepath.Dir(header.Name), 0700); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
switch typeflag {
|
|
case tar.TypeReg:
|
|
var f *os.File
|
|
if f, err = root.OpenFile(
|
|
header.Name,
|
|
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
|
header.FileInfo().Mode()&0500,
|
|
); err != nil {
|
|
return
|
|
}
|
|
if _, err = io.Copy(f, tr); err != nil {
|
|
_ = f.Close()
|
|
return
|
|
} else if err = f.Close(); err != nil {
|
|
return
|
|
}
|
|
break
|
|
|
|
case tar.TypeLink:
|
|
if err = root.Link(
|
|
header.Linkname,
|
|
header.Name,
|
|
); err != nil {
|
|
return
|
|
}
|
|
break
|
|
|
|
case tar.TypeSymlink:
|
|
if err = root.Symlink(
|
|
header.Linkname,
|
|
header.Name,
|
|
); err != nil {
|
|
return
|
|
}
|
|
break
|
|
|
|
case tar.TypeDir:
|
|
madeDirectories = append(madeDirectories, dirTargetPerm{
|
|
path: header.Name,
|
|
mode: header.FileInfo().Mode(),
|
|
})
|
|
if err = root.MkdirAll(header.Name, 0700); err != nil {
|
|
return
|
|
}
|
|
break
|
|
|
|
case tar.TypeXGlobalHeader:
|
|
continue // ignore
|
|
|
|
default:
|
|
return DisallowedTypeflagError(typeflag)
|
|
}
|
|
}
|
|
if errors.Is(err, io.EOF) {
|
|
err = nil
|
|
}
|
|
if err == nil {
|
|
for _, e := range madeDirectories {
|
|
if err = root.Chmod(e.path, e.mode&0500); err != nil {
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
return
|
|
}
|
|
|
|
temp := t.GetTempDir()
|
|
if err = os.Chmod(temp.String(), 0700); err != nil {
|
|
return
|
|
}
|
|
|
|
var entries []os.DirEntry
|
|
if entries, err = os.ReadDir(temp.String()); err != nil {
|
|
return
|
|
}
|
|
|
|
if len(entries) == 1 && entries[0].IsDir() {
|
|
p := temp.Append(entries[0].Name())
|
|
if err = os.Chmod(p.String(), 0700); err != nil {
|
|
return
|
|
}
|
|
err = os.Rename(p.String(), t.GetWorkDir().String())
|
|
} else {
|
|
err = os.Rename(temp.String(), t.GetWorkDir().String())
|
|
}
|
|
return
|
|
}
|