Initial work

Create Reader
Pulled back in Inode decoding and superblock
New Data and Metadata readers
Added getting of id, fragment, and export table data lazily
Added README to squashfs/squashfs
This commit is contained in:
Caleb Gardner
2023-12-23 02:48:54 -06:00
parent d4d1b2c2b2
commit 707391baba
23 changed files with 860 additions and 2 deletions
+2 -2
View File
@@ -2,7 +2,7 @@
[![PkgGoDev](https://pkg.go.dev/badge/github.com/CalebQ42/squashfs)](https://pkg.go.dev/github.com/CalebQ42/squashfs) [![Go Report Card](https://goreportcard.com/badge/github.com/CalebQ42/squashfs)](https://goreportcard.com/report/github.com/CalebQ42/squashfs) [![PkgGoDev](https://pkg.go.dev/badge/github.com/CalebQ42/squashfs)](https://pkg.go.dev/github.com/CalebQ42/squashfs) [![Go Report Card](https://goreportcard.com/badge/github.com/CalebQ42/squashfs)](https://goreportcard.com/report/github.com/CalebQ42/squashfs)
A PURE Go library to read squashfs. There is currently no plans to add archive creation support as it will almost always be better to just call `mksquashfs`. I could see some possible use cases, but probably won't spend time on it unless it's requested (open a discussion fi you want this feature). A PURE Go library to read squashfs. There is currently no plans to add archive creation support as it will almost always be better to just call `mksquashfs`. I could see some possible use cases, but probably won't spend time on it unless it's requested (open a discussion if you want this feature).
Currently has support for reading squashfs files and extracting files and folders. Currently has support for reading squashfs files and extracting files and folders.
@@ -15,7 +15,7 @@ Thanks also to [distri's squashfs library](https://github.com/distr1/distri/tree
* No Xattr parsing. This is simply because I haven't done any research on it and how to apply these in a pure go way. * No Xattr parsing. This is simply because I haven't done any research on it and how to apply these in a pure go way.
* Socket files are not extracted. * Socket files are not extracted.
* From my research, it seems like a socket file would be useless if it could be created. * From my research, it seems like a socket file would be useless if it could be created. They are still exposed when fuse mounted.
* Fifo files are ignored on `darwin` * Fifo files are ignored on `darwin`
## Issues ## Issues
+11
View File
@@ -0,0 +1,11 @@
module github.com/CalebQ42/squashfs
go 1.21.5
require (
github.com/pierrec/lz4/v4 v4.1.19
github.com/ulikunitz/xz v0.5.11
github.com/klauspost/compress v1.17.4
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e
github.com/therootcompany/xz v1.0.1
)
+10
View File
@@ -0,0 +1,10 @@
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+73
View File
@@ -0,0 +1,73 @@
package data
import (
"encoding/binary"
"io"
"github.com/CalebQ42/squashfs/internal/decompress"
)
type Reader struct {
r io.Reader
d decompress.Decompressor
frag io.Reader
sizes []uint32
dat []byte
curOffset uint16
curIndex uint64
}
func NewReader(r io.Reader, d decompress.Decompressor, sizes []uint32) (*Reader, error) {
return &Reader{
r: r,
d: d,
sizes: sizes,
}, nil
}
func (r *Reader) AddFrag(fragRdr io.Reader) {
r.frag = fragRdr
}
func (r *Reader) advance() error {
r.curOffset = 0
defer func() { r.curIndex++ }()
var err error
if r.curIndex == uint64(len(r.sizes))-1 && r.frag != nil {
r.dat, err = io.ReadAll(r.frag)
return err
} else if r.curIndex >= uint64(len(r.sizes))-1 {
return io.EOF
}
realSize := r.sizes[r.curIndex] &^ 0x8000
r.dat = make([]byte, realSize)
err = binary.Read(r.r, binary.LittleEndian, &r.dat)
if err != nil {
return err
}
if r.sizes[r.curIndex] != realSize {
return nil
}
r.dat, err = r.d.Decompress(r.dat)
return err
}
func (r *Reader) Read(b []byte) (int, error) {
curRead := 0
var toRead int
for curRead < len(b) {
if r.curOffset >= uint16(len(r.dat)) {
if err := r.advance(); err != nil {
return curRead, err
}
}
toRead = len(b) - curRead
if toRead > len(r.dat)-int(r.curOffset) {
toRead = len(r.dat) - int(r.curOffset)
}
copy(b[curRead:], r.dat[r.curOffset:int(r.curOffset)+toRead])
r.curOffset += uint16(toRead)
curRead += toRead
}
return curRead, nil
}
+5
View File
@@ -0,0 +1,5 @@
package decompress
type Decompressor interface {
Decompress([]byte) ([]byte, error)
}
+15
View File
@@ -0,0 +1,15 @@
package decompress
import (
"bytes"
"io"
"github.com/pierrec/lz4/v4"
)
type Lz4 struct{}
func (l Lz4) Decompress(data []byte) ([]byte, error) {
rdr := lz4.NewReader(bytes.NewReader(data))
return io.ReadAll(rdr)
}
+18
View File
@@ -0,0 +1,18 @@
package decompress
import (
"bytes"
"io"
"github.com/ulikunitz/xz/lzma"
)
type Lzma struct{}
func (l Lzma) Decompress(data []byte) ([]byte, error) {
rdr, err := lzma.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
return io.ReadAll(rdr)
}
+13
View File
@@ -0,0 +1,13 @@
package decompress
import (
"bytes"
"github.com/rasky/go-lzo"
)
type Lzo struct{}
func (l Lzo) Decompress(data []byte) ([]byte, error) {
return lzo.Decompress1X(bytes.NewReader(data), len(data), 0)
}
+18
View File
@@ -0,0 +1,18 @@
package decompress
import (
"bytes"
"io"
"github.com/therootcompany/xz"
)
type Xz struct{}
func (x Xz) Decompress(data []byte) ([]byte, error) {
rdr, err := xz.NewReader(bytes.NewReader(data), 0)
if err != nil {
return nil, err
}
return io.ReadAll(rdr)
}
+18
View File
@@ -0,0 +1,18 @@
package decompress
import (
"bytes"
"compress/zlib"
"io"
)
type Zlib struct{}
func (z Zlib) Decompress(data []byte) ([]byte, error) {
rdr, err := zlib.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
defer rdr.Close()
return io.ReadAll(rdr)
}
+19
View File
@@ -0,0 +1,19 @@
package decompress
import (
"bytes"
"io"
"github.com/klauspost/compress/zstd"
)
type Zstd struct{}
func (z Zstd) Decompress(data []byte) ([]byte, error) {
rdr, err := zstd.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
defer rdr.Close()
return io.ReadAll(rdr)
}
+62
View File
@@ -0,0 +1,62 @@
package metadata
import (
"encoding/binary"
"io"
"github.com/CalebQ42/squashfs/internal/decompress"
)
type Reader struct {
r io.Reader
d decompress.Decompressor
dat []byte
curOffset uint16
}
func NewReader(r io.Reader, d decompress.Decompressor) *Reader {
return &Reader{
r: r,
d: d,
}
}
func (r *Reader) advance() error {
r.curOffset = 0
var size uint16
err := binary.Read(r.r, binary.LittleEndian, &size)
if err != nil {
return err
}
realSize := size &^ 0x8000
r.dat = make([]byte, realSize)
err = binary.Read(r.r, binary.LittleEndian, &r.dat)
if err != nil {
return err
}
if size != realSize {
return nil
}
r.dat, err = r.d.Decompress(r.dat)
return err
}
func (r *Reader) Read(b []byte) (int, error) {
curRead := 0
var toRead int
for curRead < len(b) {
if r.curOffset >= uint16(len(r.dat)) {
if err := r.advance(); err != nil {
return curRead, err
}
}
toRead = len(b) - curRead
if toRead > len(r.dat)-int(r.curOffset) {
toRead = len(r.dat) - int(r.curOffset)
}
copy(b[curRead:], r.dat[r.curOffset:int(r.curOffset)+toRead])
r.curOffset += uint16(toRead)
curRead += toRead
}
return curRead, nil
}
+21
View File
@@ -0,0 +1,21 @@
package toreader
import "io"
type Reader struct {
r io.ReaderAt
offset int64
}
func NewReader(r io.ReaderAt, start int64) *Reader {
return &Reader{
r: r,
offset: start,
}
}
func (r *Reader) Read(b []byte) (int, error) {
n, err := r.r.ReadAt(b, r.offset)
r.offset += int64(n)
return n, err
}
+3
View File
@@ -0,0 +1,3 @@
# Lower-Level Squashfs
This library is a lower level version of the main [squashfs](https://github.com/CalebQ42/squashfs) library that doesn't try to be easy to use and exposes a lot of information that is not necesary for must use cases.
+7
View File
@@ -0,0 +1,7 @@
package squashfs
type fragEntry struct {
start uint64
size uint32
_ uint32
}
+17
View File
@@ -0,0 +1,17 @@
package squashfs
import (
"github.com/CalebQ42/squashfs/internal/metadata"
"github.com/CalebQ42/squashfs/internal/toreader"
"github.com/CalebQ42/squashfs/squashfs/inode"
)
func (r *Reader) inodeFromRef(ref uint64) (*inode.Inode, error) {
offset, meta := (ref>>16)+r.sup.InodeTableStart, ref&0xFFFF
rdr := metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
_, err := rdr.Read(make([]byte, meta))
if err != nil {
return nil, err
}
return inode.Read(rdr, r.sup.BlockSize)
}
+65
View File
@@ -0,0 +1,65 @@
package inode
import (
"encoding/binary"
"io"
)
type Directory struct {
BlockStart uint32
LinkCount uint32
Size uint16
Offset uint16
ParentNum uint32
}
type eDirectoryInit struct {
LinkCount uint32
Size uint32
BlockStart uint32
ParentNum uint32
IndCount uint16
Offset uint16
XattrInd uint32
}
type EDirectory struct {
eDirectoryInit
Indexes []DirectoryIndex
}
type directoryIndexInit struct {
Ind uint32
Start uint32
NameSize uint32
}
type DirectoryIndex struct {
directoryIndexInit
Name []byte
}
func ReadDir(r io.Reader) (d Directory, err error) {
err = binary.Read(r, binary.LittleEndian, &d)
return
}
func ReadEDir(r io.Reader) (d EDirectory, err error) {
err = binary.Read(r, binary.LittleEndian, &d.eDirectoryInit)
if err != nil {
return
}
d.Indexes = make([]DirectoryIndex, d.IndCount)
for i := range d.Indexes {
err = binary.Read(r, binary.LittleEndian, &d.Indexes[i].directoryIndexInit)
if err != nil {
return
}
d.Indexes[i].Name = make([]byte, d.Indexes[i].NameSize+1)
err = binary.Read(r, binary.LittleEndian, &d.Indexes[i].Name)
if err != nil {
return
}
}
return
}
+62
View File
@@ -0,0 +1,62 @@
package inode
import (
"encoding/binary"
"io"
"math"
)
type fileInit struct {
BlockStart uint32
FragInd uint32
FragOffset uint32
Size uint32
}
type File struct {
fileInit
BlockSizes []uint32
}
type eFileInit struct {
BlockStart uint64
Size uint64
Sparse uint64
LinkCount uint32
FragInd uint32
FragOffset uint32
XattrInd uint32
}
type EFile struct {
eFileInit
BlockSizes []uint32
}
func ReadFile(r io.Reader, blockSize uint32) (f File, err error) {
err = binary.Read(r, binary.LittleEndian, &f.fileInit)
if err != nil {
return
}
toRead := int(math.Floor(float64(f.Size) / float64(blockSize)))
if f.FragInd == 0xFFFFFFFF && f.Size%blockSize > 0 {
toRead++
}
f.BlockSizes = make([]uint32, toRead)
err = binary.Read(r, binary.LittleEndian, &f.BlockSizes)
return
}
func ReadEFile(r io.Reader, blockSize uint32) (f EFile, err error) {
err = binary.Read(r, binary.LittleEndian, &f.eFileInit)
if err != nil {
return
}
toRead := int(math.Floor(float64(f.Size) / float64(blockSize)))
if f.FragInd == 0xFFFFFFFF && f.Size%uint64(blockSize) > 0 {
toRead++
}
f.BlockSizes = make([]uint32, toRead)
err = binary.Read(r, binary.LittleEndian, &f.BlockSizes)
return
}
+144
View File
@@ -0,0 +1,144 @@
package inode
import (
"encoding/binary"
"errors"
"io"
"io/fs"
"strconv"
)
const (
Dir = uint16(iota + 1)
Fil
Sym
Block
Char
Fifo
Sock
EDir
EFil
ESym
EBlock
EChar
EFifo
ESock
)
type Header struct {
Type uint16
Perm uint16
UidInd uint16
GidInd uint16
ModTime uint32
Num uint32
}
type Inode struct {
Header
Data any
}
func Read(r io.Reader, blockSize uint32) (i *Inode, err error) {
i = new(Inode)
err = binary.Read(r, binary.LittleEndian, &i.Header)
if err != nil {
return
}
switch i.Type {
case Dir:
i.Data, err = ReadDir(r)
case Fil:
i.Data, err = ReadFile(r, blockSize)
case Sym:
i.Data, err = ReadSym(r)
case Block:
fallthrough
case Char:
i.Data, err = ReadDevice(r)
case Fifo:
fallthrough
case Sock:
i.Data, err = ReadIPC(r)
case EDir:
i.Data, err = ReadEDir(r)
case EFil:
i.Data, err = ReadEFile(r, blockSize)
case ESym:
i.Data, err = ReadESym(r)
case EBlock:
fallthrough
case EChar:
i.Data, err = ReadEDevice(r)
case EFifo:
fallthrough
case ESock:
i.Data, err = ReadEIPC(r)
default:
return i, errors.New("invalid inode type " + strconv.Itoa(int(i.Type)))
}
return
}
func (i Inode) Mode() (out fs.FileMode) {
out = fs.FileMode(i.Perm)
switch i.Data.(type) {
case Directory:
out |= fs.ModeDir
case EDirectory:
out |= fs.ModeDir
case Symlink:
out |= fs.ModeSymlink
case ESymlink:
out |= fs.ModeSymlink
case Device:
out |= fs.ModeDevice
case EDevice:
out |= fs.ModeDevice
case IPC:
out |= fs.ModeNamedPipe
case EIPC:
out |= fs.ModeNamedPipe
}
return
}
func (i Inode) LinkCount() uint32 {
switch i.Data.(type) {
case EFile:
return i.Data.(EFile).LinkCount
case Directory:
return i.Data.(Directory).LinkCount
case EDirectory:
return i.Data.(EDirectory).LinkCount
case Device:
return i.Data.(Device).LinkCount
case EDevice:
return i.Data.(EDevice).LinkCount
case IPC:
return i.Data.(IPC).LinkCount
case EIPC:
return i.Data.(EIPC).LinkCount
case Symlink:
return i.Data.(Symlink).LinkCount
case ESymlink:
return i.Data.(ESymlink).LinkCount
default:
return 0
}
}
func (i Inode) Size() uint64 {
switch i.Data.(type) {
case File:
return uint64(i.Data.(File).Size)
case EFile:
return i.Data.(EFile).Size
// case Directory:
// return uint64(i.Data.(Directory).Size)
// case EDirectory:
// return uint64(i.Data.(EDirectory).Size)
default:
return 0
}
}
+45
View File
@@ -0,0 +1,45 @@
package inode
import (
"encoding/binary"
"io"
)
type Device struct {
LinkCount uint32
Dev uint32
}
type EDevice struct {
Device
XattrInd uint32
}
func ReadDevice(r io.Reader) (d Device, err error) {
err = binary.Read(r, binary.LittleEndian, &d)
return
}
func ReadEDevice(r io.Reader) (d EDevice, err error) {
err = binary.Read(r, binary.LittleEndian, &d)
return
}
type IPC struct {
LinkCount uint32
}
type EIPC struct {
IPC
XattrInd uint32
}
func ReadIPC(r io.Reader) (i IPC, err error) {
err = binary.Read(r, binary.LittleEndian, &i)
return
}
func ReadEIPC(r io.Reader) (i EIPC, err error) {
err = binary.Read(r, binary.LittleEndian, &i)
return
}
+46
View File
@@ -0,0 +1,46 @@
package inode
import (
"encoding/binary"
"io"
)
type symlinkInit struct {
LinkCount uint32
TargetSize uint32
}
type Symlink struct {
symlinkInit
Target []byte
}
type ESymlink struct {
symlinkInit
Target []byte
XattrInd uint32
}
func ReadSym(r io.Reader) (s Symlink, err error) {
err = binary.Read(r, binary.LittleEndian, &s.symlinkInit)
if err != nil {
return
}
s.Target = make([]byte, s.TargetSize)
err = binary.Read(r, binary.LittleEndian, &s.Target)
return
}
func ReadESym(r io.Reader) (s ESymlink, err error) {
err = binary.Read(r, binary.LittleEndian, &s.symlinkInit)
if err != nil {
return
}
s.Target = make([]byte, s.TargetSize)
err = binary.Read(r, binary.LittleEndian, &s.Target)
if err != nil {
return
}
err = binary.Read(r, binary.LittleEndian, &s.XattrInd)
return
}
+186
View File
@@ -0,0 +1,186 @@
package squashfs
import (
"encoding/binary"
"errors"
"io"
"math"
"time"
"github.com/CalebQ42/squashfs/internal/decompress"
"github.com/CalebQ42/squashfs/internal/toreader"
)
// The types of compression supported by squashfs
const (
ZlibCompression = uint16(iota + 1)
LZMACompression
LZOCompression
XZCompression
LZ4Compression
ZSTDCompression
)
var (
ErrorMagic = errors.New("magic incorrect. probably not reading squashfs archive or archive is corrupted")
ErrorLog = errors.New("block log is incorrect. possible corrupted archive")
ErrorVersion = errors.New("squashfs version of archive is not 4.0. may be corrupted")
ErrorNotExportable = errors.New("archive does not have an export table")
)
type Reader struct {
r io.ReaderAt
d decompress.Decompressor
fragTable []fragEntry
idTable []uint32
exportTable []uint64
sup superblock
}
func NewReader(r io.ReaderAt) (rdr *Reader, err error) {
rdr = new(Reader)
rdr.r = r
err = binary.Read(toreader.NewReader(r, 0), binary.LittleEndian, &rdr.sup)
if err != nil {
return nil, errors.Join(errors.New("failed to read superblock"), err)
}
if !rdr.sup.checkMagic() {
return nil, ErrorMagic
}
if !rdr.sup.checkBlockLog() {
return nil, ErrorLog
}
if !rdr.sup.checkVersion() {
return nil, ErrorVersion
}
switch rdr.sup.CompType {
case ZlibCompression:
rdr.d = decompress.Zlib{}
case LZMACompression:
rdr.d = decompress.Lzma{}
case LZOCompression:
rdr.d = decompress.Lzo{}
case XZCompression:
rdr.d = decompress.Xz{}
case LZ4Compression:
rdr.d = decompress.Lz4{}
case ZSTDCompression:
rdr.d = &decompress.Zstd{}
default:
return nil, errors.New("invalid compression type. possible corrupted archive")
}
return
}
// Returns the last time the archive was modified.
func (r *Reader) ModTime() time.Time {
return time.Unix(int64(r.sup.ModTime), 0)
}
// Get a uid/gid at the given index. Lazily populates the reader's id table as necessary.
func (r *Reader) id(i uint16) (uint32, error) {
if len(r.idTable) > int(i) {
return r.idTable[i], nil
} else if i >= r.sup.IdCount {
return 0, errors.New("id out of bounds")
}
// Populate the id table as needed
blockNum := uint16(math.Ceil(float64(i) / 2048))
blocksRead := len(r.idTable) / 2048
blocksToRead := int(blockNum) - blocksRead
var offset uint64
var idsToRead uint16
var idsTmp []uint32
var err error
for i := blocksRead; i < int(blockNum)+blocksToRead; i++ {
err = binary.Read(toreader.NewReader(r.r, int64(r.sup.IdTableStart)+int64(8*i)), binary.LittleEndian, &offset)
if err != nil {
return 0, err
}
idsToRead = r.sup.IdCount - uint16(len(r.idTable))
if idsToRead > 2048 {
idsToRead = 2048
}
idsTmp = make([]uint32, idsToRead)
err = binary.Read(toreader.NewReader(r.r, int64(offset)), binary.LittleEndian, &idsTmp)
if err != nil {
return 0, err
}
r.idTable = append(r.idTable, idsTmp...)
}
return r.idTable[i], nil
}
// Get a fragment entry at the given index. Lazily populates the reader's fragment table as necessary.
func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
if len(r.fragTable) > int(i) {
return r.fragTable[i], nil
} else if i >= r.sup.FragCount {
return fragEntry{}, errors.New("fragment out of bounds")
}
// Populate the fragment table as needed
blockNum := uint32(math.Ceil(float64(i) / 512))
blocksRead := len(r.fragTable) / 512
blocksToRead := int(blockNum) - blocksRead
var offset uint64
var fragsToRead uint32
var fragsTmp []fragEntry
var err error
for i := blocksRead; i < int(blockNum)+blocksToRead; i++ {
err = binary.Read(toreader.NewReader(r.r, int64(r.sup.FragTableStart)+int64(8*i)), binary.LittleEndian, &offset)
if err != nil {
return fragEntry{}, err
}
fragsToRead = r.sup.FragCount - uint32(len(r.fragTable))
if fragsToRead > 512 {
fragsToRead = 512
}
fragsTmp = make([]fragEntry, fragsToRead)
err = binary.Read(toreader.NewReader(r.r, int64(offset)), binary.LittleEndian, &fragsTmp)
if err != nil {
return fragEntry{}, err
}
r.fragTable = append(r.fragTable, fragsTmp...)
}
return r.fragTable[i], nil
}
// Get an inode reference at the given index. Lazily populates the reader's export table as necessary.
func (r *Reader) inodeRef(i uint32) (uint64, error) {
if !r.sup.exportable() {
return 0, ErrorNotExportable
}
if len(r.exportTable) > int(i) {
return r.exportTable[i], nil
} else if i >= r.sup.InodeCount {
return 0, errors.New("inode out of bounds")
}
// Populate the export table as neede
blockNum := uint32(math.Ceil(float64(i) / 1024))
blocksRead := len(r.exportTable) / 1024
blocksToRead := int(blockNum) - blocksRead
var offset uint64
var refsToRead uint32
var refsTmp []uint64
var err error
for i := blocksRead; i < int(blockNum)+blocksToRead; i++ {
err = binary.Read(toreader.NewReader(r.r, int64(r.sup.ExportTableStart)+int64(8*i)), binary.LittleEndian, &offset)
if err != nil {
return 0, err
}
refsToRead = r.sup.InodeCount - uint32(len(r.exportTable))
if refsToRead > 1024 {
refsToRead = 1024
}
refsTmp = make([]uint64, refsToRead)
err = binary.Read(toreader.NewReader(r.r, int64(offset)), binary.LittleEndian, &refsTmp)
if err != nil {
return 0, err
}
r.exportTable = append(r.exportTable, refsTmp...)
}
return r.exportTable[i], nil
}