Added FUSE support #55
@ -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
60
fuse.go
Normal 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
1
go.mod
@ -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
2
go.sum
@ -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
572
internal/fusefs/fuse.go
Normal 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
|
|||||||
|
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
Elara6331
commented
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.
yannickulrich
commented
Done in 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)
|
||||||
|
}
|
67
internal/fusefs/syscallerr.go
Normal file
67
internal/fusefs/syscallerr.go
Normal 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
|
||||||
|
|
||||||
|
}
|
15
internal/fusefs/unmount.go
Normal file
15
internal/fusefs/unmount.go
Normal 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
|
8
main.go
8
main.go
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user
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 tocat
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 useitctl
if they want continuous data, because files aren't meant to work this way.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:In the
itd
log I also see the Done method of the watcher getting called. Could you perhaps elaborate on the problem you see?Try this with something that returns data less commonly, such as
battery
. Every timecat
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.
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.
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.