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 --- internal/pkg/archive.go | 411 ------------------------------------------------ 1 file changed, 411 deletions(-) delete mode 100644 internal/pkg/archive.go (limited to 'internal/pkg/archive.go') diff --git a/internal/pkg/archive.go b/internal/pkg/archive.go deleted file mode 100644 index ea4017d9..00000000 --- a/internal/pkg/archive.go +++ /dev/null @@ -1,411 +0,0 @@ -package pkg - -import ( - "crypto/sha512" - "encoding/binary" - "errors" - "fmt" - "io" - "io/fs" - "os" - "path/filepath" - "unsafe" - - "hakurei.app/check" -) - -/* -| mode uint32 | path_sz uint32 | -| data_sz uint64 | -| path string | -| data []byte | -*/ - -// An ArchiveHeader represents a single header in an archive. -type ArchiveHeader struct { - Mode fs.FileMode // file mode bits - Path string // pathname of the file - Size uint64 // size of data segment -} - -// Writer implements sequential writing of an archive. [Writer.WriteHeader] -// begins a new file with the provided [ArchiveHeader], and then Writer can be -// treated as an [io.Writer] to supply that file's data. -// -// It is the caller's responsibility to write entries in lexical order. -type Writer struct { - // Underlying writer. - w io.Writer - // Current header. - h ArchiveHeader - // Fixed-size header segment. - buf [wordSize * 2]byte - // Current position in data segment. - n uint64 -} - -// NewWriter returns the address of a new [Writer] writing to w. -func NewWriter(w io.Writer) *Writer { return &Writer{w: w} } - -var zero [wordSize]byte - -// padSize returns the padding size for aligning sz. -func padSize[T int | uint64](sz T) T { - return (wordSize - (sz)%wordSize) % wordSize -} - -// flush concludes writing to the current file and writes padding. -func (aw *Writer) flush() error { - if aw.h.Size > aw.n { - return fmt.Errorf("missed writing %d bytes", aw.h.Size-aw.n) - } else if aw.h.Size < aw.n { - return fmt.Errorf("wrote %d bytes beyond end of file", aw.n-aw.h.Size) - } - - if psz := padSize(aw.h.Size); psz != 0 { - if _, err := aw.w.Write(zero[:psz]); err != nil { - return err - } - } - - aw.n = 0 - return nil -} - -// WriteHeader writes h and begins accepting its corresponding file. -func (aw *Writer) WriteHeader(h *ArchiveHeader) error { - if err := aw.flush(); err != nil { - return err - } - - aw.h = *h - binary.LittleEndian.PutUint32(aw.buf[:], uint32(aw.h.Mode)) - binary.LittleEndian.PutUint32(aw.buf[wordSize/2:], uint32(len(aw.h.Path))) - binary.LittleEndian.PutUint64(aw.buf[wordSize:], aw.h.Size) - if _, err := aw.w.Write(aw.buf[:]); err != nil { - return err - } else if _, err = aw.w.Write( - unsafe.Slice(unsafe.StringData(aw.h.Path), len(aw.h.Path)), - ); err != nil { - return err - } else if psz := padSize(len(aw.h.Path)); psz != 0 { - if _, err = aw.w.Write(zero[:psz]); err != nil { - return err - } - } - return nil -} - -// Write writes p to the underlying writer and records the new position. Invalid -// positions are reported by WriteHeader and Close. -func (aw *Writer) Write(p []byte) (n int, err error) { - n, err = aw.w.Write(p) - aw.n += uint64(n) - return -} - -// Close concludes writing to the archive stream. -func (aw *Writer) Close() (err error) { - err = aw.flush() - aw.w = nil - return -} - -// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested -// and a nonlocal path is encountered in the stream. -var ErrInsecurePath = errors.New("insecure file path") - -// Reader implements sequential reading of an archive. [Reader.Next] advances to -// the next file in the archive (including the first), and then Reader can be -// treated as an [io.Reader] to access the file's data. -type Reader struct { - // Underlying reader. - r io.Reader - // Fixed-size header segment. - buf [wordSize * 2]byte - // Remaining bytes in current data segment. - n, pad uint64 -} - -// NewReader returns the address of a new [Reader] reading from r. -func NewReader(r io.Reader) *Reader { return &Reader{r: r} } - -// Next advances ar to the next entry. Remaining bytes of the current data -// segment are discarded. Advancing beyond the final entry returns [io.EOF]. -func (ar *Reader) Next() (*ArchiveHeader, error) { - if dsz := int64(ar.n + ar.pad); dsz > 0 { - if n, err := io.CopyN(io.Discard, ar.r, dsz); err != nil { - if errors.Is(err, io.EOF) && n != dsz { - err = io.ErrUnexpectedEOF - } - return nil, err - } - } - - if _, err := io.ReadFull(ar.r, ar.buf[:]); err != nil { - return nil, err - } - - h := ArchiveHeader{ - Mode: fs.FileMode(binary.LittleEndian.Uint32(ar.buf[:])), - Size: binary.LittleEndian.Uint64(ar.buf[wordSize:]), - } - pathSize := int(binary.LittleEndian.Uint32(ar.buf[wordSize/2:])) - pPathSize := alignSize(pathSize) - - buf := make([]byte, pPathSize) - if _, err := io.ReadFull(ar.r, buf); err != nil { - if errors.Is(err, io.EOF) { - err = io.ErrUnexpectedEOF - } - return nil, err - } - - h.Path = unsafe.String(unsafe.SliceData(buf), pathSize) - if !filepath.IsLocal(h.Path) { - return &h, ErrInsecurePath - } - - ar.n = h.Size - ar.pad = padSize(h.Size) - return &h, nil -} - -// Read implements [io.Reader] for the data segment of the current entry. -func (ar *Reader) Read(p []byte) (n int, err error) { - if uint64(len(p)) > ar.n { - p = p[:ar.n] - } - - if len(p) > 0 { - n, err = ar.r.Read(p) - ar.n -= uint64(n) - } - - switch err { - case io.EOF: - if ar.n > 0 { - return n, io.ErrUnexpectedEOF - } - - case nil: - if ar.n == 0 { - return n, io.EOF - } - } - return -} - -// Write writes a deterministic representation of the contents of fsys to w. -// The resulting data can be hashed to produce a deterministic checksum for the -// directory. -func Write(fsys fs.FS, root string, w io.Writer) error { - aw := NewWriter(w) - if err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - var fi fs.FileInfo - fi, err = d.Info() - if err != nil { - return err - } - - h := ArchiveHeader{ - Path: path, - Mode: fi.Mode(), - } - if h.Mode.IsRegular() { - h.Size = uint64(fi.Size()) - if err = aw.WriteHeader(&h); err != nil { - return err - } - - var r fs.File - r, err = fsys.Open(path) - if err != nil { - return err - } - _, err = io.Copy(aw, r) - if _err := r.Close(); err == nil { - err = _err - } - return err - } else if h.Mode&fs.ModeSymlink != 0 { - var newpath string - if newpath, err = fs.ReadLink(fsys, path); err != nil { - return err - } - - h.Size = uint64(len(newpath)) - if err = aw.WriteHeader(&h); err != nil { - return err - } - - _, err = aw.Write(unsafe.Slice(unsafe.StringData(newpath), len(newpath))) - return err - } else if !h.Mode.IsDir() { - return InvalidFileModeError(h.Mode) - } - return aw.WriteHeader(&h) - }); err != nil { - return err - } - return aw.Close() -} - -// SumFS saves checksum of the archive of fsys to the value pointed to by buf. -func SumFS(buf *Checksum, fsys fs.FS, root string) error { - h := sha512.New384() - if err := Write(fsys, root, h); err != nil { - return err - } - h.Sum(buf[:0]) - return nil -} - -// SumDir saves checksum of the archive of directory at pathname to the value -// pointed to by buf. -func SumDir(buf *Checksum, pathname *check.Absolute) error { - 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 -} - -var _ CuresExempt = archiveArtifact{} - -// 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 - }) -} - -// Inputs returns a slice containing the backing file. -func (a archiveArtifact) Inputs() []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 - } - }() - - type dirTargetPerm struct { - path string - mode fs.FileMode - } - var madeDirectories []dirTargetPerm - - 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 - } - - madeDirectories = append(madeDirectories, dirTargetPerm{ - path: header.Path, - mode: header.Mode, - }) - if err = root.Mkdir(header.Path, 0700); err != nil { - return - } - } else { - return InvalidFileModeError(header.Mode) - } - } - if errors.Is(err, io.EOF) { - err = nil - } - if err == nil { - for _, e := range madeDirectories { - if err = root.Chmod(e.path, e.mode.Perm()); err != nil { - return - } - } - } else { - return - } - return -} - -// CuresExempt exempts the cheap [KindArchive] implementation often found at -// the end of a [FileArtifact] pipeline. -func (archiveArtifact) CuresExempt() {} -- cgit v1.3.1