use fuse tree & ioctl compatible fork
This commit is contained in:
parent
a729080945
commit
d8b177c6e3
45
README.md
45
README.md
@ -1,3 +1,44 @@
|
|||||||
# gfw (go-fat-wrapper)
|
# Go Embedded FUSE Filesystem
|
||||||
|
|
||||||
Make any executable or script a single dependency-free binary with the power of Go and FUSE
|
Use a Go embedded filesystem as a FUSE filesystem.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
gef "git.sdf.org/CRThaze/go-efuse"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed files
|
||||||
|
var content embed.FS
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
efuse, err := gef.New(EmbeddedFUSEConfig{
|
||||||
|
EmbedFS: content,
|
||||||
|
Prefix: "files",
|
||||||
|
DefaultExecutable: "test.sh",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to create Embedded FUSE: %v\n", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if err := efuse.Mount(); err != nil {
|
||||||
|
fmt.Printf("Unable to mount Embedded FUSE:, %v\n", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := efuse.Unmount(); err != nil {
|
||||||
|
fmt.Printf("Failed to unmount: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := efuse.Execute(); err != nil {
|
||||||
|
fmt.Printf("Failed to execute: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package main
|
package embededFUSE
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -17,19 +18,18 @@ import (
|
|||||||
fuseFS "bazil.org/fuse/fs"
|
fuseFS "bazil.org/fuse/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var FSName string
|
|
||||||
var ServeInitFailTimeoutMS = 500
|
var ServeInitFailTimeoutMS = 500
|
||||||
|
|
||||||
func init() {
|
func getFSName() string {
|
||||||
var progName string
|
var progName string
|
||||||
if name, err := os.Executable(); err != nil {
|
if name, err := os.Executable(); err != nil {
|
||||||
progName = path.Base(name)
|
progName = path.Base(name)
|
||||||
} else if len(os.Args) > 0 {
|
} else if len(os.Args) > 0 {
|
||||||
progName = os.Args[0]
|
progName = os.Args[0]
|
||||||
} else {
|
} else {
|
||||||
progName = "generic-gfw"
|
progName = "generic-goembed"
|
||||||
}
|
}
|
||||||
FSName = strings.Map(func(r rune) rune {
|
return strings.Map(func(r rune) rune {
|
||||||
if !unicode.In(r, unicode.Hyphen, unicode.Letter) {
|
if !unicode.In(r, unicode.Hyphen, unicode.Letter) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
@ -37,82 +37,86 @@ func init() {
|
|||||||
}, progName)
|
}, progName)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FS struct {
|
|
||||||
fsys fs.FS
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FS) Root() (fuseFS.Node, error) {
|
|
||||||
return &Dir{fsys: f.fsys}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Dir struct {
|
|
||||||
fsys fs.FS
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) Attr(ctx context.Context, attr *fuse.Attr) error {
|
|
||||||
attr.Mode = os.ModeDir | 0o555
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) Lookup(name string) (fuseFS.Node, error) {
|
|
||||||
file, err := d.fsys.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fuse.ToErrno(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
stat, err := file.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat.IsDir() {
|
|
||||||
return &Dir{fsys: d.fsys}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &File{file: file, size: uint64(stat.Size())}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
file fs.File
|
fs embed.FS
|
||||||
size uint64
|
path string
|
||||||
|
size uint64
|
||||||
|
executable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Attr(ctx context.Context, attr *fuse.Attr) error {
|
func (f *File) Attr(ctx context.Context, attr *fuse.Attr) error {
|
||||||
attr.Mode = 0o400
|
attr.Mode = 0o400
|
||||||
|
if f.executable {
|
||||||
|
attr.Mode = 0o500
|
||||||
|
}
|
||||||
attr.Size = f.size
|
attr.Size = f.size
|
||||||
attr.Mtime = time.Now()
|
attr.Mtime = time.Now()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) ReadAll() ([]byte, error) {
|
func (f *File) ReadAll() ([]byte, error) {
|
||||||
bs := make([]byte, f.size, f.size)
|
buff := make([]byte, f.size, f.size)
|
||||||
n, err := f.file.Read(bs)
|
file, err := f.fs.Open(f.path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fuse.ToErrno(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
n, err := file.Read(buff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToErrno(err)
|
return nil, fuse.ToErrno(err)
|
||||||
}
|
}
|
||||||
if uint64(n) < f.size {
|
if uint64(n) < f.size {
|
||||||
return nil, fuse.ToErrno(syscall.EIO)
|
return nil, fuse.ToErrno(syscall.EIO)
|
||||||
}
|
}
|
||||||
return bs, nil
|
return buff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type GFWConfig struct {
|
func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
||||||
|
size := int64(f.size)
|
||||||
|
if req.Offset >= int64(f.size) {
|
||||||
|
return fuse.ToErrno(syscall.EOVERFLOW)
|
||||||
|
}
|
||||||
|
if req.Offset+int64(req.Size) > int64(f.size) {
|
||||||
|
size = int64(f.size) - req.Offset
|
||||||
|
}
|
||||||
|
buff := make([]byte, size, size)
|
||||||
|
file, err := f.fs.Open(f.path)
|
||||||
|
if err != nil {
|
||||||
|
return fuse.ToErrno(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
secReader := io.NewSectionReader(file.(io.ReaderAt), req.Offset, size)
|
||||||
|
_, err = secReader.Read(buff)
|
||||||
|
if err != nil {
|
||||||
|
return fuse.ToErrno(err)
|
||||||
|
}
|
||||||
|
resp.Data = buff
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Ioctl(ctx context.Context, req *fuse.IoctlRequest, resp *fuse.IoctlResponse) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedFUSEConfig struct {
|
||||||
EmbedFS embed.FS
|
EmbedFS embed.FS
|
||||||
DefaultExecutable string
|
DefaultExecutable string
|
||||||
OtherExecutables []string
|
OtherExecutables []string
|
||||||
Mountpoint string
|
Mountpoint string
|
||||||
|
Prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
type GFW interface {
|
type EmbeddedFUSE interface {
|
||||||
Mount() error
|
Mount() error
|
||||||
Unmount() error
|
Unmount() error
|
||||||
Execute(...string) error
|
Execute(...string) error
|
||||||
|
Ls() ([]os.DirEntry, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type gfw struct {
|
type embededFUSE struct {
|
||||||
fs embed.FS
|
fs embed.FS
|
||||||
conn *fuse.Conn
|
conn *fuse.Conn
|
||||||
|
tree *fuseFS.Tree
|
||||||
mounted bool
|
mounted bool
|
||||||
mountpoint string
|
mountpoint string
|
||||||
defaultExecutable string
|
defaultExecutable string
|
||||||
@ -122,7 +126,7 @@ type gfw struct {
|
|||||||
srvErrChan chan error
|
srvErrChan chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gfw) Mount() error {
|
func (g *embededFUSE) Mount() error {
|
||||||
if g.mounted {
|
if g.mounted {
|
||||||
return fmt.Errorf("Already mounted.")
|
return fmt.Errorf("Already mounted.")
|
||||||
}
|
}
|
||||||
@ -142,9 +146,9 @@ func (g *gfw) Mount() error {
|
|||||||
g.mountpoint,
|
g.mountpoint,
|
||||||
fuse.AllowNonEmptyMount(),
|
fuse.AllowNonEmptyMount(),
|
||||||
fuse.CacheSymlinks(),
|
fuse.CacheSymlinks(),
|
||||||
fuse.FSName(FSName),
|
fuse.FSName(getFSName()),
|
||||||
fuse.ReadOnly(),
|
fuse.ReadOnly(),
|
||||||
fuse.Subtype("gfw"),
|
fuse.Subtype("goembed"),
|
||||||
); err == nil {
|
); err == nil {
|
||||||
g.conn = conn
|
g.conn = conn
|
||||||
} else {
|
} else {
|
||||||
@ -157,7 +161,7 @@ func (g *gfw) Mount() error {
|
|||||||
case <-g.ctx.Done():
|
case <-g.ctx.Done():
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
if err := fuseFS.Serve(g.conn, &FS{fsys: g.fs}); err != nil {
|
if err := fuseFS.Serve(g.conn, g.tree); err != nil {
|
||||||
g.srvErrChan <- err
|
g.srvErrChan <- err
|
||||||
g.cancel()
|
g.cancel()
|
||||||
}
|
}
|
||||||
@ -171,21 +175,18 @@ func (g *gfw) Mount() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gfw) Unmount() error {
|
func (g *embededFUSE) Unmount() error {
|
||||||
fmt.Println("Canceling...")
|
|
||||||
g.cancel()
|
g.cancel()
|
||||||
fmt.Println("Unmounting...")
|
|
||||||
if err := fuse.Unmount(g.mountpoint); err != nil {
|
if err := fuse.Unmount(g.mountpoint); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("Closing...")
|
|
||||||
g.conn.Close()
|
g.conn.Close()
|
||||||
return os.Remove(g.mountpoint)
|
return os.Remove(g.mountpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gfw) Execute(execAndArgs ...string) error {
|
func (g *embededFUSE) Execute(execAndArgs ...string) error {
|
||||||
if !g.mounted {
|
if !g.mounted {
|
||||||
return fmt.Errorf("GFW is not mounted.")
|
return fmt.Errorf("EmbeddedFUSE is not mounted.")
|
||||||
}
|
}
|
||||||
if len(g.executables) == 0 {
|
if len(g.executables) == 0 {
|
||||||
return fmt.Errorf("No registerd executables.")
|
return fmt.Errorf("No registerd executables.")
|
||||||
@ -203,27 +204,68 @@ func (g *gfw) Execute(execAndArgs ...string) error {
|
|||||||
}
|
}
|
||||||
execName = path.Join(g.mountpoint, execName)
|
execName = path.Join(g.mountpoint, execName)
|
||||||
cmd := exec.Command(execName, args...)
|
cmd := exec.Command(execName, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGFW(conf GFWConfig) (GFW, error) {
|
func (g *embededFUSE) Ls() ([]os.DirEntry, error) {
|
||||||
|
return os.ReadDir(g.mountpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(conf EmbeddedFUSEConfig) (EmbeddedFUSE, error) {
|
||||||
if conf.OtherExecutables == nil {
|
if conf.OtherExecutables == nil {
|
||||||
conf.OtherExecutables = []string{}
|
conf.OtherExecutables = []string{}
|
||||||
if conf.DefaultExecutable != "" {
|
if conf.DefaultExecutable != "" {
|
||||||
conf.OtherExecutables = append(conf.OtherExecutables, conf.DefaultExecutable)
|
conf.OtherExecutables = append(conf.OtherExecutables, conf.DefaultExecutable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g := gfw{
|
g := embededFUSE{
|
||||||
fs: conf.EmbedFS,
|
fs: conf.EmbedFS,
|
||||||
|
tree: &fuseFS.Tree{},
|
||||||
mountpoint: conf.Mountpoint,
|
mountpoint: conf.Mountpoint,
|
||||||
defaultExecutable: conf.DefaultExecutable,
|
defaultExecutable: conf.DefaultExecutable,
|
||||||
executables: map[string]struct{}{},
|
executables: map[string]struct{}{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range conf.OtherExecutables {
|
for _, e := range conf.OtherExecutables {
|
||||||
g.executables[e] = struct{}{}
|
g.executables[e] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.Prefix == "" {
|
||||||
|
conf.Prefix = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.WalkDir(conf.EmbedFS, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if path == "." {
|
||||||
|
return nil
|
||||||
|
} else if strings.HasPrefix(conf.Prefix, path) || path == conf.Prefix {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := d.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() {
|
||||||
|
trimmedPath := strings.TrimPrefix(path, fmt.Sprintf("%s/", conf.Prefix))
|
||||||
|
executable := false
|
||||||
|
if _, ok := g.executables[trimmedPath]; ok {
|
||||||
|
executable = true
|
||||||
|
}
|
||||||
|
g.tree.Add(trimmedPath, &File{
|
||||||
|
fs: conf.EmbedFS,
|
||||||
|
path: path, size: uint64(info.Size()),
|
||||||
|
executable: executable,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
return &g, nil
|
return &g, nil
|
||||||
}
|
}
|
34
embededFUSE_test.go
Normal file
34
embededFUSE_test.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package embededFUSE
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed files
|
||||||
|
var content embed.FS
|
||||||
|
|
||||||
|
func TestExecute(t *testing.T) {
|
||||||
|
efuse, err := New(EmbeddedFUSEConfig{
|
||||||
|
EmbedFS: content,
|
||||||
|
Prefix: "files",
|
||||||
|
DefaultExecutable: "test.sh",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to create Embedded FUSE: %v\n", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if err := efuse.Mount(); err != nil {
|
||||||
|
fmt.Printf("Unable to mount Embedded FUSE:, %v\n", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := efuse.Unmount(); err != nil {
|
||||||
|
fmt.Printf("Failed to unmount: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := efuse.Execute(); err != nil {
|
||||||
|
fmt.Printf("Failed to execute: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
11
go.mod
11
go.mod
@ -1,8 +1,9 @@
|
|||||||
module git.sdf.org/CRThaze/gfw
|
module git.sdf.org/CRThaze/go-efuse
|
||||||
|
|
||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5
|
||||||
bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5 // indirect
|
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
require golang.org/x/sys v0.4.0 // indirect
|
||||||
)
|
|
||||||
|
replace bazil.org/fuse => github.com/natano/bazil-fuse v0.0.0-20230815182653-7701d12d48d3
|
||||||
|
6
go.sum
6
go.sum
@ -1,4 +1,6 @@
|
|||||||
bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5 h1:A0NsYy4lDBZAC6QiYeJ4N+XuHIKBpyhAVRMHRQZKTeQ=
|
github.com/natano/bazil-fuse v0.0.0-20230815182653-7701d12d48d3 h1:7xzjJF01uh344zhceQXU7iOYKfede/4z41gb7GHMjus=
|
||||||
bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5/go.mod h1:gG3RZAMXCa/OTes6rr9EwusmR1OH1tDDy+cg9c5YliY=
|
github.com/natano/bazil-fuse v0.0.0-20230815182653-7701d12d48d3/go.mod h1:gG3RZAMXCa/OTes6rr9EwusmR1OH1tDDy+cg9c5YliY=
|
||||||
|
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ=
|
||||||
|
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
30
main.go
30
main.go
@ -1,30 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed files/*
|
|
||||||
var content embed.FS
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
gfw, err := NewGFW(GFWConfig{
|
|
||||||
EmbedFS: content,
|
|
||||||
DefaultExecutable: "test.sh",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to create GFW: %v", err)
|
|
||||||
}
|
|
||||||
if err := gfw.Mount(); err != nil {
|
|
||||||
log.Fatalf("Unable to mount GFW:, %v", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := gfw.Unmount(); err != nil {
|
|
||||||
log.Printf("Failed to unmount: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if err := gfw.Execute(); err != nil {
|
|
||||||
log.Printf("Failed to execute: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user