Repository: diskfs/go-diskfs
Pull request #194 (2023-12-12 20:06:59)

squashfs: sometimes missing xattrs seems to be marked with 0xffffffffffffffff
Using mksquashfs 1:4.5-3build1 and these flags causes the created
squash file not have xattrs but this is indicated by the index being
all 1s rather than the flag in the superblock.

mksquashfs $IMG $IMG.sqfs -comp zstd -Xcompression-level 3 -b 1M -no-xattrs -all-root


ncw: Rebased!

Patch:
diff --git a/filesystem/squashfs/compressor.go b/filesystem/squashfs/compressor.go
index 42c0819..4076a47 100644
--- a/filesystem/squashfs/compressor.go
+++ b/filesystem/squashfs/compressor.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io"
 
+	"github.com/klauspost/compress/zstd"
 	"github.com/pierrec/lz4/v4"
 	"github.com/ulikunitz/xz"
 	"github.com/ulikunitz/xz/lzma"
@@ -328,6 +329,32 @@ func (c *CompressorZstd) optionsBytes() []byte {
 func (c *CompressorZstd) flavour() compression {
 	return compressionZstd
 }
+func (c *CompressorZstd) compress(in []byte) ([]byte, error) {
+	var b bytes.Buffer
+	z, err := zstd.NewWriter(&b)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create zstd compressor: %w", err)
+	}
+	if _, err := z.Write(in); err != nil {
+		return nil, err
+	}
+	if err := z.Close(); err != nil {
+		return nil, err
+	}
+	return b.Bytes(), nil
+}
+func (c *CompressorZstd) decompress(in []byte) ([]byte, error) {
+	z, err := zstd.NewReader(nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create zstd decompressor: %w", err)
+	}
+	defer z.Close()
+	p, err := z.DecodeAll(in, nil)
+	if err != nil {
+		return nil, fmt.Errorf("error decompressing zstd: %w", err)
+	}
+	return p, nil
+}
 
 func newCompressor(flavour compression) (Compressor, error) {
 	var c Compressor
@@ -345,7 +372,7 @@ func newCompressor(flavour compression) (Compressor, error) {
 	case compressionLz4:
 		c = &CompressorLz4{}
 	case compressionZstd:
-		return nil, fmt.Errorf("zstd compression not yet supported")
+		c = &CompressorZstd{}
 	default:
 		return nil, fmt.Errorf("unknown compression type: %d", flavour)
 	}
diff --git a/filesystem/squashfs/directory.go b/filesystem/squashfs/directory.go
index 0f5bd8d..804d339 100644
--- a/filesystem/squashfs/directory.go
+++ b/filesystem/squashfs/directory.go
@@ -64,8 +64,8 @@ func parseDirectory(b []byte) (*directory, error) {
 		if err != nil {
 			return nil, fmt.Errorf("could not parse directory header: %v", err)
 		}
-		if directoryHeader.count+1 > maxDirEntries {
-			return nil, fmt.Errorf("corrupted directory, had %d entries instead of max %d", directoryHeader.count+1, maxDirEntries)
+		if directoryHeader.count > maxDirEntries {
+			return nil, fmt.Errorf("corrupted directory, had %d entries instead of max %d", directoryHeader.count, maxDirEntries)
 		}
 		pos += dirHeaderSize
 		for count := uint32(0); count < directoryHeader.count; count++ {
diff --git a/filesystem/squashfs/squashfs.go b/filesystem/squashfs/squashfs.go
index d67c61c..88ffbb1 100644
--- a/filesystem/squashfs/squashfs.go
+++ b/filesystem/squashfs/squashfs.go
@@ -147,7 +147,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) {
 	// create the compressor function we will use
 	compress, err := newCompressor(s.compression)
 	if err != nil {
-		return nil, fmt.Errorf("unable to create compressor")
+		return nil, fmt.Errorf("unable to create compressor: %v", err)
 	}
 
 	// load fragments
@@ -160,7 +160,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) {
 	var (
 		xattrs *xAttrTable
 	)
-	if !s.noXattrs {
+	if !s.noXattrs && s.xattrTableStart != 0xffff_ffff_ffff_ffff {
 		// xattr is right to the end of the disk
 		xattrs, err = readXattrsTable(s, file, compress)
 		if err != nil {
diff --git a/go.mod b/go.mod
index b77b8b3..b2a0a59 100644
--- a/go.mod
+++ b/go.mod
@@ -13,3 +13,5 @@ require (
 	golang.org/x/sys v0.5.0
 	gopkg.in/djherbis/times.v1 v1.3.0
 )
+
+require github.com/klauspost/compress v1.17.4 // indirect
diff --git a/go.sum b/go.sum
index 553f78d..2b4e172 100644
--- a/go.sum
+++ b/go.sum
@@ -7,6 +7,8 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
 github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
+github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
 github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
 github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=


Test patch:
diff --git a/filesystem/squashfs/compressor_internal_test.go b/filesystem/squashfs/compressor_internal_test.go
index d0a4efe..136e4e3 100644
--- a/filesystem/squashfs/compressor_internal_test.go
+++ b/filesystem/squashfs/compressor_internal_test.go
@@ -19,7 +19,7 @@ func TestNewCompressor(t *testing.T) {
 		{compressionLzo, nil, fmt.Errorf("LZO compression not yet supported")},
 		{compressionXz, &CompressorXz{}, nil},
 		{compressionLz4, &CompressorLz4{}, nil},
-		{compressionZstd, nil, fmt.Errorf("zstd compression not yet supported")},
+		{compressionZstd, &CompressorZstd{}, nil},
 		{100, nil, fmt.Errorf("unknown compression type")},
 	}
 
@@ -153,3 +153,19 @@ func TestCompressionLz4(t *testing.T) {
 	c := CompressorLz4{}
 	testCompressAndDecompress(t, &c, compressed)
 }
+func TestCompressionZstd(t *testing.T) {
+	compressed := []byte{
+		0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x00, 0x21, 0x03, 0x00, 0xde, 0xc9, 0x4e,
+		0xd0, 0xef, 0x19, 0xdb, 0x0a, 0x6a, 0x35, 0x26, 0x61, 0x86, 0x2d, 0xa0,
+		0x42, 0x18, 0xa8, 0x89, 0xe9, 0xc4, 0x7b, 0x1a, 0xc7, 0x85, 0x8e, 0xd6,
+		0x36, 0xd6, 0x83, 0x84, 0x21, 0xf4, 0x06, 0x38, 0x07, 0x7b, 0x33, 0x3f,
+		0x72, 0x4c, 0xae, 0xcd, 0xfd, 0xa0, 0xb0, 0x71, 0x2f, 0x64, 0x62, 0x62,
+		0x2f, 0xc3, 0x5f, 0xa1, 0x21, 0xc6, 0xbf, 0x2c, 0x39, 0xef, 0x56, 0x23,
+		0x61, 0xb0, 0x98, 0x84, 0xcd, 0x24, 0xc4, 0xbf, 0x30, 0xae, 0xd9, 0x9e,
+		0xb0, 0x7b, 0xc5, 0xa3, 0x8d, 0xf7, 0x4f, 0xb8, 0xdd, 0x7b, 0x77, 0xb6,
+		0x8c, 0x5a, 0x10, 0xa4, 0xce, 0xc2, 0x0a, 0x3d, 0x51, 0x23, 0x8c, 0x10,
+		0x1a, 0x18, 0x20, 0x19, 0x3b,
+	}
+	c := CompressorZstd{}
+	testCompressAndDecompress(t, &c, compressed)
+}