vorteil/vorteil

View on GitHub
pkg/vdecompiler/fs.go

Summary

Maintainability
B
4 hrs
Test Coverage
package vdecompiler

/**
 * SPDX-License-Identifier: Apache-2.0
 * Copyright 2020 vorteil.io Pty Ltd
 */

import (
    "bytes"
    "encoding/binary"
    "errors"
    "fmt"
    "io"
    "path/filepath"
    "strings"

    "github.com/vorteil/vorteil/pkg/ext"
    "github.com/vorteil/vorteil/pkg/vimg"
)

type fsInfo struct {
    superblock *ext.Superblock
    bgdt       []*ext.BlockGroupDescriptorTableEntry
}

func (iio *IO) readSuperblock(index int) (*ext.Superblock, error) {

    entry, err := iio.GPTEntry(UTF16toString(vimg.RootPartitionName))
    if err != nil {
        return nil, err
    }

    var bpg, bs int64
    if index > 0 {
        bpg = int64(iio.fs.superblock.BlocksPerGroup)
        bs = int64(1024 << iio.fs.superblock.BlockSize)
    }

    _, err = iio.img.Seek(int64(int64(entry.FirstLBA)*vimg.SectorSize+ext.SuperblockOffset+(bs*bpg*int64(index))), io.SeekStart)
    if err != nil {
        return nil, err
    }

    sb := new(ext.Superblock)
    err = binary.Read(iio.img, binary.LittleEndian, sb)
    if err != nil {
        return nil, err
    }

    if sb.Signature != ext.Signature {
        return nil, errors.New("superblock doesn't contain a valid ext file-system signature (magic number)")
    }

    return sb, nil

}

// Superblock loads the ext superblock from block group 'index'.
func (iio *IO) Superblock(index int) (*ext.Superblock, error) {

    // only return a cached superblock if index is zero
    if index == 0 && iio.fs.superblock != nil {
        return iio.fs.superblock, nil
    }

    var err error
    if iio.fs.superblock == nil {
        iio.fs.superblock, err = iio.readSuperblock(0)
        if err != nil {
            return nil, err
        }
    }

    if index == 0 {
        return iio.fs.superblock, nil
    }

    // TODO: check that index isn't out of bounds

    return iio.readSuperblock(index)

}

func (iio *IO) readBGDT(index int) ([]*ext.BlockGroupDescriptorTableEntry, error) {

    sb, err := iio.Superblock(0)
    if err != nil {
        return nil, err
    }

    block := 1
    if sb.BlockSize == 0 {
        block++
    }
    block += int(sb.BlocksPerGroup) * index

    lba, err := iio.BlockToLBA(block)
    if err != nil {
        return nil, err
    }

    _, err = iio.img.Seek(int64(lba*vimg.SectorSize), io.SeekStart)
    if err != nil {
        return nil, err
    }

    bgs := (sb.TotalBlocks + sb.BlocksPerGroup - 1) / sb.BlocksPerGroup
    bgdt := make([]*ext.BlockGroupDescriptorTableEntry, bgs)
    for i := 0; i < int(bgs); i++ {
        bgdte := new(ext.BlockGroupDescriptorTableEntry)
        err = binary.Read(iio.img, binary.LittleEndian, bgdte)
        if err != nil {
            return nil, err
        }
        bgdt[i] = bgdte
    }

    return bgdt, nil

}

// BGDT loads a block group descriptor table from block group 'index'.
func (iio *IO) BGDT(index int) ([]*ext.BlockGroupDescriptorTableEntry, error) {

    // only return a cached bgdt if index is zero
    if index == 0 && iio.fs.bgdt != nil {
        return iio.fs.bgdt, nil
    }

    var err error
    if iio.fs.bgdt == nil {
        iio.fs.bgdt, err = iio.readBGDT(0)
        if err != nil {
            return nil, err
        }
    }

    if index == 0 {
        return iio.fs.bgdt, nil
    }

    // TODO: check that index isn't out of bounds

    return iio.readBGDT(index)

}

