diff --git a/README.md b/README.md index 3f43669..3471c39 100644 --- a/README.md +++ b/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) + } +} + +``` diff --git a/wrap.go b/embededFUSE.go similarity index 57% rename from wrap.go rename to embededFUSE.go index d6d311d..a6d2c0b 100644 --- a/wrap.go +++ b/embededFUSE.go @@ -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 } diff --git a/embededFUSE_test.go b/embededFUSE_test.go new file mode 100644 index 0000000..84c5751 --- /dev/null +++ b/embededFUSE_test.go @@ -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) + } +} diff --git a/go.mod b/go.mod index a448a18..37ffe5c 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 40a7f44..7b55d5d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/main.go b/main.go deleted file mode 100644 index c38ce50..0000000 --- a/main.go +++ /dev/null @@ -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) - } -}