Repository: diskfs/go-diskfs
Pull request #193 (2023-12-12 20:05:47)

squashfs: fix off by one in directory counting
This was causing "corrupted directory, had 257 entries instead of max
256" errors, however this was because parseDirectoryHeader had already
added one to the count.


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..ce4cbf2 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
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)
+}