From ffbf4ebc6421b5bd83521981f45f0037c3efef4c Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sat, 3 Dec 2022 02:45:58 -0600 Subject: [PATCH] Fuse SUCCESS --- fuse.go | 121 ++++++++++++++++++++++++++++++++ go.mod | 7 +- go.sum | 12 ++-- internal/data/fullreader.go | 85 ++++++++++++++++++++++ internal/directory/directory.go | 2 + internal/inode/inode.go | 64 +++++++++++++++++ reader_file.go | 9 ++- reader_fs.go | 11 ++- squashfs_test.go | 20 ++++++ 9 files changed, 320 insertions(+), 11 deletions(-) create mode 100644 fuse.go diff --git a/fuse.go b/fuse.go new file mode 100644 index 0000000..6e37c7a --- /dev/null +++ b/fuse.go @@ -0,0 +1,121 @@ +package squashfs + +import ( + "bytes" + "context" + "io" + + "github.com/CalebQ42/fuse" + "github.com/CalebQ42/fuse/fs" + "github.com/CalebQ42/squashfs/internal/inode" +) + +func (r *Reader) Mount(mountpoint string) (con *fuse.Conn, err error) { + con, err = fuse.Mount(mountpoint, fuse.ReadOnly()) + if err != nil { + return + } + err = fs.Serve(con, &squashFuse{r: r}) + return +} + +type squashFuse struct { + r *Reader +} + +func (s *squashFuse) Root() (fs.Node, error) { + return &fileNode{File: s.r.FS.File}, nil +} + +type fileNode struct { + *File +} + +func (f *fileNode) Attr(ctx context.Context, attr *fuse.Attr) error { + attr.Blocks = f.r.s.Size / 512 + if f.r.s.Size%512 > 0 { + attr.Blocks++ + } + attr.Gid = f.r.ids[f.i.GidInd] + attr.Inode = uint64(f.i.Num) + attr.Mode = f.i.Mode() + attr.Nlink = f.i.LinkCount() + attr.Size = f.i.Size() + attr.Uid = f.r.ids[f.i.UidInd] + return nil +} + +func (f *fileNode) Id() uint64 { + return uint64(f.i.Num) +} + +func (f *fileNode) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) { + return f.SymlinkPath(), nil +} + +func (f *fileNode) Lookup(ctx context.Context, name string) (fs.Node, error) { + asFS, err := f.FS() + if err != nil { + return nil, fuse.ENOTDIR + } + ret, err := asFS.OpenFile(name) + if err != nil { + return nil, fuse.ENOENT + } + return &fileNode{File: ret}, nil +} + +func (f *fileNode) ReadAll(ctx context.Context) ([]byte, error) { + if f.IsRegular() { + var buf bytes.Buffer + _, err := f.WriteTo(&buf) + return buf.Bytes(), err + } + return nil, fuse.ENODATA +} + +func (f *fileNode) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { + if f.IsRegular() { + buf := make([]byte, req.Size) + n, err := f.File.ReadAt(buf, req.Offset) + if err == io.EOF { + resp.Data = buf[:n] + } + return nil + } + return fuse.ENODATA +} + +func (f *fileNode) ReadDirAll(ctx context.Context) (out []fuse.Dirent, err error) { + asFS, err := f.FS() + if err != nil { + return nil, fuse.ENOTDIR + } + var t fuse.DirentType + for i := range asFS.e { + switch asFS.e[i].Type { + case inode.Fil: + t = fuse.DT_File + case inode.Dir: + t = fuse.DT_Dir + case inode.Block: + t = fuse.DT_Block + case inode.Sym: + t = fuse.DT_Link + case inode.Char: + t = fuse.DT_Char + case inode.Fifo: + t = fuse.DT_FIFO + case inode.Sock: + t = fuse.DT_Socket + default: + t = fuse.DT_Unknown + } + out = append(out, fuse.Dirent{ + Inode: uint64(asFS.e[i].Num), + Type: t, + Name: asFS.e[i].Name, + }) + } + return +} diff --git a/go.mod b/go.mod index 9d2ebef..b1ee55b 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,12 @@ module github.com/CalebQ42/squashfs go 1.19 require ( - github.com/klauspost/compress v1.15.9 - github.com/pierrec/lz4/v4 v4.1.15 + github.com/CalebQ42/fuse v0.1.0 + github.com/klauspost/compress v1.15.12 + github.com/pierrec/lz4/v4 v4.1.17 github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e github.com/therootcompany/xz v1.0.1 github.com/ulikunitz/xz v0.5.10 ) + +require golang.org/x/sys v0.2.0 // indirect diff --git a/go.sum b/go.sum index 4d3fc78..c29ecaa 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,14 @@ -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/CalebQ42/fuse v0.1.0 h1:KLCNjun7zcd2kBNVFfH+SWJyhuwJdE0nhw5/q8K8HGQ= +github.com/CalebQ42/fuse v0.1.0/go.mod h1:pJpoKG03HJKVhsp8o0YQYqmfbFsr3Eowt90yQGQVO+4= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= +github.com/pierrec/lz4/v4 v4.1.17/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.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/data/fullreader.go b/internal/data/fullreader.go index c868702..d0c8011 100644 --- a/internal/data/fullreader.go +++ b/internal/data/fullreader.go @@ -79,6 +79,91 @@ func (r FullReader) process(index int, offset int64, out chan outDat) { } } +func (r FullReader) ReadAt(p []byte, off int64) (n int, err error) { + out := make(chan outDat, len(r.sizes)) + offset := r.start + num := len(r.sizes) + start := off / int64(r.blockSize) + end := len(p) / int(r.blockSize) + if end%int(r.blockSize) > 0 { + end++ + } + if end > len(r.sizes) { + if r.fragRdr != nil { + end = len(r.sizes) + } else { + end = len(r.sizes) + 1 + } + } + for i := 0; i < num; i++ { + if i < int(start) || i > end { + offset += uint64(realSize(r.sizes[i])) + continue + } + if i == num-1 && r.fragRdr != nil { + go func() { + rdr, e := r.fragRdr() + if err != nil { + out <- outDat{ + i: num - 1, + err: e, + } + return + } + dat, e := io.ReadAll(rdr) + out <- outDat{ + i: num - 1, + err: e, + data: dat, + } + if clr, ok := rdr.(io.Closer); ok { + clr.Close() + } + }() + continue + } + go r.process(i, int64(offset), out) + offset += uint64(realSize(r.sizes[i])) + } + cache := make(map[int]outDat) + for cur := start; cur < int64(end); { + dat := <-out + if dat.err != nil { + err = dat.err + return + } + if dat.i != int(cur) { + cache[dat.i] = dat + continue + } + if cur == start { + dat.data = dat.data[off%int64(r.blockSize):] + } + for i := range dat.data { + p[n+i] = dat.data[i] + } + n += len(dat.data) + cur++ + var ok bool + for { + dat, ok = cache[int(cur)] + if !ok { + break + } + for i := range dat.data { + p[n+i] = dat.data[i] + } + n += len(dat.data) + cur++ + delete(cache, int(cur)) + } + } + if n < len(p) { + err = io.EOF + } + return +} + func (r FullReader) WriteTo(w io.Writer) (n int64, err error) { out := make(chan outDat, len(r.sizes)) offset := r.start diff --git a/internal/directory/directory.go b/internal/directory/directory.go index 0f37fa2..7f49bc3 100644 --- a/internal/directory/directory.go +++ b/internal/directory/directory.go @@ -29,6 +29,7 @@ type Entry struct { BlockStart uint32 Type uint16 Offset uint16 + Num uint32 } func readEntry(r io.Reader) (e entry, err error) { @@ -72,6 +73,7 @@ func ReadEntries(rdr io.Reader, size uint32) (e []Entry, err error) { BlockStart: h.InodeStart, Type: en.Type, Offset: en.Offset, + Num: h.Num + uint32(en.NumOffset), }) } } diff --git a/internal/inode/inode.go b/internal/inode/inode.go index d633eab..11976f5 100644 --- a/internal/inode/inode.go +++ b/internal/inode/inode.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "errors" "io" + "io/fs" "strconv" ) @@ -77,3 +78,66 @@ func Read(r io.Reader, blockSize uint32) (i Inode, err error) { } 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 + } +} diff --git a/reader_file.go b/reader_file.go index 11389bb..77dd8e7 100644 --- a/reader_file.go +++ b/reader_file.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" + "github.com/CalebQ42/squashfs/internal/data" "github.com/CalebQ42/squashfs/internal/directory" "github.com/CalebQ42/squashfs/internal/inode" ) @@ -18,7 +19,7 @@ import ( type File struct { i inode.Inode rdr io.Reader - fullRdr io.WriterTo + fullRdr *data.FullReader r *Reader parent *FS e directory.Entry @@ -35,7 +36,7 @@ func (r Reader) newFile(en directory.Entry, parent *FS) (*File, error) { return nil, err } var rdr io.Reader - var full io.WriterTo + var full *data.FullReader if i.Type == inode.Fil || i.Type == inode.EFil { full, rdr, err = r.getReaders(i) if err != nil { @@ -68,6 +69,10 @@ func (f File) Read(p []byte) (int, error) { return f.rdr.Read(p) } +func (f File) ReadAt(p []byte, off int64) (int, error) { + return f.fullRdr.ReadAt(p, off) +} + // WriteTo writes all data from the file to the writer. This is multi-threaded. // The underlying reader is seperate from the one used with Read and can be reused. func (f File) WriteTo(w io.Writer) (int64, error) { diff --git a/reader_fs.go b/reader_fs.go index 469615e..60296b7 100644 --- a/reader_fs.go +++ b/reader_fs.go @@ -39,8 +39,8 @@ func (r Reader) newFS(e directory.Entry, parent *FS) (*FS, error) { }, nil } -// Open opens the file at name. Returns a squashfs.File. -func (f FS) Open(name string) (fs.File, error) { +// Opens the file at name. Returns a squashfs.File. +func (f FS) OpenFile(name string) (*File, error) { name = filepath.Clean(name) if !fs.ValidPath(name) { return nil, &fs.PathError{ @@ -73,7 +73,7 @@ func (f FS) Open(name string) (fs.File, error) { Err: err, } } - out, err := newFS.Open(strings.Join(split[1:], "/")) + out, err := newFS.OpenFile(strings.Join(split[1:], "/")) if err != nil { err.(*fs.PathError).Path = name } @@ -96,6 +96,11 @@ func (f FS) Open(name string) (fs.File, error) { } } +// Opens the file at name. Returns a io/fs.File. +func (f FS) Open(name string) (fs.File, error) { + return f.OpenFile(name) +} + // Glob returns the name of the files at the given pattern. // All paths are relative to the FS. // Uses filepath.Match to compare names. diff --git a/squashfs_test.go b/squashfs_test.go index 7e71918..553322d 100644 --- a/squashfs_test.go +++ b/squashfs_test.go @@ -185,3 +185,23 @@ func TestSingleFile(t *testing.T) { } t.Fatal("HI") } + +func TestFuse(t *testing.T) { + tmpDir := "testing" + fil, err := preTest(tmpDir) + if err != nil { + t.Fatal(err) + } + os.Remove(filepath.Base(filePath)) + rdr, err := squashfs.NewReader(fil) + if err != nil { + t.Fatal(err) + } + con, err := rdr.Mount("testing/fuseTest") + if err != nil { + t.Fatal(err) + } + defer con.Close() + <-con.Ready + t.Fatal("testing") +}