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