149 lines
3.0 KiB
Go
149 lines
3.0 KiB
Go
package squashfs
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
|
|
"github.com/CalebQ42/fuse"
|
|
"github.com/CalebQ42/fuse/fs"
|
|
"github.com/CalebQ42/squashfs/internal/inode"
|
|
)
|
|
|
|
// Mounts the archive to the given mountpoint using fuse3. Non-blocking.
|
|
// If Unmount does not get called, the mount point must be unmounted using umount before the directory can be used again.
|
|
func (r *Reader) Mount(mountpoint string) (err error) {
|
|
if r.con != nil {
|
|
return errors.New("squashfs archive already mounted")
|
|
}
|
|
r.con, err = fuse.Mount(mountpoint, fuse.ReadOnly())
|
|
if err != nil {
|
|
return
|
|
}
|
|
<-r.con.Ready
|
|
r.mountDone = make(chan struct{})
|
|
go func() {
|
|
fs.Serve(r.con, squashFuse{r: r})
|
|
close(r.mountDone)
|
|
}()
|
|
return
|
|
}
|
|
|
|
// Blocks until the mount ends.
|
|
func (r *Reader) MountWait() {
|
|
if r.mountDone != nil {
|
|
<-r.mountDone
|
|
}
|
|
}
|
|
|
|
// Unmounts the archive.
|
|
func (r *Reader) Unmount() error {
|
|
if r.con != nil {
|
|
defer func() { r.con = nil }()
|
|
return r.con.Close()
|
|
}
|
|
return errors.New("squashfs archive is not mounted")
|
|
}
|
|
|
|
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, 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 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
|
|
}
|