Files
hakurei/internal/pkg/tar.go
Ophestra 5e59b6671c
Some checks failed
Test / Create distribution (push) Successful in 47s
Test / ShareFS (push) Failing after 54s
Test / Sandbox (race detector) (push) Failing after 1m10s
Test / Sandbox (push) Failing after 1m13s
Test / Hakurei (push) Failing after 1m30s
Test / Hakurei (race detector) (push) Failing after 1m37s
Test / Hpkg (push) Failing after 1m41s
Test / Flake checks (push) Has been skipped
internal/pkg: remove typeflag promotion loop
Expanding this enables sharing of code common between types.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-01-13 00:23:14 +09:00

240 lines
5.1 KiB
Go

package pkg
import (
"archive/tar"
"compress/bzip2"
"compress/gzip"
"encoding/binary"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"time"
"hakurei.app/container/check"
)
const (
// TarUncompressed denotes an uncompressed tarball.
TarUncompressed = iota
// TarGzip denotes a tarball compressed via [gzip].
TarGzip
// TarBzip2 denotes a tarball compressed via [bzip2].
TarBzip2
)
// A tarArtifact is an [Artifact] unpacking a tarball backed by a [File].
type tarArtifact struct {
// Caller-supplied backing tarball.
f Artifact
// Compression on top of the tarball.
compression uint64
}
// 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] backed by the supplied [Artifact] and
// compression method. The source [Artifact] must be compatible with
// [TContext.Open].
func NewTar(a Artifact, compression uint64) Artifact {
ta := tarArtifact{a, compression}
if s, ok := a.(fmt.Stringer); ok {
if name := s.String(); name != "" {
return &tarArtifactNamed{ta, name}
}
}
return &ta
}
// NewHTTPGetTar is abbreviation for NewHTTPGet passed to NewTar.
func NewHTTPGetTar(
hc *http.Client,
url string,
checksum Checksum,
compression uint64,
) Artifact {
return NewTar(NewHTTPGet(hc, url, checksum), compression)
}
// Kind returns the hardcoded [Kind] constant.
func (a *tarArtifact) Kind() Kind { return KindTar }
// Params returns compression encoded in little endian.
func (a *tarArtifact) Params() []byte {
return binary.LittleEndian.AppendUint64(nil, a.compression)
}
// Dependencies returns a slice containing the backing file.
func (a *tarArtifact) Dependencies() []Artifact {
return []Artifact{a.f}
}
// 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) {
temp := t.GetTempDir()
var tr io.ReadCloser
if tr, err = t.Open(a.f); err != nil {
return
}
defer func(f io.ReadCloser) {
closeErr := tr.Close()
if err == nil {
err = closeErr
}
closeErr = f.Close()
if err == nil {
err = closeErr
}
}(tr)
tr = io.NopCloser(tr)
switch a.compression {
case TarUncompressed:
break
case TarGzip:
if tr, err = gzip.NewReader(tr); err != nil {
return
}
break
case TarBzip2:
tr = io.NopCloser(bzip2.NewReader(tr))
break
default:
return os.ErrInvalid
}
type dirTargetPerm struct {
path *check.Absolute
mode fs.FileMode
}
var madeDirectories []dirTargetPerm
if err = os.MkdirAll(temp.String(), 0700); err != nil {
return
}
var header *tar.Header
r := tar.NewReader(tr)
for header, err = r.Next(); err == nil; header, err = r.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
}
}
pathname := temp.Append(header.Name)
if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir {
if err = os.MkdirAll(pathname.Dir().String(), 0700); err != nil {
return
}
}
switch typeflag {
case tar.TypeReg:
var f *os.File
if f, err = os.OpenFile(
pathname.String(),
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
header.FileInfo().Mode()&0500,
); err != nil {
return
}
if _, err = io.Copy(f, r); err != nil {
_ = f.Close()
return
} else if err = f.Close(); err != nil {
return
}
break
case tar.TypeLink:
if err = os.Link(header.Linkname, pathname.String()); err != nil {
return
}
break
case tar.TypeSymlink:
if err = os.Symlink(header.Linkname, pathname.String()); err != nil {
return
}
break
case tar.TypeDir:
madeDirectories = append(madeDirectories, dirTargetPerm{
path: pathname,
mode: header.FileInfo().Mode(),
})
if err = os.MkdirAll(pathname.String(), 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 = os.Chmod(e.path.String(), e.mode&0500); err != nil {
return
}
}
} else {
return
}
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
}