Added FUSE support #55

Merged
Elara6331 merged 65 commits from yannickulrich/itd:fuse into master 2023-03-25 22:23:52 +00:00
8 changed files with 728 additions and 0 deletions

View File

@ -106,5 +106,8 @@ func setCfgDefaults() {
"notifs.ignore.body": []string{}, "notifs.ignore.body": []string{},
"music.vol.interval": 5, "music.vol.interval": 5,
"fuse.enabled": false,
"fuse.mountpoint": "/tmp/itd/mnt",
}, "."), nil) }, "."), nil)
} }

60
fuse.go Normal file
View File

@ -0,0 +1,60 @@
package main
import (
"go.arsenm.dev/itd/internal/fusefs"
"os"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"go.arsenm.dev/logger/log"
"context"
"go.arsenm.dev/infinitime"
)
func startFUSE(ctx context.Context, dev *infinitime.Device) error {
// This is where we'll mount the FS
err := os.MkdirAll(k.String("fuse.mountpoint"), 0755)
if err != nil && !os.IsExist(err) {
return err
}
// Ignore the error because nothing might be mounted on the mountpoint
_ = fusefs.Unmount(k.String("fuse.mountpoint"))
root, err := fusefs.BuildRootNode(dev)
if err != nil {
log.Error("Building root node failed").
Err(err).
Send()
return err
}
server, err := fs.Mount(k.String("fuse.mountpoint"), root, &fs.Options{
MountOptions: fuse.MountOptions{
// Set to true to see how the file system works.
Debug: false,
SingleThreaded: true,
},
})
if err != nil {
log.Error("Mounting failed").
Str("target", k.String("fuse.mountpoint")).
Err(err).
Send()
return err
}
log.Info("Mounted on target").
Str("target", k.String("fuse.mountpoint")).
Send()
fusefs.BuildProperties(dev)
if err != nil {
log.Warn("Error getting BLE filesystem").Err(err).Send()
return err
}
// Wait until unmount before exiting
go server.Serve()
return nil
}

1
go.mod
View File

