squashfs - squashfs_test.go
		
     1  package squashfs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"io/fs"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  	"testing/fstest"
    13  	"time"
    14  )
    15  
    16  var (
    17  	contentsA = "my contents"
    18  	contentsB = strings.Repeat("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"+contentsA, 1024)
    19  	contentsC = strings.Repeat("ABCDEFGHIJKLMNOP", 8192)
    20  	contentsD = strings.Repeat("ZYXWVUTSRQPONMLK", 16384)
    21  	contentsE = contentsA + contentsD
    22  
    23  	timestamp = time.Unix(1234567, 0)
    24  )
    25  
    26  type testFn func(*SquashFS) error
    27  
    28  func test(t *testing.T, runFSTest bool, tests []testFn, children ...child) {
    29  	t.Helper()
    30  
    31  	sqfs, err := buildSquashFS(t, children...)
    32  	if err != nil {
    33  		t.Fatalf("unexpected error creating squashfs file: %s", err)
    34  	}
    35  
    36  	f, err := os.Open(sqfs)
    37  	if err != nil {
    38  		t.Fatalf("unexpected error opening squashfs file: %s", err)
    39  	}
    40  	defer f.Close()
    41  
    42  	sfs, err := Open(f)
    43  	if err != nil {
    44  		t.Fatalf("unexpected error opening squashfs reader: %s", err)
    45  	}
    46  
    47  	if runFSTest {
    48  		if err := fstest.TestFS(sfs, ".required"); err != nil {
    49  			t.Fatal(err)
    50  		}
    51  	}
    52  
    53  	for n, test := range tests {
    54  		if err := test(sfs); err != nil {
    55  			t.Errorf("test %d: %s", n+1, err)
    56  		}
    57  	}
    58  }
    59  
    60  func readSqfsFile(sfs fs.FS, path, expectation string) error {
    61  	f, err := sfs.Open(path)
    62  	if err != nil {
    63  		return fmt.Errorf("unexpected error opening file in squashfs FS: %w", err)
    64  	}
    65  
    66  	contents, err := io.ReadAll(f)
    67  	if err != nil {
    68  		return fmt.Errorf("unexpected error reading file in squashfs FS: %w", err)
    69  	}
    70  
    71  	if string(contents) != expectation {
    72  		return fmt.Errorf("expected to read %q, got %q", expectation, contents)
    73  	}
    74  
    75  	return nil
    76  }
    77  
    78  func TestOpenRead(t *testing.T) {
    79  	test(
    80  		t,
    81  		true,
    82  		[]testFn{
    83  			func(sfs *SquashFS) error {
    84  				return readSqfsFile(sfs, filepath.Join("dirA", "fileA"), contentsA)
    85  			},
    86  			func(sfs *SquashFS) error {
    87  				return readSqfsFile(sfs, filepath.Join("dirA", "fileB"), contentsB)
    88  			},
    89  			func(sfs *SquashFS) error {
    90  				return readSqfsFile(sfs, filepath.Join("dirA", "fileC"), contentsC)
    91  			},
    92  			func(sfs *SquashFS) error {
    93  				return readSqfsFile(sfs, filepath.Join("dirA", "fileD"), contentsD)
    94  			},
    95  			func(sfs *SquashFS) error {
    96  				return readSqfsFile(sfs, filepath.Join("dirA", "fileE"), contentsE)
    97  			},
    98  		},
    99  		dirData("dirA", []child{
   100  			fileData("fileA", contentsA),
   101  			fileData("fileB", contentsB),
   102  			fileData("fileC", contentsC),
   103  			fileData("fileD", contentsD),
   104  			fileData("fileE", contentsE),
   105  		}),
   106  	)
   107  }
   108  
   109  var offsetReadTests = [...]struct {
   110  	Start, Length int64
   111  	Expectation   string
   112  }{
   113  	{
   114  		0, 10,
   115  		contentsE[:10],
   116  	},
   117  	{
   118  		0, 100,
   119  		contentsE[:100],
   120  	},
   121  	{
   122  		0, 1000,
   123  		contentsE[:1000],
   124  	},
   125  	{
   126  		100, 10,
   127  		contentsE[100:110],
   128  	},
   129  	{
   130  		100, 100,
   131  		contentsE[100:200],
   132  	},
   133  	{
   134  		100, 1000,
   135  		contentsE[100:1100],
   136  	},
   137  	{
   138  		0, 1 << 15,
   139  		contentsE[:1<<15],
   140  	},
   141  	{
   142  		1, 1 << 15,
   143  		contentsE[1 : 1+1<<15],
   144  	},
   145  	{
   146  		int64(len(contentsE)) - 1000, 1000,
   147  		contentsE[len(contentsE)-1000:],
   148  	},
   149  }
   150  
   151  func TestOpenReadAt(t *testing.T) {
   152  	var buf [1 << 15]byte
   153  
   154  	test(
   155  		t,
   156  		true,
   157  		[]testFn{
   158  			func(sfs *SquashFS) error {
   159  				a, err := sfs.Open("dirA/fileE")
   160  				if err != nil {
   161  					return fmt.Errorf("unexpected error opening file in squashfs FS: %w", err)
   162  				}
   163  
   164  				r, ok := a.(io.ReaderAt)
   165  				if !ok {
   166  					return fmt.Errorf("didn't get io.ReaderAt")
   167  				}
   168  
   169  				for n, test := range offsetReadTests {
   170  					m, err := r.ReadAt(buf[:test.Length], test.Start)
   171  					if err != nil {
   172  						return fmt.Errorf("test %d: %w", n+1, err)
   173  					}
   174  
   175  					if out := string(buf[:m]); out != test.Expectation {
   176  						return fmt.Errorf("test %d: expecting to read %q (%d), got %q (%d)", n+1, test.Expectation, len(test.Expectation), out, m)
   177  					}
   178  				}
   179  
   180  				return nil
   181  			},
   182  		},
   183  		dirData("dirA", []child{
   184  			fileData("fileE", contentsE),
   185  		}),
   186  	)
   187  }
   188  
   189  func TestSeek(t *testing.T) {
   190  	var buf [1 << 15]byte
   191  
   192  	test(
   193  		t,
   194  		true,
   195  		[]testFn{
   196  			func(sfs *SquashFS) error {
   197  				a, err := sfs.Open("dirA/fileE")
   198  				if err != nil {
   199  					return fmt.Errorf("unexpected error opening file in squashfs FS: %w", err)
   200  				}
   201  
   202  				r, ok := a.(io.ReadSeeker)
   203  				if !ok {
   204  					return fmt.Errorf("didn't get io.ReaderAt")
   205  				}
   206  
   207  				for n, test := range offsetReadTests {
   208  					for s, seek := range [...]struct {
   209  						Offset int64
   210  						Whence int
   211  					}{
   212  						{
   213  							test.Start,
   214  							io.SeekStart,
   215  						},
   216  						{
   217  							-test.Length,
   218  							io.SeekCurrent,
   219  						},
   220  						{
   221  							test.Start - int64(len(contentsE)),
   222  							io.SeekEnd,
   223  						},
   224  					} {
   225  						p, err := r.Seek(seek.Offset, seek.Whence)
   226  						if err != nil {
   227  							return fmt.Errorf("test %d.%d: %w", n+1, s+1, err)
   228  						}
   229  
   230  						if p != test.Start {
   231  							return fmt.Errorf("test %d.%d: expecting to be at byte %d, actually at %d", n+1, s+1, test.Start, p)
   232  						}
   233  
   234  						m, err := r.Read(buf[:test.Length])
   235  						if err != nil {
   236  							return fmt.Errorf("test %d.%d: %w", n+1, s+1, err)
   237  						}
   238  
   239  						if out := string(buf[:m]); out != test.Expectation {
   240  							return fmt.Errorf("test %d.%d: expecting to read %q (%d), got %q (%d)", s+1, n+1, test.Expectation, len(test.Expectation), out, m)
   241  						}
   242  					}
   243  				}
   244  
   245  				return nil
   246  			},
   247  		},
   248  		dirData("dirA", []child{
   249  			fileData("fileE", contentsE),
   250  		}),
   251  	)
   252  }
   253  
   254  func TestStat(t *testing.T) {
   255  	const (
   256  		symD = "../dirC/fileB"
   257  		symE = "/dirC/fileB"
   258  	)
   259  
   260  	test(
   261  		t,
   262  		false,
   263  		[]testFn{
   264  			func(sfs *SquashFS) error {
   265  				stats, err := sfs.Stat(".")
   266  				if err != nil {
   267  					return fmt.Errorf("unexpected error stat'ing root: %w", err)
   268  				}
   269  
   270  				if !stats.IsDir() {
   271  					return fmt.Errorf("expecting stat for root dir to be a dir")
   272  				}
   273  
   274  				if m := stats.Mode(); m&0o777 != 0o777 {
   275  					return fmt.Errorf("expecting perms 777, got %s", m)
   276  				}
   277  
   278  				return nil
   279  			},
   280  			func(sfs *SquashFS) error {
   281  				stats, err := sfs.Stat("dirA")
   282  				if err != nil {
   283  					return fmt.Errorf("unexpected error stat'ing dir: %w", err)
   284  				}
   285  
   286  				if !stats.IsDir() {
   287  					return fmt.Errorf("expecting stat for dir to be a dir")
   288  				}
   289  
   290  				if m := stats.Mode(); m&0o555 != 0o555 {
   291  					return fmt.Errorf("expecting perms 555, got %s", m)
   292  				}
   293  
   294  				return nil
   295  			},
   296  			func(sfs *SquashFS) error {
   297  				stats, err := sfs.Stat("dirB/fileA")
   298  				if err != nil {
   299  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   300  				}
   301  
   302  				if stats.IsDir() {
   303  					return fmt.Errorf("expecting stat for file to be not a dir")
   304  				}
   305  
   306  				if size := stats.Size(); size != int64(len(contentsA)) {
   307  					return fmt.Errorf("expecting size for file to be %d, got %d", len(contentsA), size)
   308  				}
   309  
   310  				if m := stats.Mode(); m != 0o600 {
   311  					return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o777), m)
   312  				}
   313  
   314  				if time := stats.ModTime(); time.Sub(timestamp) != 0 {
   315  					return fmt.Errorf("expecting modtime %s, got %s", timestamp, time)
   316  				}
   317  
   318  				return nil
   319  			},
   320  			func(sfs *SquashFS) error {
   321  				stats, err := sfs.Stat("dirC/fileC")
   322  				if err != nil {
   323  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   324  				}
   325  
   326  				if m := stats.Mode(); m != 0o123 {
   327  					return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
   328  				}
   329  
   330  				return nil
   331  			},
   332  			func(sfs *SquashFS) error {
   333  				stats, err := sfs.Stat("dirD/fileD")
   334  				if err != nil {
   335  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   336  				}
   337  
   338  				if m := stats.Mode(); m != 0o123 {
   339  					return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
   340  				}
   341  
   342  				return nil
   343  			},
   344  			func(sfs *SquashFS) error {
   345  				stats, err := sfs.Stat("dirD/fileE")
   346  				if err != nil {
   347  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   348  				}
   349  
   350  				if m := stats.Mode(); m != 0o123 {
   351  					return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
   352  				}
   353  
   354  				return nil
   355  			},
   356  			func(sfs *SquashFS) error {
   357  				stats, err := sfs.LStat("dirD/fileE")
   358  				if err != nil {
   359  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   360  				}
   361  
   362  				expected := fs.ModeSymlink | fs.ModePerm
   363  
   364  				if m := stats.Mode(); m != expected {
   365  					return fmt.Errorf("expecting perms %s, got %s", expected, m)
   366  				}
   367  
   368  				return nil
   369  			},
   370  			func(sfs *SquashFS) error {
   371  				stats, err := sfs.Stat("dirE/fileB")
   372  				if err != nil {
   373  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   374  				}
   375  
   376  				if m := stats.Mode(); m != 0o123 {
   377  					return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
   378  				}
   379  
   380  				return nil
   381  			},
   382  			func(sfs *SquashFS) error {
   383  				stats, err := sfs.LStat("dirE/fileB")
   384  				if err != nil {
   385  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   386  				}
   387  
   388  				if m := stats.Mode(); m != 0o123 {
   389  					return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
   390  				}
   391  
   392  				return nil
   393  			},
   394  			func(sfs *SquashFS) error {
   395  				sym, err := sfs.Readlink("dirD/fileD")
   396  				if err != nil {
   397  					return fmt.Errorf("unexpected error readlink'ing file: %w", err)
   398  				}
   399  
   400  				if sym != symD {
   401  					return fmt.Errorf("expecting symlink dest %q, got %q", symD, sym)
   402  				}
   403  
   404  				return nil
   405  			},
   406  			func(sfs *SquashFS) error {
   407  				sym, err := sfs.Readlink("dirD/fileE")
   408  				if err != nil {
   409  					return fmt.Errorf("unexpected error readlink'ing file: %w", err)
   410  				}
   411  
   412  				if sym != symE {
   413  					return fmt.Errorf("expecting symlink dest %q, got %q", symE, sym)
   414  				}
   415  
   416  				return nil
   417  			},
   418  			func(sfs *SquashFS) error {
   419  				stats, err := sfs.Stat("dirA")
   420  				if err != nil {
   421  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   422  				}
   423  
   424  				dir, ok := stats.(dirStat)
   425  				if !ok {
   426  					return fmt.Errorf("expecting dir type, got: %t", stats)
   427  				}
   428  
   429  				if dir.uid != 0 || dir.gid != 0 {
   430  					return fmt.Errorf("expecting uid %d and gid %d, got %d and %d", 0, 0, dir.uid, dir.gid)
   431  				}
   432  
   433  				return nil
   434  			},
   435  			func(sfs *SquashFS) error {
   436  				stats, err := sfs.Stat("dirB/fileA")
   437  				if err != nil {
   438  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   439  				}
   440  
   441  				file, ok := stats.(fileStat)
   442  				if !ok {
   443  					return fmt.Errorf("expecting dir type, got: %t", stats)
   444  				}
   445  
   446  				if file.uid != 1000 || file.gid != 1000 {
   447  					return fmt.Errorf("expecting uid %d and gid %d, got %d and %d", 1000, 1000, file.uid, file.gid)
   448  				}
   449  
   450  				return nil
   451  			},
   452  			func(sfs *SquashFS) error {
   453  				stats, err := sfs.Stat("dirC/fileB")
   454  				if err != nil {
   455  					return fmt.Errorf("unexpected error stat'ing file: %w", err)
   456  				}
   457  
   458  				file, ok := stats.(fileStat)
   459  				if !ok {
   460  					return fmt.Errorf("expecting dir type, got: %t", stats)
   461  				}
   462  
   463  				if file.uid != 123 || file.gid != 456 {
   464  					return fmt.Errorf("expecting uid %d and gid %d, got %d and %d", 123, 456, file.uid, file.gid)
   465  				}
   466  
   467  				return nil
   468  			},
   469  		},
   470  		dirData("dirA", []child{}, chmod(0o555)),
   471  		dirData("dirB", []child{
   472  			fileData("fileA", contentsA, chmod(0o600), modtime(timestamp), owner(1000, 1000)),
   473  		}),
   474  		dirData("dirC", []child{
   475  			fileData("fileB", contentsA, chmod(0o123), owner(123, 456)),
   476  			symlink("fileC", "fileB", chmod(0o321)),
   477  		}),
   478  		dirData("dirD", []child{
   479  			symlink("fileD", symD, chmod(0o231)),
   480  			symlink("fileE", symE, chmod(0o132)),
   481  		}),
   482  		symlink("dirE", "dirC"),
   483  	)
   484  }
   485  
   486  func TestReadDir(t *testing.T) {
   487  	test(
   488  		t,
   489  		true,
   490  		[]testFn{
   491  			func(sfs *SquashFS) error {
   492  				entries, err := sfs.ReadDir("dirA")
   493  				if err != nil {
   494  					return err
   495  				}
   496  
   497  				if len(entries) != 0 {
   498  					return fmt.Errorf("expecting no entries, got %v", entries)
   499  				}
   500  
   501  				return nil
   502  			},
   503  			func(sfs *SquashFS) error {
   504  				entries, err := sfs.ReadDir("dirB")
   505  				if err != nil {
   506  					return err
   507  				}
   508  
   509  				if len(entries) != 1 {
   510  					return fmt.Errorf("expecting 1 entry, got %d", len(entries))
   511  				}
   512  
   513  				if name := entries[0].Name(); name != "childA" {
   514  					return fmt.Errorf("expecting entry to be %q, got %q", "childA", name)
   515  				}
   516  
   517  				return nil
   518  			},
   519  			func(sfs *SquashFS) error {
   520  				entries, err := sfs.ReadDir("dirC")
   521  				if err != nil {
   522  					return err
   523  				}
   524  
   525  				if len(entries) != 3 {
   526  					return fmt.Errorf("expecting 3 entries, got %d", len(entries))
   527  				}
   528  
   529  				for n, child := range [...]string{"childA", "childB", "childC"} {
   530  					if name := entries[n].Name(); name != child {
   531  						return fmt.Errorf("expecting entry %d to be %q, got %q", n+1, child, name)
   532  					}
   533  				}
   534  
   535  				return nil
   536  			},
   537  		},
   538  		dirData("dirA", []child{}),
   539  		dirData("dirB", []child{
   540  			fileData("childA", ""),
   541  		}),
   542  		dirData("dirC", []child{
   543  			dirData("childA", []child{}),
   544  			fileData("childB", ""),
   545  			symlink("childC", "childB"),
   546  		}, chmod(0o432)),
   547  	)
   548  }
   549  
   550  func TestReadlink(t *testing.T) {
   551  	test(
   552  		t,
   553  		false,
   554  		[]testFn{
   555  			func(sfs *SquashFS) error {
   556  				sym, err := sfs.ReadLink("childA")
   557  				if !errors.Is(err, fs.ErrInvalid) {
   558  					return fmt.Errorf("expecting error fs.ErrInvalid, got %s", err)
   559  				} else if sym != "" {
   560  					return fmt.Errorf("expecting an empty string, got %q", sym)
   561  				}
   562  
   563  				return nil
   564  			},
   565  			func(sfs *SquashFS) error {
   566  				const expected = "childA"
   567  
   568  				sym, err := sfs.ReadLink("symB")
   569  				if err != nil {
   570  					return fmt.Errorf("got unexpected error: %s", err)
   571  				} else if sym != expected {
   572  					return fmt.Errorf("expecting an %s, got %q", expected, sym)
   573  				}
   574  
   575  				return nil
   576  			},
   577  			func(sfs *SquashFS) error {
   578  				const expected = "/not/exist"
   579  
   580  				sym, err := sfs.ReadLink("symC")
   581  				if err != nil {
   582  					return fmt.Errorf("got unexpected error: %s", err)
   583  				} else if sym != expected {
   584  					return fmt.Errorf("expecting an %s, got %q", expected, sym)
   585  				}
   586  
   587  				return nil
   588  			},
   589  			func(sfs *SquashFS) error {
   590  				const expected = "symE"
   591  
   592  				sym, err := sfs.ReadLink("dirD/symE")
   593  				if err != nil {
   594  					return fmt.Errorf("got unexpected error: %s", err)
   595  				} else if sym != expected {
   596  					return fmt.Errorf("expecting an %s, got %q", expected, sym)
   597  				}
   598  
   599  				return nil
   600  			},
   601  		},
   602  		fileData("childA", ""),
   603  		symlink("symB", "childA"),
   604  		symlink("symC", "/not/exist"),
   605  		dirData("dirD", []child{
   606  			symlink("symE", "symE"),
   607  		}),
   608  	)
   609  }
   610