diff --git a/fuse2.go b/fuse2.go new file mode 100644 index 0000000..8338610 --- /dev/null +++ b/fuse2.go @@ -0,0 +1,150 @@ +package squashfs + +import ( + "bytes" + "context" + "errors" + "io" + + "github.com/CalebQ42/squashfs/internal/inode" + "github.com/seaweedfs/fuse" + "github.com/seaweedfs/fuse/fs" +) + +// Mounts the archive to the given mountpoint using fuse2. 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) MountFuse2(mountpoint string) (err error) { + if r.con != nil { + return errors.New("squashfs archive already mounted") + } + r.con2, err = fuse.Mount(mountpoint, fuse.ReadOnly()) + if err != nil { + return + } + <-r.con2.Ready + r.mount2Done = make(chan struct{}) + go func() { + fs.Serve(r.con2, squashFuse2{r: r}) + close(r.mount2Done) + }() + return +} + +// Blocks until the mount ends. +// Fuse2 version. +func (r *Reader) MountWaitFuse2() { + if r.mount2Done != nil { + <-r.mount2Done + } +} + +// Unmounts the archive. +// Fuse2 version. +func (r *Reader) UnmountFuse2() error { + if r.con != nil { + defer func() { r.con = nil }() + return r.con.Close() + } + return errors.New("squashfs archive is not mounted") +} + +type squashFuse2 struct { + r *Reader +} + +func (s squashFuse2) Root() (fs.Node, error) { + return fileNode2{File: s.r.FS.File}, nil +} + +type fileNode2 struct { + *File +} + +func (f fileNode2) 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 fileNode2) Id() uint64 { + return uint64(f.i.Num) +} + +func (f fileNode2) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) { + return f.SymlinkPath(), nil +} + +func (f fileNode2) 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 fileNode2{File: ret}, nil +} + +func (f fileNode2) 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 fileNode2) 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 fileNode2) 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/fuse3.go b/fuse3.go new file mode 100644 index 0000000..e0b8b28 --- /dev/null +++ b/fuse3.go @@ -0,0 +1,148 @@ +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 +} diff --git a/fuse_darwin.go b/fuse_darwin.go new file mode 100644 index 0000000..353aee1 --- /dev/null +++ b/fuse_darwin.go @@ -0,0 +1,7 @@ +package squashfs + +import ( + "golang.org/x/sys/unix" +) + +var ENODATA = unix.Errno(unix.ENODATA) diff --git a/fuse_linux.go b/fuse_linux.go new file mode 100644 index 0000000..816560a --- /dev/null +++ b/fuse_linux.go @@ -0,0 +1,5 @@ +package squashfs + +import "github.com/CalebQ42/fuse" + +var ENODATA = fuse.ENODATA diff --git a/fuse_windows.go b/fuse_windows.go new file mode 100644 index 0000000..c50a696 --- /dev/null +++ b/fuse_windows.go @@ -0,0 +1,3 @@ +package squashfs + +var ENODATA = windows.Errno(windows.ENODATA)