From c2d900ec74e03e9c782cfe7b7ce06ff62cda6601 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 29 Aug 2026 18:58:06 +0900 Subject: pkg: move from internal Closes #43. Signed-off-by: Ophestra --- pkg/tar.go | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 pkg/tar.go (limited to 'pkg/tar.go') diff --git a/pkg/tar.go b/pkg/tar.go new file mode 100644 index 00000000..9a8c4b76 --- /dev/null +++ b/pkg/tar.go @@ -0,0 +1,226 @@ +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 +} + +var _ CuresExempt = new(tarArtifact) + +// 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] prefixed with unpack. +func (a *tarArtifactNamed) String() string { return "unpack-" + a.name } + +// 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 + }) +} + +// Inputs returns a slice containing the backing file. +func (a *tarArtifact) Inputs() []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.TypeChar: + t.GetMessage().Verbosef( + "%sskipping character device %d, %d %q%s", + t.cache.sgrWarn, header.Devmajor, header.Devminor, header.Name, t.cache.sgrRes, + ) + continue + + 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 +} + +// CuresExempt exempts the cheap [KindTar] implementation often at the end of a +// [FileArtifact] pipeline. +func (*tarArtifact) CuresExempt() {} -- cgit v1.3.1