1 // Package squashfs is a SquashFS reader and writer using fs.FS 2 package squashfs // import "vimagination.zapto.org/squashfs" 3 4 import ( 5 "errors" 6 "fmt" 7 "io" 8 "io/fs" 9 ) 10 11 const defaultCacheSize = 1024 12 13 type squashfs struct { 14 superblock superblock 15 reader io.ReaderAt 16 17 blockCache blockCache 18 } 19 20 func (s *squashfs) Open(path string) (fs.File, error) { 21 f, err := s.resolve(path, true) 22 if err != nil { 23 return nil, err 24 } 25 26 switch f := f.(type) { 27 case fileStat: 28 return &file{ 29 squashfs: s, 30 file: f, 31 }, nil 32 case dirStat: 33 return s.newDir(f) 34 } 35 36 return nil, fs.ErrInvalid 37 } 38 39 func (s *squashfs) ReadFile(name string) ([]byte, error) { 40 f, err := s.Open(name) 41 if err != nil { 42 return nil, err 43 } 44 45 ff, ok := f.(*file) 46 if !ok { 47 return nil, fs.ErrInvalid 48 } 49 50 buf := make([]byte, ff.file.fileSize) 51 52 if _, err = ff.read(buf); err != nil && !errors.Is(err, io.EOF) { 53 return nil, err 54 } 55 56 return buf, nil 57 } 58 59 func (s *squashfs) ReadDir(name string) ([]fs.DirEntry, error) { 60 d, err := s.Open(name) 61 if err != nil { 62 return nil, err 63 } 64 65 dd, ok := d.(*dir) 66 if !ok { 67 return nil, fs.ErrInvalid 68 } 69 70 return dd.ReadDir(-1) 71 } 72 73 type FS interface { 74 fs.ReadFileFS 75 fs.ReadDirFS 76 fs.StatFS 77 78 // Lstat returns a FileInfo describing the named file. If the file is a 79 // symbolic link, the returned FileInfo describes the symbolic link. 80 LStat(name string) (fs.FileInfo, error) 81 } 82 83 // Open reads the passed io.ReaderAt as a SquashFS image, returning a fs.FS 84 // implementation. 85 // 86 // The returned fs.FS, and any files opened from it will cease to work if the 87 // io.ReaderAt is closed. 88 func Open(r io.ReaderAt) (FS, error) { 89 return OpenWithCacheSize(r, defaultCacheSize) 90 } 91 92 // OpenWithCacheSize acts like Open, but allows a custom cache size, which 93 // normally defaults to 1024. 94 func OpenWithCacheSize(r io.ReaderAt, cacheSize uint) (FS, error) { 95 var sb superblock 96 if err := sb.readFrom(io.NewSectionReader(r, 0, headerLength)); err != nil { 97 return nil, fmt.Errorf("error reading superblock: %w", err) 98 } 99 100 return &squashfs{ 101 superblock: sb, 102 reader: r, 103 blockCache: newBlockCache(cacheSize), 104 }, nil 105 } 106 107 func (s *squashfs) Stat(path string) (fs.FileInfo, error) { 108 return s.resolve(path, true) 109 } 110 111 func (s *squashfs) LStat(path string) (fs.FileInfo, error) { 112 return s.resolve(path, false) 113 } 114