func (iio *IO) superblockAndBGDT() (*ext.Superblock, []*ext.BlockGroupDescriptorTableEntry, error) {

    sb, err := iio.Superblock(0)
    if err != nil {
        return nil, nil, err
    }

    bgdt, err := iio.BGDT(0)
    if err != nil {
        return nil, nil, err
    }

    return sb, bgdt, nil

}

// ResolveInode looks up an inode on the file-system.
func (iio *IO) ResolveInode(ino int) (*ext.Inode, error) {

    sb, bgdt, err := iio.superblockAndBGDT()
    if err != nil {
        return nil, err
    }

    bgno := (ino - 1) / int(sb.InodesPerGroup)
    inodeOffset := (ino - 1) % int(sb.InodesPerGroup)
    firstInodeTableBlock := int(bgdt[bgno].InodeTableBlockAddr)

    // TODO: check for out of bounds

    lba, err := iio.BlockToLBA(firstInodeTableBlock)
    if err != nil {
        return nil, err
    }

    _, err = iio.img.Seek(int64(lba*vimg.SectorSize+inodeOffset*ext.InodeSize), io.SeekStart)
    if err != nil {
        return nil, err
    }

    inode := new(ext.Inode)
    err = binary.Read(iio.img, binary.LittleEndian, inode)
    return inode, err

}

// BlockToLBA converts a file-system block number into an absolute disk LBA.
func (iio *IO) BlockToLBA(block int) (int, error) {

    entry, err := iio.GPTEntry(UTF16toString(vimg.RootPartitionName))
    if err != nil {
        return 0, err
    }

    sb, err := iio.Superblock(0)
    if err != nil {
        return 0, err
    }

    sectorsPerBlock := 2 << sb.BlockSize

    return int(entry.FirstLBA) + block*sectorsPerBlock, nil

}

// Readdir returns a list of directory entries within a directory.
func (iio *IO) Readdir(inode *ext.Inode) ([]*DirectoryEntry, error) {

    rdr, err := iio.InodeReader(inode)
    if err != nil {
        return nil, err
    }

    dirent := new(Dirent)
    list := make([]*DirectoryEntry, 0)

    for {
        err = binary.Read(rdr, binary.LittleEndian, dirent)
        if err == io.EOF {
            break
        }
        if err != nil {
            return nil, err
        }

        l := int(dirent.Size)
        buf := new(bytes.Buffer)
        _, err = io.CopyN(buf, rdr, int64(l-8))
        if err == io.EOF {
            break
        }
        if err != nil {
            return nil, err
        }

        name := cstring(buf.Bytes()[:dirent.NameLen])

        if name == "" || dirent.Inode == 0 {
            continue
        }

        list = append(list, &DirectoryEntry{
            Name:  name,
            Type:  dirent.Type,
            Inode: int(dirent.Inode),
        })
    }

    return list, nil

}

func (iio *IO) resolveChildInodeNumber(inode *ext.Inode, path string) (int, error) {

    _, base := filepath.Split(path)

    list, err := iio.Readdir(inode)
    if err != nil {
        return 0, err
    }

    for _, entry := range list {
        if entry.Name == base {
            return entry.Inode, nil
        }
    }

    return 0, fmt.Errorf("file not found: %s", path)

}

// ResolvePathToInodeNo translates a filepath into an inode number if it can be
// found on the disk.
func (iio *IO) ResolvePathToInodeNo(path string) (int, error) {

    path = filepath.Join("/", path)
    path = filepath.ToSlash(path)
    dir, base := filepath.Split(path)
    if (dir == "" || dir == "/" || dir == "\"") && base == "" {
        return ext.RootDirInode, nil
    }

    parent, err := iio.ResolvePathToInodeNo(dir)
    if err != nil {
        return 0, err
    }

    inode, err := iio.ResolveInode(parent)
    if err != nil {
        return 0, err
    }

    return iio.resolveChildInodeNumber(inode, path)

}

