use fuse tree & ioctl compatible fork

This commit is contained in:
Diego Fernando Carrión 2024-07-06 21:59:33 +02:00
parent a729080945
commit d8b177c6e3
Signed by: CRThaze
GPG Key ID: 8279B79A1A7F8194
6 changed files with 190 additions and 100 deletions

View File

@ -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)
}
}
```

View File

@ -1,9 +1,10 @@
package main
package embededFUSE
import (
"context"
"embed"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
@ -17,19 +18,18 @@ import (
fuseFS "bazil.org/fuse/fs"
)
var FSName string
var ServeInitFailTimeoutMS = 500
func init() {
func getFSName() string {
var progName string
if name, err := os.Executable(); err != nil {
progName = path.Base(name)
} else if len(os.Args) > 0 {
progName = os.Args[0]
} 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) {
return -1
}
@ -37,82 +37,86 @@ func init() {
}, 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 {
file fs.File
size uint64
fs embed.FS
path string
size uint64
executable bool
}
func (f *File) Attr(ctx context.Context, attr *fuse.Attr) error {
attr.Mode = 0o400
if f.executable {
attr.Mode = 0o500
}
attr.Size = f.size
attr.Mtime = time.Now()
return nil
}
func (f *File) ReadAll() ([]byte, error) {
bs := make([]byte, f.size, f.size)
n, err := f.file.Read(bs)
buff := make([]byte, f.size, f.size)
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 {
return nil, fuse.ToErrno(err)
}
if uint64(n) < f.size {
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
DefaultExecutable string
OtherExecutables []string
Mountpoint string
Prefix string
}
type GFW interface {
type EmbeddedFUSE interface {
Mount() error
Unmount() error
Execute(...string) error
Ls() ([]os.DirEntry, error)
}
type gfw struct {
type embededFUSE struct {
fs embed.FS
conn *fuse.Conn
tree *fuseFS.Tree
mounted bool
mountpoint string
defaultExecutable string
@ -122,7 +126,7 @@ type gfw struct {
srvErrChan chan error
}
func (g *gfw) Mount() error {
func (g *embededFUSE) Mount() error {
if g.mounted {
return fmt.Errorf("Already mounted.")
}
@ -142,9 +146,9 @@ func (g *gfw) Mount() error {
g.mountpoint,
fuse.AllowNonEmptyMount(),
fuse.CacheSymlinks(),
fuse.FSName(FSName),
fuse.FSName(getFSName()),
fuse.ReadOnly(),
fuse.Subtype("gfw"),
fuse.Subtype("goembed"),
); err == nil {
g.conn = conn
} else {
@ -157,7 +161,7 @@ func (g *gfw) Mount() error {
case <-g.ctx.Done():
return
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.cancel()
}
@ -171,21 +175,18 @@ func (g *gfw) Mount() error {
return nil
}
func (g *gfw) Unmount() error {
fmt.Println("Canceling...")
func (g *embededFUSE) Unmount() error {
g.cancel()
fmt.Println("Unmounting...")
if err := fuse.Unmount(g.mountpoint); err != nil {
return err
}
fmt.Println("Closing...")
g.conn.Close()
return os.Remove(g.mountpoint)
}
func (g *gfw) Execute(execAndArgs ...string) error {
func (g *embededFUSE) Execute(execAndArgs ...string) error {
if !g.mounted {
return fmt.Errorf("GFW is not mounted.")
return fmt.Errorf("EmbeddedFUSE is not mounted.")
}
if len(g.executables) == 0 {
return fmt.Errorf("No registerd executables.")
@ -203,27 +204,68 @@ func (g *gfw) Execute(execAndArgs ...string) error {
}
execName = path.Join(g.mountpoint, execName)
cmd := exec.Command(execName, args...)
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return err
}
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 {
conf.OtherExecutables = []string{}
if conf.DefaultExecutable != "" {
conf.OtherExecutables = append(conf.OtherExecutables, conf.DefaultExecutable)
}
}
g := gfw{
g := embededFUSE{
fs: conf.EmbedFS,
tree: &fuseFS.Tree{},
mountpoint: conf.Mountpoint,
defaultExecutable: conf.DefaultExecutable,
executables: map[string]struct{}{},
}
for _, e := range conf.OtherExecutables {
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
}

34
embededFUSE_test.go Normal file
View 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
View File

@ -1,8 +1,9 @@
module git.sdf.org/CRThaze/gfw
module git.sdf.org/CRThaze/go-efuse
go 1.22
require (
bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5 // indirect
golang.org/x/sys v0.4.0 // indirect
)
require bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5
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
View File

@ -1,4 +1,6 @@
bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5 h1:A0NsYy4lDBZAC6QiYeJ4N+XuHIKBpyhAVRMHRQZKTeQ=
bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5/go.mod h1:gG3RZAMXCa/OTes6rr9EwusmR1OH1tDDy+cg9c5YliY=
github.com/natano/bazil-fuse v0.0.0-20230815182653-7701d12d48d3 h1:7xzjJF01uh344zhceQXU7iOYKfede/4z41gb7GHMjus=
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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

30
main.go
View File

@ -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)
}
}