@ -45,6 +45,7 @@ require (
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.1 // indirect github.com/gookit/color v1.5.1 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/hanwen/go-fuse/v2 v2.2.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect

2
go.sum
View File

@ -257,6 +257,8 @@ github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfI
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY= github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hanwen/go-fuse/v2 v2.2.0 h1:jo5QZYmBLNcl9ovypWaQ5yXMSSV+Ch68xoC3rtZvvBM=
github.com/hanwen/go-fuse/v2 v2.2.0/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=

572
internal/fusefs/fuse.go Normal file
View File

@ -0,0 +1,572 @@
package fusefs
import (
"go.arsenm.dev/infinitime"
"go.arsenm.dev/infinitime/blefs"
"go.arsenm.dev/logger/log"
"context"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"io"
"bytes"
"strconv"
)
type ITProperty struct {
name string
Ino uint64
gen func() ([]byte, error)
}
type DirEntry struct {
isDir bool
modtime uint64
size uint32
path string
}
type ITNode struct {
fs.Inode
kind int
Ino uint64
lst []DirEntry
self DirEntry
path string
}
var myfs *blefs.FS = nil
var inodemap map[string]uint64 = nil
func BuildRootNode(dev *infinitime.Device) (*ITNode, error) {
var err error
inodemap = make(map[string]uint64)
myfs, err = dev.FS()
if err != nil {
log.Error("FUSE Failed to get filesystem").Err(err).Send()
return nil, err
}
return &ITNode{kind: 0}, nil
}
var properties = make([]ITProperty, 6)
func BuildProperties(dev *infinitime.Device) {
properties[0] = ITProperty{"heartrate", 2,
func() ([]byte, error) {
ans, err := dev.HeartRate()
return []byte(strconv.Itoa(int(ans)) + "\n"), err
}}
properties[1] = ITProperty{"battery", 3,
func() ([]byte, error) {
ans, err := dev.BatteryLevel()
return []byte(strconv.Itoa(int(ans)) + "\n"), err
}}
properties[2] = ITProperty{"motion", 4,
func() ([]byte, error) {
ans, err := dev.Motion()
return []byte(strconv.Itoa(int(ans.X)) + " " + strconv.Itoa(int(ans.Y)) + " " + strconv.Itoa(int(ans.Z)) + "\n"), err
}}
properties[3] = ITProperty{"stepcount", 6,
func() ([]byte, error) {
ans, err := dev.StepCount()
return []byte(strconv.Itoa(int(ans)) + "\n"), err
}}
properties[4] = ITProperty{"version", 7,
func() ([]byte, error) {
ans, err := dev.Version()
return []byte(ans + "\n"), err
}}
properties[5] = ITProperty{"address", 8,
func() ([]byte, error) {
ans := dev.Address()
return []byte(ans + "\n"), nil
}}
}
var _ fs.NodeReaddirer = (*ITNode)(nil)
// Readdir is part of the NodeReaddirer interface
func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
switch n.kind {
case 0:
// root folder
r := make([]fuse.DirEntry, 2)
r[0] = fuse.DirEntry{
yannickulrich marked this conversation as resolved
Review

In my opinion, "device" is unclear. I think it should be called "info" instead, that seems clearer.

Another thing is that you seem to be providing continuous sensor data by never sending an EOF, which means files will be read forever. Files generally don't work like that, so it leads to consequences. Since the syscall for reading from the file will never return until new information is received, a program that reads from this file will not be able to be killed, even by SIGKILL. You can see this if you try to cat the file. It can also break your system's shell depending on how it's configured. Instead, I think you should just provide single data points, and people will just have to use itctl if they want continuous data, because files aren't meant to work this way.

In my opinion, "device" is unclear. I think it should be called "info" instead, that seems clearer. Another thing is that you seem to be providing continuous sensor data by never sending an EOF, which means files will be read forever. Files generally don't work like that, so it leads to consequences. Since the syscall for reading from the file will never return until new information is received, a program that reads from this file will not be able to be killed, even by `SIGKILL`. You can see this if you try to `cat` the file. It can also break your system's shell depending on how it's configured. Instead, I think you should just provide single data points, and people will just have to use `itctl` if they want continuous data, because files aren't meant to work this way.
Review

Happy to change that.

Regarding the continuous data stream, isn't this how device files work under Linux? If I run cat /dev/hidraw0 I get a live stream of data. I was hoping to get the same behaviour. I'm happy to try and fix any bug but think this is probably the best way of doing this. Alternatively, we can have to files, a live stream file (eg. /tmp/itd/mnt/live/motion) and a one-shot file (eg. /tmp/itd/mnt/info/motion). Would you be opposed to this?

I'm not 100% sure what you mean with the cat example. This works perfectly for me:

$ cat /tmp/itd/mnt/info/motion
22 -5 -1042
22 -5 -1042
22 -5 -1042
19 -5 -1041
20 -5 -1042
18 -6 -1038
19 0 -1041
24 0 -1043
23 -6 -1041
^C
$

In the itd log I also see the Done method of the watcher getting called. Could you perhaps elaborate on the problem you see?

Happy to change that. Regarding the continuous data stream, isn't this how device files work under Linux? If I run `cat /dev/hidraw0` I get a live stream of data. I was hoping to get the same behaviour. I'm happy to try and fix any bug but think this is probably the best way of doing this. Alternatively, we can have to files, a live stream file (eg. `/tmp/itd/mnt/live/motion`) and a one-shot file (eg. `/tmp/itd/mnt/info/motion`). Would you be opposed to this? I'm not 100% sure what you mean with the `cat` example. This works perfectly for me: ``` $ cat /tmp/itd/mnt/info/motion 22 -5 -1042 22 -5 -1042 22 -5 -1042 19 -5 -1041 20 -5 -1042 18 -6 -1038 19 0 -1041 24 0 -1043 23 -6 -1041 ^C $ ``` In the `itd` log I also see the [Done method of the watcher](https://gitea.arsenm.dev/Arsen6331/infinitime/src/branch/master/infinitime.go#L693-L697) getting called. Could you perhaps elaborate on the problem you see?
Review

Could you perhaps elaborate on the problem you see?

Try this with something that returns data less commonly, such as battery. Every time cat tries to read from the file, it executes a syscall. Since data isn't returned until ITD receives it from the watch, the syscall doesn't return until then either. What this means is that whatever program is reading the file will be executing kernel code, which prevents you from killing it.

Linux does this kind of stuff with a char device file. Unfortunately, FUSE doesn't provide a way to make a char device. There seems to be a workaround, but it only works for the root user, so it would not work in ITD. I did test that, and I can confirm it doesn't work.

> Could you perhaps elaborate on the problem you see? Try this with something that returns data less commonly, such as `battery`. Every time `cat` tries to read from the file, it executes a syscall. Since data isn't returned until ITD receives it from the watch, the syscall doesn't return until then either. What this means is that whatever program is reading the file will be executing kernel code, which prevents you from killing it. Linux does this kind of stuff with a char device file. Unfortunately, FUSE doesn't provide a way to make a char device. There seems to be a workaround, but it only works for the root user, so it would not work in ITD. I did test that, and I can confirm it doesn't work.
Review

Okay, fair enough. Sorry for doubting you. How do you feel about keeping a stream-type file for motion? I'm not 100% sure how much sense the instantaneous motion makes.. Otherwise, I'm going to remove the entire channel stuff.

Okay, fair enough. Sorry for doubting you. How do you feel about keeping a stream-type file for motion? I'm not 100% sure how much sense the instantaneous motion makes.. Otherwise, I'm going to remove the entire channel stuff.
Review

Yeah, instantaneous motion data doesn't make much sense, but the continuous motion data file also doesn't work all the time. InfiniTime will stop sending motion data if you have Raise To Wake and ShakeWake disabled and you put the watch to sleep with the button. This will cause programs to once again read forever without the ability to be killed, so I think it would be better to remove the continuous data entirely. I would love to have it, but if it breaks most programs, it's not worth it, especially since itctl can be used instead.

Yeah, instantaneous motion data doesn't make much sense, but the continuous motion data file also doesn't work all the time. InfiniTime will stop sending motion data if you have Raise To Wake and ShakeWake disabled and you put the watch to sleep with the button. This will cause programs to once again read forever without the ability to be killed, so I think it would be better to remove the continuous data entirely. I would love to have it, but if it breaks most programs, it's not worth it, especially since `itctl` can be used instead.
Name: "info",
Ino: 0,
Mode: fuse.S_IFDIR,
}
r[1] = fuse.DirEntry{
Name: "fs",
Ino: 1,
Mode: fuse.S_IFDIR,
}
return fs.NewListDirStream(r), 0
case 1:
// info folder
r := make([]fuse.DirEntry, 6)
for ind, value := range properties {
r[ind] = fuse.DirEntry{
Name: value.name,
Ino: value.Ino,
Mode: fuse.S_IFREG,
}
}
return fs.NewListDirStream(r), 0
case 2:
// on info
files, err := myfs.ReadDir(n.path)
if err != nil {
log.Error("FUSE ReadDir failed").Str("path", n.path).Err(err).Send()
return nil, syscallErr(err)
}
log.Debug("FUSE ReadDir succeeded").Str("path", n.path).Int("objects", len(files)).Send()
r := make([]fuse.DirEntry, len(files))
n.lst = make([]DirEntry, len(files))
for ind, entry := range files {
info, err := entry.Info()
if err != nil {
log.Error("FUSE Info failed").Str("path", n.path).Err(err).Send()
return nil, syscallErr(err)
}
name := info.Name()
file := DirEntry{
path: n.path + "/" + name,
size: uint32(info.Size()),
modtime: uint64(info.ModTime().Unix()),
isDir: info.IsDir(),
}
n.lst[ind] = file
ino := inodemap[file.path]
if ino == 0 {
ino = uint64(len(inodemap)) + 1
inodemap[file.path] = ino
}
if file.isDir {
r[ind] = fuse.DirEntry{
Name: name,
Mode: fuse.S_IFDIR,
Ino : ino + 10,
}
} else {
r[ind] = fuse.DirEntry{
Name: name,
Mode: fuse.S_IFREG,
Ino : ino + 10,
}
}
}
return fs.NewListDirStream(r), 0
}
r := make([]fuse.DirEntry, 0)
return fs.NewListDirStream(r), 0
}
var _ fs.NodeLookuper = (*ITNode)(nil)
func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
switch n.kind {
case 0:
// root folder
if name == "info" {
stable := fs.StableAttr{
Mode: fuse.S_IFDIR,
Ino: uint64(0),
}
operations := &ITNode{kind: 1, Ino: 0}
child := n.NewInode(ctx, operations, stable)
return child, 0
} else if name == "fs" {
stable := fs.StableAttr{
Mode: fuse.S_IFDIR,
Ino: uint64(1),
}
operations := &ITNode{kind: 2, Ino: 1, path : ""}
child := n.NewInode(ctx, operations, stable)
return child, 0
}
case 1:
// info folder
for _, value := range properties {
if value.name == name {
stable := fs.StableAttr{
Mode: fuse.S_IFREG,
Ino: uint64(value.Ino),
}
operations := &ITNode{kind: 3, Ino: value.Ino}
child := n.NewInode(ctx, operations, stable)
return child, 0
}
}
case 2:
// FS object
if len(n.lst) == 0 {
n.Readdir(ctx)
}
for _, file := range n.lst {
if file.path != n.path + "/" + name {
continue
}
log.Debug("FUSE Lookup successful").Str("path", file.path).Send()
if file.isDir {
stable := fs.StableAttr{
Mode: fuse.S_IFDIR,
Ino: inodemap[file.path],
}
operations := &ITNode{kind: 2, path: file.path}
child := n.NewInode(ctx, operations, stable)
return child, 0
} else {
stable := fs.StableAttr{
Mode: fuse.S_IFREG,
Ino: inodemap[file.path],
}
operations := &ITNode{
kind: 2, path: file.path,
self: file,
}
child := n.NewInode(ctx, operations, stable)
return child, 0
}
break
}
log.Warn("FUSE Lookup failed").Str("path", n.path + "/" + name).Send()
}
return nil, syscall.ENOENT
}
type bytesFileReadHandle struct {
content []byte
}
var _ fs.FileReader = (*bytesFileReadHandle)(nil)
func (fh *bytesFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
log.Debug("FUSE Executing Read").Int("size", len(fh.content)).Send()
end := off + int64(len(dest))
if end > int64(len(fh.content)) {
end = int64(len(fh.content))
}
return fuse.ReadResultData(fh.content[off:end]), 0
}
type sensorFileReadHandle struct {
content []byte
}
var _ fs.FileReader = (*sensorFileReadHandle)(nil)
func (fh *sensorFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
log.Info("Executing Read").Int("size", len(fh.content)).Send()
end := off + int64(len(dest))
if end > int64(len(fh.content)) {
end = int64(len(fh.content))
}
return fuse.ReadResultData(fh.content[off:end]), 0
}
var _ fs.FileFlusher = (*sensorFileReadHandle)(nil)
func (fh *sensorFileReadHandle) Flush(ctx context.Context) (errno syscall.Errno) {
return 0
}
type bytesFileWriteHandle struct {
content []byte
path string
}
var _ fs.FileWriter = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) {
log.Info("Executing Write").Str("path", fh.path).Int("prev_size", len(fh.content)).Int("next_size", len(data)).Send()
if off != int64(len(fh.content)) {
log.Error("FUSE Write file size changed unexpectedly").Int("expect", int(off)).Int("received", len(fh.content)).Send()
return 0, syscall.ENXIO
}
fh.content = append(fh.content[:], data[:]...)
return uint32(len(data)), 0
}
var _ fs.FileFlusher = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno) {
log.Debug("FUSE Attempting flush").Str("path", fh.path).Send()
fp, err := myfs.Create(fh.path, uint32(len(fh.content)))
if err != nil {
log.Error("FUSE Flush failed: create").Str("path", fh.path).Err(err).Send()
return syscallErr(err)
}
if len(fh.content) == 0 {
log.Debug("FUSE Flush no data to write").Str("path", fh.path).Send()
err = fp.Close()
if err != nil {
log.Error("FUSE Flush failed during close").Str("path", fh.path).Err(err).Send()
return syscallErr(err)
}
return 0
}
go func() {
// For every progress event
for sent := range fp.Progress() {
log.Debug("FUSE Flush progress").Int("bytes", int(sent)).Int("total", len(fh.content)).Send()
}
}()
r := bytes.NewReader(fh.content)
nread, err := io.Copy(fp, r)
if err != nil {
log.Error("FUSE Flush failed during write").Str("path", fh.path).Err(err).Send()
fp.Close()
yannickulrich marked this conversation as resolved
Review

These should be debug logs rather than info logs. Also, the message should be a bit more specific, something like "FUSE getattr". Same for all the similar logs.

These should be debug logs rather than info logs. Also, the message should be a bit more specific, something like "FUSE getattr". Same for all the similar logs.
Review

Done in b5328ec

Done in b5328ec
return syscallErr(err)
}
if int(nread) != len(fh.content) {
log.Error("FUSE Flush failed during write").Str("path", fh.path).Int("expect", len(fh.content)).Int("got", int(nread)).Send()
fp.Close()
return syscall.EIO
}
err = fp.Close()
if err != nil {
log.Error("FUSE Flush failed during close").Str("path", fh.path).Err(err).Send()
return syscallErr(err)
}
log.Debug("FUSE Flush done").Str("path", fh.path).Int("size", len(fh.content)).Send()
return 0
}
var _ fs.FileFsyncer = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
return fh.Flush(ctx)
}
var _ fs.NodeGetattrer = (*ITNode)(nil)
func (bn *ITNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
log.Debug("FUSE getattr").Str("path", bn.path).Send()
out.Ino = bn.Ino
out.Mtime = bn.self.modtime
out.Ctime = bn.self.modtime
out.Atime = bn.self.modtime
out.Size = uint64(bn.self.size)
return 0
}
var _ fs.NodeSetattrer = (*ITNode)(nil)
func (bn *ITNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
log.Debug("FUSE setattr").Str("path", bn.path).Send()
out.Size = 0
out.Mtime = 0
return 0
}
var _ fs.NodeOpener = (*ITNode)(nil)
func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
switch f.kind {
case 2:
// FS file
if openFlags&syscall.O_RDWR != 0 {
log.Error("FUSE Open failed: RDWR").Str("path", f.path).Send()
return nil, 0, syscall.EROFS
}
if openFlags & syscall.O_WRONLY != 0 {
log.Debug("FUSE Opening for write").Str("path", f.path).Send()
fh = &bytesFileWriteHandle{
path : f.path,
content : make([]byte, 0),
}
return fh, fuse.FOPEN_DIRECT_IO, 0
} else {
log.Debug("FUSE Opening for read").Str("path", f.path).Send()
fp, err := myfs.Open(f.path)
if err != nil {
log.Error("FUSE: Opening failed").Str("path", f.path).Err(err).Send()
return nil, 0, syscallErr(err)
}
defer fp.Close()
b := &bytes.Buffer{}
go func() {
// For every progress event
for sent := range fp.Progress() {
log.Debug("FUSE Read progress").Int("bytes", int(sent)).Int("total", int(f.self.size)).Send()
}
}()
_, err = io.Copy(b, fp)
if err != nil {
log.Error("FUSE Read failed").Str("path", f.path).Err(err).Send()
fp.Close()
return nil, 0, syscallErr(err)
}
fh = &bytesFileReadHandle{
content: b.Bytes(),
}
return fh, fuse.FOPEN_DIRECT_IO, 0
}
case 3:
// Device file
// disallow writes
if openFlags&(syscall.O_RDWR|syscall.O_WRONLY) != 0 {
return nil, 0, syscall.EROFS
}
for _, value := range properties {
if value.Ino == f.Ino {
ans, err := value.gen()
if err != nil {
return nil, 0, syscallErr(err)
}
fh = &sensorFileReadHandle{
content : ans,
}
return fh, fuse.FOPEN_DIRECT_IO, 0
}
}
}
return nil, 0, syscall.EINVAL
}
var _ fs.NodeCreater = (*ITNode)(nil)
func (f *ITNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
if f.kind != 2 {
return nil, nil, 0, syscall.EROFS
}
path := f.path + "/" + name
ino := uint64(len(inodemap)) + 11
inodemap[path] = ino
stable := fs.StableAttr{
Mode: fuse.S_IFREG,
Ino: ino,
}
operations := &ITNode{
kind: 2, Ino: ino,
path : path,
}
node = f.NewInode(ctx, operations, stable)
fh = &bytesFileWriteHandle{
path : path,
content : make([]byte, 0),
}
log.Debug("FUSE Creating file").Str("path", path).Send()
errno = 0
return node, fh, fuseFlags, 0
}
var _ fs.NodeMkdirer = (*ITNode)(nil)
func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
if f.kind != 2 {
return nil, syscall.EROFS
}
path := f.path + "/" + name
err := myfs.Mkdir(path)
if err != nil {
log.Error("FUSE Mkdir failed").
Str("path", path).
Err(err).
Send()
return nil, syscallErr(err)
}
ino := uint64(len(inodemap)) + 11
inodemap[path] = ino
stable := fs.StableAttr{
Mode: fuse.S_IFDIR,
Ino: ino,
}
operations := &ITNode{
kind: 2, Ino: ino,
path : path,
}
node := f.NewInode(ctx, operations, stable)
log.Debug("FUSE Mkdir success").
Str("path", path).
Int("ino", int(ino)).
Send()
return node, 0
}
var _ fs.NodeRenamer = (*ITNode)(nil)
func (f *ITNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno {
if f.kind != 2 {
return syscall.EROFS
}
p1 := f.path + "/" + name
p2 := newParent.EmbeddedInode().Path(nil)[2:] + "/" + newName
err := myfs.Rename(p1, p2)
if err != nil {
log.Error("FUSE Rename failed").
Str("src", p1).
Str("dest", p2).
Err(err).
Send()
return syscallErr(err)
}
log.Debug("FUSE Rename sucess").
Str("src", p1).
Str("dest", p2).
Send()
ino := inodemap[p1]
delete(inodemap, p1)
inodemap[p2] = ino
return 0
}
var _ fs.NodeUnlinker = (*ITNode)(nil)
func (f *ITNode) Unlink(ctx context.Context, name string) syscall.Errno {
if f.kind != 2 {
return syscall.EROFS
}
delete(inodemap, f.path + "/" + name)
err := myfs.Remove(f.path + "/" + name)
if err != nil {
log.Error("FUSE Unlink failed").
Str("file", f.path + "/" + name).
Err(err).
Send()
return syscallErr(err)
}
log.Debug("FUSE Unlink success").
Str("file", f.path + "/" + name).
Send()
return 0
}
var _ fs.NodeRmdirer = (*ITNode)(nil)
func (f *ITNode) Rmdir(ctx context.Context, name string) syscall.Errno {
return f.Unlink(ctx, name)
}

View File

@ -0,0 +1,67 @@
package fusefs
import (
"go.arsenm.dev/infinitime/blefs"
"syscall"
)
func syscallErr(err error) syscall.Errno {
if err == nil {
return 0
}
switch err {
case blefs.FSError{0x02}: // filesystem error
return syscall.EIO // TODO
case blefs.FSError{0x05}: // read-only filesystem
return syscall.EROFS
case blefs.FSError{0x03}: // no such file
return syscall.ENOENT
case blefs.FSError{0x04}: // protocol error
return syscall.EPROTO
case blefs.FSError{-5}: // input/output error
return syscall.EIO
case blefs.FSError{-84}: // filesystem is corrupted
return syscall.ENOTRECOVERABLE // TODO
case blefs.FSError{-2}: // no such directory entry
return syscall.ENOENT
case blefs.FSError{-17}: // entry already exists
return syscall.EEXIST
case blefs.FSError{-20}: // entry is not a directory
return syscall.ENOTDIR
case blefs.FSError{-39}: // directory is not empty
return syscall.ENOTEMPTY
case blefs.FSError{-9}: // bad file number
return syscall.EBADF
case blefs.FSError{-27}: // file is too large
return syscall.EFBIG
case blefs.FSError{-22}: // invalid parameter
return syscall.EINVAL
case blefs.FSError{-28}: // no space left on device
return syscall.ENOSPC
case blefs.FSError{-12}: // no more memory available
return syscall.ENOMEM
case blefs.FSError{-61}: // no attr available
return syscall.ENODATA // TODO
case blefs.FSError{-36}: // file name is too long
return syscall.ENAMETOOLONG
case blefs.ErrFileNotExists: // file does not exist
return syscall.ENOENT
case blefs.ErrFileReadOnly: // file is read only
return syscall.EACCES
case blefs.ErrFileWriteOnly: // file is write only
return syscall.EACCES
case blefs.ErrInvalidOffset: // invalid file offset
return syscall.EFAULT // TODO
case blefs.ErrOffsetChanged: // offset has already been changed
return syscall.ESPIPE
case blefs.ErrReadOpen: // only one file can be opened for reading at a time
return syscall.ENFILE
case blefs.ErrWriteOpen: // only one file can be opened for writing at a time
return syscall.ENFILE
case blefs.ErrNoRemoveRoot: // refusing to remove root directory
return syscall.EPERM
}
return syscall.EIO // TODO
}

View File

@ -0,0 +1,15 @@
package fusefs
import (
_ "unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
)
func Unmount(mountPoint string) error {
return unmount(mountPoint, &fuse.MountOptions{DirectMount: false})
}
// Unfortunately, the FUSE library does not export its unmount function,
// so this is required until that changes
//go:linkname unmount github.com/hanwen/go-fuse/v2/fuse.unmount
func unmount(mountPoint string, opts *fuse.MountOptions) error

View File

@ -181,6 +181,14 @@ func main() {
log.Error("Error intializing puremaps integration").Err(err).Send() log.Error("Error intializing puremaps integration").Err(err).Send()
} }
// Start fuse socket
if k.Bool("fuse.enabled") {
err = startFUSE(ctx, dev)
if err != nil {
log.Error("Error starting fuse socket").Err(err).Send()
}
}
// Start control socket // Start control socket
err = startSocket(ctx, dev) err = startSocket(ctx, dev)
if err != nil { if err != nil {