type Dirent struct {
    Inode   uint32
    Size    uint16
    NameLen uint8
    Type    uint8
}

type DirectoryEntry struct {
    Inode int
    Type  uint8
    Name  string
}

type ext4ExtentHeader struct {
    Magic      uint16
    Entries    uint16
    Max        uint16
    Depth      uint16
    Generation uint32
}

type ext4ExtentIdx struct {
    Block  uint32
    LeafLo uint32
    LeafHi uint16
    _      uint16
}

type ext4Extent struct {
    Block uint32
    Len   uint16
    Hi    uint16
    Lo    uint32
}

func (iio *IO) inInodeSymlink(inode *ext.Inode) (io.Reader, error) {

    var s string
    var data []byte
    x := make([]uint32, 15)
    for i := range inode.DirectPointer {
        x[i] = inode.DirectPointer[i]
    }
    x[12] = inode.SinglyIndirect
    x[13] = inode.DoublyIndirect
    x[14] = inode.TriplyIndirect
    buf := new(bytes.Buffer)
    _ = binary.Write(buf, binary.LittleEndian, x)
    data = buf.Bytes()
    data = data[:inode.SizeLower]
    s = string(data)
    return strings.NewReader(s), nil

}

func (iio *IO) emptyInode(inode *ext.Inode) (io.Reader, error) {
    blockAddrs := make([]int, 0)
    return &inodeReader{
        iio:        iio,
        inode:      inode,
        blockAddrs: blockAddrs,
    }, nil
}

func (iio *IO) exploreExtentsTree(hdr *ext4ExtentHeader, blockSize int64, data []byte, blockAddrs *[]int, c *int) error {

    r := bytes.NewReader(data)
    _ = binary.Read(r, binary.LittleEndian, hdr)

    for i := 0; i < int(hdr.Entries); i++ {

        index := new(ext4ExtentIdx)
        binary.Read(r, binary.LittleEndian, index)
        if index.Block != 0 {
            return errors.New("extent index data unaccounted for")
        }
        baddr := int(index.LeafLo) + (int(index.LeafHi) << 32)

        block, err := iio.loadBlock(baddr)
        if err != nil {
            return err
        }

        err = iio.recurseExtentsTree(blockSize, block, blockAddrs, c)
        if err != nil {
            return err
        }

    }

    return nil

}

func (iio *IO) recurseExtentsTree(blockSize int64, data []byte, blockAddrs *[]int, c *int) error {

    // read header
    hdr := new(ext4ExtentHeader)
    r := bytes.NewReader(data)
    _ = binary.Read(r, binary.LittleEndian, hdr)
    if hdr.Magic != 0xF30A {
        return errors.New("extent node doesn't have magic number")
    }

    if hdr.Depth != 0 {
        return iio.exploreExtentsTree(hdr, blockSize, data, blockAddrs, c)
    }

    for i := 0; i < int(hdr.Entries); i++ {
        extent := new(ext4Extent)
        binary.Read(r, binary.LittleEndian, extent)
        if extent.Block != 0 && i == 0 {
            return errors.New("extent data unaccounted for")
        }

        baddr := int(extent.Lo) + (int(extent.Hi) << 32)
        l := int(extent.Len)
        for j := 0; j < int(l) && *c < len(*blockAddrs); (*c), j = (*c + 1), j+1 {
            (*blockAddrs)[*c] = baddr + j // TODO: catch corruption
        }
    }

    return nil

}

func (iio *IO) dataFromExtentsTree(inode *ext.Inode) (io.Reader, error) {

    sb, err := iio.Superblock(0)
    if err != nil {
        return nil, err
    }

    blockSize := int64(1024 << sb.BlockSize)
    blockAddrs := make([]int, (InodeSize(inode)+blockSize-1)/blockSize)

    i := 0

    buf := new(bytes.Buffer)
    binary.Write(buf, binary.LittleEndian, inode.DirectPointer[:])
    err = iio.recurseExtentsTree(blockSize, buf.Bytes(), &blockAddrs, &i)
    if err != nil {
        return nil, err
    }

    out := &inodeReader{
        iio:        iio,
        inode:      inode,
        blockAddrs: blockAddrs,
    }

    return io.LimitReader(out, int64(inode.SizeLower)), nil

}

