diff --git a/README.md b/README.md index d1e8893..d2f4b6f 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree * Reading inodes * Reading directories * Basic gzip compression (Shouldn't be too hard to implement other, but for right now, this works) +* Listing all files via a string slice # Not Working (Yet). Roughly in order. @@ -31,4 +32,4 @@ Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree # Where I'm at. -* I CAN READ THE ENTIRE DIRECTORY!!!!! (This is a big ol' step) \ No newline at end of file +* Started work on fragments \ No newline at end of file diff --git a/compression.go b/compression.go index 27a1f35..74ab900 100644 --- a/compression.go +++ b/compression.go @@ -8,7 +8,6 @@ import ( type Decompressor interface { Decompress(io.Reader) ([]byte, error) - DecompressCopy(*io.Writer, *io.SectionReader) error } type ZlibDecompressor struct{} @@ -25,15 +24,3 @@ func (z *ZlibDecompressor) Decompress(r io.Reader) ([]byte, error) { } return data.Bytes(), nil } - -func (z *ZlibDecompressor) DecompressCopy(w *io.Writer, r *io.SectionReader) error { - rdr, err := zlib.NewReader(r) - if err != nil { - return err - } - _, err = io.Copy(*w, rdr) - if err != nil { - return err - } - return err -} diff --git a/fragment.go b/fragment.go new file mode 100644 index 0000000..868c294 --- /dev/null +++ b/fragment.go @@ -0,0 +1,48 @@ +package squashfs + +import ( + "encoding/binary" + "errors" + "io" + + "github.com/CalebQ42/GoSquashfs/internal/inode" +) + +type FragmentEntryRaw struct { + Start uint64 + Size uint32 +} + +type FragmentEntry struct { + start uint64 + size uint32 + compressed bool +} + +//NewFragmentEntry reads a fragment entry from the given io.Reader. +func (r *Reader) NewFragmentEntry(rdr *io.Reader) (*FragmentEntry, error) { + var entry FragmentEntry + var raw FragmentEntryRaw + err := binary.Read(*rdr, binary.LittleEndian, &raw) + if err != nil { + return nil, err + } + entry.start = raw.Start + entry.compressed = raw.Size&0x1000000 == 0x1000000 + entry.size = raw.Size &^ 0x1000000 + return &entry, nil +} + +//GetFragmentFromInode returns the fragment data for a given inode +func (r *Reader) GetFragmentFromInode(in *inode.Inode) ([]byte, error) { + if in.Type != inode.BasicFileType { + return nil, errors.New("Only basic file is supported right now") + } + bf := in.Info.(inode.BasicFile) + var size uint32 + if bf.Init.BlockStart == 0 { + size = bf.Init.Size + } else { + size = bf.BlockSizes + } +} diff --git a/internal/inode/process.go b/internal/inode/process.go index 8645867..05875d2 100644 --- a/internal/inode/process.go +++ b/internal/inode/process.go @@ -15,11 +15,11 @@ type Inode struct { } //ProcessInode tries to read an inode from the BlockReader -func ProcessInode(br io.Reader, blockSize uint32) (Inode, error) { +func ProcessInode(br io.Reader, blockSize uint32) (*Inode, error) { var head Header err := binary.Read(br, binary.LittleEndian, &head) if err != nil { - return Inode{}, err + return nil, err } var info interface{} switch head.InodeType { @@ -27,97 +27,97 @@ func ProcessInode(br io.Reader, blockSize uint32) (Inode, error) { var inode BasicDirectory err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode case BasicFileType: inode, err := NewBasicFile(br, blockSize) if err != nil { - return Inode{}, err + return nil, err } info = inode case BasicSymlinkType: inode, err := NewBasicSymlink(br) if err != nil { - return Inode{}, err + return nil, err } info = inode case BasicBlockDeviceType: var inode BasicDevice err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode case BasicCharDeviceType: var inode BasicDevice err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode case BasicFifoType: var inode BasicIPC err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode case BasicSocketType: var inode BasicIPC err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode case ExtDirType: inode, err := NewExtendedDirectory(br) if err != nil { - return Inode{}, err + return nil, err } info = inode case ExtFileType: inode, err := NewExtendedFile(br, blockSize) if err != nil { - return Inode{}, err + return nil, err } info = inode case ExtSymlinkType: inode, err := NewExtendedSymlink(br) if err != nil { - return Inode{}, err + return nil, err } info = inode case ExtBlockDeviceType: var inode ExtendedDevice err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode case ExtCharDeviceType: var inode ExtendedDevice err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode case ExtFifoType: var inode ExtendedIPC err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode case ExtSocketType: var inode ExtendedIPC err = binary.Read(br, binary.LittleEndian, &inode) if err != nil { - return Inode{}, err + return nil, err } info = inode } - return Inode{ + return &Inode{ Type: int(head.InodeType), Header: head, Info: info, diff --git a/squashfsreader.go b/squashfsreader.go index 54e7180..ef0b9f5 100644 --- a/squashfsreader.go +++ b/squashfsreader.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "strings" "github.com/CalebQ42/GoSquashfs/internal/directory" "github.com/CalebQ42/GoSquashfs/internal/inode" @@ -14,6 +15,17 @@ const ( magic = 0x73717368 ) +var ( + //ErrNoMagic is returned if the magic number in the superblock isn't correct. + ErrNoMagic = errors.New("Magic number doesn't match. Either isn't a squashfs or corrupted") + //ErrIncompatibleCompression is returned if the compression type in the superblock doesn't work. + ErrIncompatibleCompression = errors.New("Compression type unsupported") + //ErrCompressorOptions is returned if compressor options is present. It's not currently supported. + ErrCompressorOptions = errors.New("Compressor options is not currently supported") +) + +//Reader processes and reads a squashfs archive. +//TODO: Give a way to actually read files :P type Reader struct { r io.ReaderAt super Superblock @@ -22,6 +34,7 @@ type Reader struct { dirs []*directory.Directory } +//NewSquashfsReader returns a new squashfs.Reader from an io.ReaderAt func NewSquashfsReader(r io.ReaderAt) (*Reader, error) { var rdr Reader rdr.r = r @@ -30,25 +43,43 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) { return nil, err } if rdr.super.Magic != magic { - return nil, errors.New("doesn't have magic number, probably isn't a squashfs") + return nil, ErrNoMagic } rdr.flags = rdr.super.GetFlags() switch rdr.super.CompressionType { case gzipCompression: rdr.decompressor = &ZlibDecompressor{} default: - return nil, errors.New("Unsupported compression type") + return nil, ErrIncompatibleCompression } if rdr.flags.CompressorOptions { //TODO: parse compressor options - fmt.Println("Compressor options is NOT currently supported") - return nil, errors.New("Has compressor options") + return nil, ErrCompressorOptions } return &rdr, nil } +//GetFilesList returns a list of ALL files in the squashfs, going down every folder. +//Paths that terminate in a folder end with / +func (r *Reader) GetFilesList() ([]string, error) { + inoderdr, err := r.NewBlockReaderFromInodeRef(r.super.RootInodeRef) + if err != nil { + return nil, err + } + i, err := inode.ProcessInode(inoderdr, r.super.BlockSize) + if err != nil { + return nil, err + } + paths, err := r.readDir(i) + if err != nil { + return nil, err + } + return paths, nil +} + +//readDir returns a list of all decendents of a given inode. Inode given MUST be a directory type. func (r *Reader) readDir(i *inode.Inode) (paths []string, err error) { - dir, err := r.ReadDirFromInode(*i) + dir, err := r.ReadDirFromInode(i) if err != nil { return } @@ -77,20 +108,10 @@ func (r *Reader) readDir(i *inode.Inode) (paths []string, err error) { } func (r *Reader) readDirTable() error { - inoderdr, err := r.NewBlockReaderFromInodeRef(r.super.RootInodeRef) + paths, err := r.GetFilesList() if err != nil { return err } - i, err := inode.ProcessInode(inoderdr, r.super.BlockSize) - if err != nil { - return err - } - paths, err := r.readDir(&i) - if err != nil { - return err - } - for _, path := range paths { - fmt.Println(path) - } + fmt.Println(strings.Join(paths, "\n")) return nil } diff --git a/utils.go b/utils.go index c44d48e..e1770e9 100644 --- a/utils.go +++ b/utils.go @@ -3,11 +3,17 @@ package squashfs import ( "errors" "io" + "strings" "github.com/CalebQ42/GoSquashfs/internal/directory" "github.com/CalebQ42/GoSquashfs/internal/inode" ) +var ( + //ErrNotFound means that the given path is NOT present in the archive + ErrNotFound = errors.New("Path not found") +) + //ProcessInodeRef processes an inode reference and returns two values // //The first value is the inode table offset. AKA, it's where the metadata block of the inode STARTS relative to the inode table. @@ -19,7 +25,9 @@ func processInodeRef(inodeRef uint64) (tableOffset uint64, metaOffset uint64) { return } -func (r *Reader) ReadDirFromInode(i inode.Inode) (*directory.Directory, error) { +//ReadDirFromInode returns a fully populated directory.Directory from a given inode.Inode. +//If the given inode is not a directory it returns an error. +func (r *Reader) ReadDirFromInode(i *inode.Inode) (*directory.Directory, error) { var offset uint32 var metaOffset uint16 var size uint16 @@ -50,6 +58,7 @@ func (r *Reader) ReadDirFromInode(i inode.Inode) (*directory.Directory, error) { return dir, nil } +//GetInodeFromEntry returns the inode associated with a given directory.Entry func (r *Reader) GetInodeFromEntry(en *directory.Entry) (*inode.Inode, error) { br, err := r.NewBlockReader(int64(r.super.InodeTableStart + uint64(en.Header.InodeOffset))) if err != nil { @@ -63,5 +72,45 @@ func (r *Reader) GetInodeFromEntry(en *directory.Entry) (*inode.Inode, error) { if err != nil { return nil, err } - return &i, nil + return i, nil +} + +//GetInodeFromPath returns the inode at the given path, relative to root. +//The given path can start or without "/". +func (r *Reader) GetInodeFromPath(path string) (*inode.Inode, error) { + path = strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/") + pathDirs := strings.Split(path, "/") + rdr, err := r.NewBlockReaderFromInodeRef(r.super.RootInodeRef) + if err != nil { + return nil, err + } + curInodeDir, err := inode.ProcessInode(rdr, r.super.BlockSize) + if err != nil { + return nil, err + } + for depth := 0; depth < len(pathDirs); depth++ { + if curInodeDir.Type != inode.BasicDirectoryType && curInodeDir.Type != inode.ExtDirType { + return nil, ErrNotFound + } + dir, err := r.ReadDirFromInode(curInodeDir) + if err != nil { + return nil, err + } + for _, entry := range dir.Entries { + if entry.Name == pathDirs[depth] { + if depth == len(pathDirs)-1 { + in, err := r.GetInodeFromEntry(&entry) + if err != nil { + return nil, err + } + return in, nil + } + curInodeDir, err = r.GetInodeFromEntry(&entry) + if err != nil { + return nil, err + } + } + } + } + return nil, ErrNotFound }