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 = 1 << 24 // 16MB 12 13 // The SquashFS type implements many of the FS interfaces, such as: 14 // fs.FS 15 // fs.ReadFileFS 16 // fs.ReadDirFS 17 // fs.StatFS 18 // 19 // and has additional methods for dealing with symlinks. 20 type SquashFS struct { 21 superblock superblock 22 reader io.ReaderAt 23 24 blockCache blockCache 25 } 26 27 // Open opens the named file for reading. 28 func (s *SquashFS) Open(path string) (fs.File, error) { 29 f, err := s.open(path) 30 if err != nil { 31 return nil, &fs.PathError{ 32 Op: "open", 33 Path: path, 34 Err: err, 35 } 36 } 37 38 return f, nil 39 } 40 41 func (s *SquashFS) open(path string) (fs.File, error) { 42 f, err := s.resolve(path, true) 43 if err != nil { 44 return nil, err 45 } 46 47 switch f := f.(type) { 48 case fileStat: 49 return &file{ 50 squashfs: s, 51 file: f, 52 }, nil 53 case dirStat: 54 return s.newDir(f) 55 } 56 57 return nil, fs.ErrInvalid 58 } 59 60 // ReadFile return the byte contents of the named file. 61 func (s *SquashFS) ReadFile(name string) ([]byte, error) { 62 d, err := s.readFile(name) 63 if err != nil { 64 return nil, &fs.PathError{ 65 Op: "readfile", 66 Path: name, 67 Err: err, 68 } 69 } 70 71 return d, nil 72 } 73 74 func (s *SquashFS) readFile(name string) ([]byte, error) { 75 f, err := s.Open(name) 76 if err != nil { 77 return nil, err 78 } 79 80 ff, ok := f.(*file) 81 if !ok { 82 return nil, fs.ErrInvalid 83 } 84 85 buf := make([]byte, ff.file.fileSize) 86 87 if _, err = ff.read(buf); err != nil && !errors.Is(err, io.EOF) { 88 return nil, err 89 } 90 91 return buf, nil 92 } 93 94 func (s *SquashFS) ReadLink(name string) (string, error) { 95 fi, err := s.resolve(name, false) 96 if err != nil { 97 return "", &fs.PathError{ 98 Op: "readlink", 99 Path: name, 100 Err: err, 101 } 102 } 103 104 sym, ok := fi.(symlinkStat) 105 if !ok { 106 return "", &fs.PathError{ 107 Op: "readlink", 108 Path: name, 109 Err: fs.ErrInvalid, 110 } 111 } 112 113 return sym.targetPath, nil 114 } 115 116 // ReadDir returns a sorted list of directory entries for the named directory. 117 func (s *SquashFS) ReadDir(name string) ([]fs.DirEntry, error) { 118 de, err := s.readDir(name) 119 if err != nil { 120 return nil, &fs.PathError{ 121 Op: "readdir", 122 Path: name, 123 Err: err, 124 } 125 } 126 127 return de, nil 128 } 129 130 func (s *SquashFS) readDir(name string) ([]fs.DirEntry, error) { 131 d, err := s.open(name) 132 if err != nil { 133 return nil, err 134 } 135 136 dd, ok := d.(*dir) 137 if !ok { 138 return nil, fs.ErrInvalid 139 } 140 141 return dd.ReadDir(-1) 142 } 143 144 // Open reads the passed io.ReaderAt as a SquashFS image, returning a fs.FS 145 // implementation. 146 // 147 // The returned fs.FS, and any files opened from it will cease to work if the 148 // io.ReaderAt is closed. 149 func Open(r io.ReaderAt) (*SquashFS, error) { 150 return OpenWithCacheSize(r, defaultCacheSize) 151 } 152 153 // OpenWithCacheSize acts like Open, but allows a custom cache size, which 154 // normally defaults to 16MB. 155 func OpenWithCacheSize(r io.ReaderAt, cacheSize int) (*SquashFS, error) { 156 var sb superblock 157 if err := sb.readFrom(io.NewSectionReader(r, 0, headerLength)); err != nil { 158 return nil, fmt.Errorf("error reading superblock: %w", err) 159 } 160 161 return &SquashFS{ 162 superblock: sb, 163 reader: r, 164 blockCache: newBlockCache(cacheSize), 165 }, nil 166 } 167 168 // Stat returns a FileInfo describing the name file. 169 func (s *SquashFS) Stat(path string) (fs.FileInfo, error) { 170 fi, err := s.resolve(path, true) 171 if err != nil { 172 return nil, &fs.PathError{ 173 Op: "stat", 174 Path: path, 175 Err: err, 176 } 177 } 178 179 return fi, nil 180 } 181 182 // Lstat returns a FileInfo describing the named file. If the file is a 183 // symbolic link, the returned FileInfo describes the symbolic link. 184 func (s *SquashFS) LStat(path string) (fs.FileInfo, error) { 185 fi, err := s.resolve(path, false) 186 if err != nil { 187 return nil, &fs.PathError{ 188 Op: "lstat", 189 Path: path, 190 Err: err, 191 } 192 } 193 194 return fi, nil 195 } 196 197 // Readlink returns the destination of the named symbolic link. 198 func (s *SquashFS) Readlink(path string) (string, error) { 199 fi, err := s.resolve(path, false) 200 if err != nil { 201 return "", &fs.PathError{ 202 Op: "readlink", 203 Path: path, 204 Err: err, 205 } 206 } 207 208 sym, ok := fi.(symlinkStat) 209 if !ok { 210 return "", &fs.PathError{ 211 Op: "readlink", 212 Path: path, 213 Err: fs.ErrInvalid, 214 } 215 } 216 217 return sym.targetPath, nil 218 } 219