Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f91d449
feat: extract bins
upils Jun 30, 2026
1663446
refactor: rework approach
upils Jul 29, 2026
3435cee
refactor: refining
upils Jul 29, 2026
09a01bd
refactor: refining
upils Jul 29, 2026
8d87fae
docs: clean comments
upils Aug 19, 2026
1a38ccd
docs: remove mentions of packages in tarball pkg
upils Sep 1, 2026
ca8d6e8
Merge branch 'main' into support-bin-extract
upils Sep 3, 2026
ee8732a
style: refine naming and docs
upils Sep 3, 2026
ca72fd6
refactor: rework approach
upils Sep 8, 2026
dc8e94b
fix: clean superfluous import
upils Sep 8, 2026
f882d47
test: remove unused field for now
upils Sep 8, 2026
116752e
refactor: put deb DataReader back in deb package
upils Sep 9, 2026
e9c4d88
style: add missing trailing newline
upils Sep 9, 2026
b1ec390
style: refine naming
upils Sep 9, 2026
a8ad813
style: improve naming consistency
upils Sep 9, 2026
6ad029e
style: avoid ambiguity
upils Sep 9, 2026
150e904
refactor: rework approach with interface
upils Sep 10, 2026
791224b
refactor: refine interface
upils Sep 11, 2026
50b03d1
tests: polish tests
upils Sep 14, 2026
798ce59
docs: fix misleading docs
upils Sep 14, 2026
ec2c67c
refactor: rename to fit new abstraction
upils Sep 14, 2026
6f2fe07
docs: refine docs
upils Sep 16, 2026
436a161
fix: clarify interface following review
upils Oct 5, 2026
7afa609
fix: use a single default test package
upils Oct 5, 2026
95a45c1
fix: use better names
upils Oct 5, 2026
1a0d381
ci: rerun
upils Oct 5, 2026
eee0a4e
ci: rerun
upils Oct 12, 2026
1fc0c45
feat: rework heavily the approach
upils Oct 14, 2026
37df805
fix: clean unwanted change coming from main
upils Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/archive/archive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/canonical/chisel/internal/archive"
"github.com/canonical/chisel/internal/archive/testarchive"
"github.com/canonical/chisel/internal/deb"
"github.com/canonical/chisel/internal/tarball"
"github.com/canonical/chisel/internal/testutil"
)
Expand Down Expand Up @@ -1235,7 +1236,7 @@ func (s *S) testOpenArchiveArch(c *C, test realArchiveTest, arch string) {
c.Assert(info.Name, DeepEquals, test.pkg)
c.Assert(info.Arch, DeepEquals, arch)

err = tarball.Extract(pkg, &tarball.ExtractOptions{
err = tarball.Extract(deb.OpenTar(pkg), &tarball.ExtractOptions{
Package: test.pkg,
TargetDir: extractDir,
Extract: map[string][]tarball.ExtractInfo{
Expand Down
27 changes: 27 additions & 0 deletions internal/bin/extract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package bin

import (
"io"

"github.com/ulikunitz/xz"
)

// DataReader takes a Reader for a bin package (an XZ-compressed tarball) and
// returns a Reader to the inner tarball.
func DataReader(pkgReader io.Reader) (io.ReadCloser, error) {
xzReader, err := xz.NewReader(pkgReader)
if err != nil {
return nil, err
}
return io.NopCloser(xzReader), nil
}

func OpenTar(pkgReader io.ReadSeeker) func() (io.ReadCloser, error) {
return func() (io.ReadCloser, error) {
_, err := pkgReader.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
return DataReader(pkgReader)
}
}
41 changes: 41 additions & 0 deletions internal/bin/extract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package bin_test

import (
"archive/tar"
"bytes"

. "gopkg.in/check.v1"

"github.com/canonical/chisel/internal/bin"
"github.com/canonical/chisel/internal/testutil"
)

func (s *S) TestDataReader(c *C) {
data := testutil.MustMakeBin([]testutil.TarEntry{
testutil.Dir(0o755, "./"),
testutil.Reg(0o644, "./file", "content"),
})

dataReader, err := bin.DataReader(bytes.NewReader(data))
c.Assert(err, IsNil)
tarReader := tar.NewReader(dataReader)
_, err = tarReader.Next()
c.Assert(err, IsNil)
}

func (s *S) TestDataReaderInvalid(c *C) {
_, err := bin.DataReader(bytes.NewReader([]byte("not an xz stream")))
c.Assert(err, ErrorMatches, "xz.*")
}

func (s *S) TestOpenTar(c *C) {
data := testutil.MustMakeBin([]testutil.TarEntry{
testutil.Dir(0o755, "./"),
testutil.Reg(0o644, "./file", "content"),
})
pkgReader := testutil.ReadSeekNopCloser(bytes.NewReader(data))
openTar := bin.OpenTar(pkgReader)

// Each call must return a fresh stream over the same content.
testutil.CheckOpenTar(c, openTar)
}
53 changes: 53 additions & 0 deletions internal/bin/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package bin

import (
"fmt"
"sync"
)

// Avoid importing the log type information unnecessarily. There's a small cost
// associated with using an interface rather than the type. Depending on how
// often the logger is plugged in, it would be worth using the type instead.
type log_Logger interface {
Output(calldepth int, s string) error
}

var globalLoggerLock sync.Mutex
var globalLogger log_Logger
var globalDebug bool

// Specify the *log.Logger object where log messages should be sent to.
func SetLogger(logger log_Logger) {
globalLoggerLock.Lock()
globalLogger = logger
globalLoggerLock.Unlock()
}

// Enable the delivery of debug messages to the logger. Only meaningful
// if a logger is also set.
func SetDebug(debug bool) {
globalLoggerLock.Lock()
globalDebug = debug
globalLoggerLock.Unlock()
}

// logf sends to the logger registered via SetLogger the string resulting
// from running format and args through Sprintf.
func logf(format string, args ...any) {
globalLoggerLock.Lock()
defer globalLoggerLock.Unlock()
if globalLogger != nil {
globalLogger.Output(2, fmt.Sprintf(format, args...))
}
}

// debugf sends to the logger registered via SetLogger the string resulting
// from running format and args through Sprintf, but only if debugging was
// enabled via SetDebug.
func debugf(format string, args ...any) {
globalLoggerLock.Lock()
defer globalLoggerLock.Unlock()
if globalDebug && globalLogger != nil {
globalLogger.Output(2, fmt.Sprintf(format, args...))
}
}
13 changes: 13 additions & 0 deletions internal/bin/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package bin_test

import (
"testing"

. "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

type S struct{}

var _ = Suite(&S{})
10 changes: 10 additions & 0 deletions internal/deb/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,13 @@ func DataReader(pkgReader io.ReadSeeker) (io.ReadCloser, error) {

return dataReader, nil
}

func OpenTar(pkgReader io.ReadSeeker) func() (io.ReadCloser, error) {
return func() (io.ReadCloser, error) {
_, err := pkgReader.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
return DataReader(pkgReader)
}
}
18 changes: 18 additions & 0 deletions internal/deb/extract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package deb_test

import (
"bytes"

. "gopkg.in/check.v1"

"github.com/canonical/chisel/internal/deb"
"github.com/canonical/chisel/internal/testutil"
)

func (s *S) TestOpenTar(c *C) {
pkgReader := testutil.ReadSeekNopCloser(bytes.NewReader(testutil.PackageData["test-package"]))
openTar := deb.OpenTar(pkgReader)

// Each call must return a fresh stream over the same content.
testutil.CheckOpenTar(c, openTar)
}
12 changes: 6 additions & 6 deletions internal/slicer/slicer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/klauspost/compress/zstd"

"github.com/canonical/chisel/internal/archive"
"github.com/canonical/chisel/internal/deb"
"github.com/canonical/chisel/internal/fsutil"
"github.com/canonical/chisel/internal/manifestutil"
"github.com/canonical/chisel/internal/scripts"
Expand Down Expand Up @@ -146,7 +147,7 @@ func Run(options *RunOptions) error {
}

// Fetch all packages, using the selection order.
packages := make(map[string]io.ReadSeekCloser)
packages := make(map[string]tarball.OpenTarFunc)
var pkgInfos []manifestutil.PackageInfo
for _, slice := range options.Selection.Slices {
if packages[slice.Package] != nil {
Expand All @@ -158,7 +159,7 @@ func Run(options *RunOptions) error {
return err
}
defer reader.Close()
packages[slice.Package] = reader
packages[slice.Package] = deb.OpenTar(reader)
pkgInfos = append(pkgInfos, info)
}

Expand Down Expand Up @@ -235,17 +236,16 @@ func Run(options *RunOptions) error {

// Extract all packages, also using the selection order.
for _, slice := range options.Selection.Slices {
reader := packages[slice.Package]
if reader == nil {
openTar := packages[slice.Package]
if openTar == nil {
continue
}
err := tarball.Extract(reader, &tarball.ExtractOptions{
err := tarball.Extract(openTar, &tarball.ExtractOptions{
Package: slice.Package,
Extract: extract[slice.Package],
TargetDir: targetDir,
Create: create,
})
reader.Close()
packages[slice.Package] = nil
if err != nil {
return err
Expand Down
35 changes: 17 additions & 18 deletions internal/tarball/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"strings"
"syscall"

"github.com/canonical/chisel/internal/deb"
"github.com/canonical/chisel/internal/fsutil"
"github.com/canonical/chisel/internal/strdist"
)
Expand Down Expand Up @@ -58,7 +57,11 @@ func getValidOptions(options *ExtractOptions) (*ExtractOptions, error) {
return options, nil
}

func Extract(pkgReader io.ReadSeeker, options *ExtractOptions) (err error) {
// OpenTarFunc opens a fresh reader over a package's inner tar stream.
// Each call must return a stream from its start.
type OpenTarFunc func() (io.ReadCloser, error)

func Extract(openTar OpenTarFunc, options *ExtractOptions) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("cannot extract from package %q: %w", options.Package, err)
Expand All @@ -79,15 +82,15 @@ func Extract(pkgReader io.ReadSeeker, options *ExtractOptions) (err error) {
return err
}

return extractData(pkgReader, validOpts)
return extractEntries(openTar, validOpts)
}

func extractData(pkgReader io.ReadSeeker, options *ExtractOptions) error {
dataReader, err := deb.DataReader(pkgReader)
func extractEntries(openTar OpenTarFunc, options *ExtractOptions) error {
tarStream, err := openTar()
if err != nil {
return err
}
defer dataReader.Close()
defer tarStream.Close()

oldUmask := syscall.Umask(0)
defer func() {
Expand Down Expand Up @@ -117,10 +120,10 @@ func extractData(pkgReader io.ReadSeeker, options *ExtractOptions) error {
// create them with the permissions defined in the tarball.
//
// The assumption is that the tar entries of the parent directories appear
// before the entry for the file itself. This is the case for .deb files but
// not for all tarballs.
// before the entry for the file itself. This is the case for the tarballs
// produced by common packaging tools but not for all tarballs.
tarDirMode := make(map[string]fs.FileMode)
tarReader := tar.NewReader(dataReader)
tarReader := tar.NewReader(tarStream)
for {
tarHeader, err := tarReader.Next()
if err == io.EOF {
Expand Down Expand Up @@ -261,11 +264,7 @@ func extractData(pkgReader io.ReadSeeker, options *ExtractOptions) error {
ExtractOptions: options,
pendingLinks: pendingHardLinks,
}
_, err := pkgReader.Seek(0, io.SeekStart)
if err != nil {
return err
}
err = extractHardLinks(pkgReader, extractHardLinkOptions)
err := extractHardLinks(openTar, extractHardLinkOptions)
if err != nil {
return err
}
Expand Down Expand Up @@ -299,14 +298,14 @@ type extractHardLinkOptions struct {

// extractHardLinks iterates through the tarball a second time to extract the
// hard links that were not extracted in the first pass.
func extractHardLinks(pkgReader io.ReadSeeker, opts *extractHardLinkOptions) error {
dataReader, err := deb.DataReader(pkgReader)
func extractHardLinks(openTar OpenTarFunc, opts *extractHardLinkOptions) error {
tarStream, err := openTar()
if err != nil {
return err
}
defer dataReader.Close()
defer tarStream.Close()

tarReader := tar.NewReader(dataReader)
tarReader := tar.NewReader(tarStream)
for {
tarHeader, err := tarReader.Next()
if err == io.EOF {
Expand Down
Loading
Loading