diff --git a/cmd/chisel/cmd_debug_check_release_archives.go b/cmd/chisel/cmd_debug_check_release_archives.go index c9c6270e4..c7931d9a6 100644 --- a/cmd/chisel/cmd_debug_check_release_archives.go +++ b/cmd/chisel/cmd_debug_check_release_archives.go @@ -13,8 +13,8 @@ import ( "github.com/canonical/chisel/internal/archive" "github.com/canonical/chisel/internal/cache" - "github.com/canonical/chisel/internal/deb" "github.com/canonical/chisel/internal/setup" + "github.com/canonical/chisel/internal/tarball" ) var shortCheckReleaseArchivesHelp = "Check the release's archives" @@ -150,7 +150,7 @@ func computePathObservations(release *setup.Release, archives map[string]archive if err != nil { return nil, err } - dataReader, err := deb.DataReader(pkgReader) + dataReader, err := tarball.DebFormat.TarStream(pkgReader) if err != nil { return nil, err } diff --git a/internal/archive/archive_test.go b/internal/archive/archive_test.go index b89a8d71d..3a83d73d5 100644 --- a/internal/archive/archive_test.go +++ b/internal/archive/archive_test.go @@ -1237,6 +1237,7 @@ func (s *S) testOpenArchiveArch(c *C, test realArchiveTest, arch string) { err = tarball.Extract(pkg, &tarball.ExtractOptions{ Package: test.pkg, + Format: tarball.DebFormat, TargetDir: extractDir, Extract: map[string][]tarball.ExtractInfo{ fmt.Sprintf("/usr/share/doc/%s/copyright", test.pkg): { diff --git a/internal/deb/extract.go b/internal/deb/extract.go index bf543c2d8..88fc4ad56 100644 --- a/internal/deb/extract.go +++ b/internal/deb/extract.go @@ -12,7 +12,7 @@ import ( // DataReader takes a Reader for the ar file belonging to a Debian package and // returns a Reader to the inner tarball. -func DataReader(pkgReader io.ReadSeeker) (io.ReadCloser, error) { +func DataReader(pkgReader io.Reader) (io.ReadCloser, error) { arReader := ar.NewReader(pkgReader) var dataReader io.ReadCloser for dataReader == nil { diff --git a/internal/slicer/slicer.go b/internal/slicer/slicer.go index ce7911621..6c528c651 100644 --- a/internal/slicer/slicer.go +++ b/internal/slicer/slicer.go @@ -241,6 +241,7 @@ func Run(options *RunOptions) error { } err := tarball.Extract(reader, &tarball.ExtractOptions{ Package: slice.Package, + Format: tarball.DebFormat, Extract: extract[slice.Package], TargetDir: targetDir, Create: create, diff --git a/internal/tarball/extract.go b/internal/tarball/extract.go index 40db2cbac..a0c7af11a 100644 --- a/internal/tarball/extract.go +++ b/internal/tarball/extract.go @@ -12,13 +12,43 @@ import ( "strings" "syscall" + "github.com/ulikunitz/xz" + "github.com/canonical/chisel/internal/deb" "github.com/canonical/chisel/internal/fsutil" "github.com/canonical/chisel/internal/strdist" ) +// PkgFormat identifies the format of a package. +type PkgFormat string + +const ( + // DebFormat is the Debian package format: an ar archive holding a + // compressed data tarball. + DebFormat PkgFormat = "deb" + // BinFormat is the bin package format: a plain XZ-compressed tarball. + BinFormat PkgFormat = "bin" +) + +// TarStream returns a reader over the raw, uncompressed tar stream contained +// in the given package. The stream is not parsed. +func (format PkgFormat) TarStream(pkgReader io.Reader) (io.ReadCloser, error) { + switch format { + case DebFormat: + return deb.DataReader(pkgReader) + case BinFormat: + xzReader, err := xz.NewReader(pkgReader) + if err != nil { + return nil, err + } + return io.NopCloser(xzReader), nil + } + return nil, fmt.Errorf("internal error: unsupported package format: %q", format) +} + type ExtractOptions struct { Package string + Format PkgFormat TargetDir string Extract map[string][]ExtractInfo // Create can optionally be set to control the creation of extracted entries. @@ -83,11 +113,11 @@ func Extract(pkgReader io.ReadSeeker, options *ExtractOptions) (err error) { } func extractData(pkgReader io.ReadSeeker, options *ExtractOptions) error { - dataReader, err := deb.DataReader(pkgReader) + tarStream, err := options.Format.TarStream(pkgReader) if err != nil { return err } - defer dataReader.Close() + defer tarStream.Close() oldUmask := syscall.Umask(0) defer func() { @@ -117,10 +147,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 { @@ -300,13 +330,13 @@ 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) + tarStream, err := opts.Format.TarStream(pkgReader) 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 { diff --git a/internal/tarball/extract_test.go b/internal/tarball/extract_test.go index fd5eb6147..ed43414ef 100644 --- a/internal/tarball/extract_test.go +++ b/internal/tarball/extract_test.go @@ -496,6 +496,9 @@ func (s *S) TestExtract(c *C) { options := test.options options.Package = "test-package" options.TargetDir = dir + if options.Format == "" { + options.Format = tarball.DebFormat + } createdPaths := make(map[string]bool) options.Create = func(_ []tarball.ExtractInfo, o *fsutil.CreateOptions) error { relPath := filepath.Clean("/" + strings.TrimPrefix(o.Path, dir)) @@ -539,6 +542,7 @@ func (s *S) TestExtract(c *C) { var extractCreateCallbackTests = []struct { summary string pkgdata []byte + format tarball.PkgFormat options tarball.ExtractOptions calls map[string][]tarball.ExtractInfo }{{ @@ -605,6 +609,9 @@ func (s *S) TestExtractCreateCallback(c *C) { options := test.options options.Package = "test-package" options.TargetDir = dir + if options.Format == "" { + options.Format = tarball.DebFormat + } createExtractInfos := map[string][]tarball.ExtractInfo{} options.Create = func(extractInfos []tarball.ExtractInfo, o *fsutil.CreateOptions) error { if extractInfos == nil {