func (iio *IO) seekToBlock(blockNo int) error {

    lba, err := iio.BlockToLBA(blockNo)
    if err != nil {
        return err
    }

    _, err = iio.img.Seek(int64(lba*vimg.SectorSize), io.SeekStart)
    if err != nil {
        return err
    }

    return nil

}

func (iio *IO) loadBlock(blockNo int) ([]byte, error) {

    sb, err := iio.Superblock(0)
    if err != nil {
        return nil, err
    }

    blockSize := int64(1024 << sb.BlockSize)

    err = iio.seekToBlock(blockNo)
    if err != nil {
        return nil, err
    }

    buf := new(bytes.Buffer)
    _, err = io.CopyN(buf, iio.img, int64(blockSize))
    if err != nil {
        return nil, err
    }

    return buf.Bytes(), nil

}

func (iio *IO) scanPointers(pointerBlock, depth int) ([]int, error) {

    block, err := iio.loadBlock(pointerBlock)
    if err != nil {
        return nil, err
    }
    rdr := bytes.NewReader(block)
    var addr uint32
    var list []int

    for {

        err = binary.Read(rdr, binary.LittleEndian, &addr)
        if err != nil {
            if err == io.EOF {
                err = nil
            } else {
                list = nil
            }
            return list, err
        }

        if depth == 0 {
            list = append(list, int(addr))
            continue
        } else if addr == 0 {
            continue
        }

        sub, err := iio.scanPointers(int(addr), depth-1)
        if err != nil {
            return nil, err
        }

        list = append(list, sub...)

    }

}

func loadBlockPointers(iio *IO, addr, depth int, blockAddrs *[]int, i *int) error {

    if *i < len(*blockAddrs) {
        list, err := iio.scanPointers(addr, depth)
        if err != nil {
            return err
        }

        for j := 0; *i < len(*blockAddrs) && j < len(list); *i, j = *i+1, j+1 {
            (*blockAddrs)[*i] = list[j]
        }
    }

    return nil

}

func (iio *IO) dataFromBlockPointers(inode *ext.Inode) (io.Reader, error) {

    sb, err := iio.Superblock(0)
    if err != nil {
        return nil, err
    }

    blockSize := int64(1024 << sb.BlockSize)
    blockAddrs := make([]int, (InodeSize(inode)+blockSize-1)/blockSize)

    // load direct pointers
    for i := 0; i < len(inode.DirectPointer[:]) && i < len(blockAddrs); i++ {
        blockAddrs[i] = int(inode.DirectPointer[i])
    }

    i := 12

    for depth, addr := range []uint32{inode.SinglyIndirect, inode.DoublyIndirect, inode.TriplyIndirect} {
        err = loadBlockPointers(iio, int(addr), depth, &blockAddrs, &i)
        if err != nil {
            return nil, err
        }
    }

    out := &inodeReader{
        iio:        iio,
        inode:      inode,
        blockAddrs: blockAddrs,
    }

    return io.LimitReader(out, InodeSize(inode)), nil

}

// InodeReader reads all of the data stored for an inode.
func (iio *IO) InodeReader(inode *ext.Inode) (io.Reader, error) {

    if InodeIsSymlink(inode) && inode.Sectors == 0 {
        return iio.inInodeSymlink(inode)
    }

    if inode.Sectors == 0 {
        return iio.emptyInode(inode)
    }

    if inode.Flags&0x80000 > 0 {
        return iio.dataFromExtentsTree(inode)
    }

    return iio.dataFromBlockPointers(inode)

}