Added FUSE support #55

Merged
Elara6331 merged 65 commits from yannickulrich/itd:fuse into master 2023-03-25 22:23:52 +00:00
2 changed files with 46 additions and 33 deletions
Showing only changes of commit 87c78566c1 - Show all commits

35
fuse.go
View File

@ -12,7 +12,7 @@ import (
func startFuse(ctx context.Context, dev *infinitime.Device) error { func startFuse(ctx context.Context, dev *infinitime.Device) error {
// This is where we'll mount the FS // This is where we'll mount the FS
os.Mkdir(k.String("fuse.mountpoint"), 0755) os.Mkdir(k.String("fuse.mountpoint"), 0755)
root := &ITNode{kind: 0} root := fusefs.BuildRootNode(dev)
server, err := fs.Mount(k.String("fuse.mountpoint"), root, &fs.Options{ server, err := fs.Mount(k.String("fuse.mountpoint"), root, &fs.Options{
MountOptions: fuse.MountOptions{ MountOptions: fuse.MountOptions{
// Set to true to see how the file system works. // Set to true to see how the file system works.
@ -32,43 +32,12 @@ func startFuse(ctx context.Context, dev *infinitime.Device) error {
Str("target", k.String("fuse.mountpoint")). Str("target", k.String("fuse.mountpoint")).
Send() Send()
properties[0] = ITProperty{"heartrate", 2, fusefs.BuildProperties(dev)
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchHeartRate(ctx)
return converterU8(ctx, ans), err
}}
properties[1] = ITProperty{"battery", 3,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchBatteryLevel(ctx)
return converterU8(ctx, ans), err
}}
properties[2] = ITProperty{"motion", 4,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchMotion(ctx)
return converterMotionValues(ctx, ans), err
}}
properties[3] = ITProperty{"stepcount", 5,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchStepCount(ctx)
return converterU32(ctx, ans), err
}}
properties[4] = ITProperty{"version", 6,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.Version()
return converter1String(ctx, ans), err
}}
properties[5] = ITProperty{"address", 7,
func(ctx context.Context) (<-chan []byte, error) {
ans := dev.Address()
return converter1String(ctx, ans), nil
}}
myfs, err = dev.FS()
if err != nil { if err != nil {
log.Warn("Error getting BLE filesystem").Err(err).Send() log.Warn("Error getting BLE filesystem").Err(err).Send()
return err return err
} }
inodemap = make(map[string]uint64)
// Wait until unmount before exiting // Wait until unmount before exiting
go server.Serve() go server.Serve()

View File

@ -89,7 +89,51 @@ type ITNode struct {
path string path string
} }
func BuildRootNode(dev *infinitime.Device) *ITNode {
inodemap = make(map[string]uint64)
myfs, _ = dev.FS()
return &ITNode{kind: 0}
}
var properties = make([]ITProperty, 6) var properties = make([]ITProperty, 6)
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.
func BuildProperties(dev *infinitime.Device) {
properties[0] = ITProperty{"heartrate", 2,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchHeartRate(ctx)
return converterU8(ctx, ans), err
}}
properties[1] = ITProperty{"battery", 3,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchBatteryLevel(ctx)
return converterU8(ctx, ans), err
}}
properties[2] = ITProperty{"motion", 4,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchMotion(ctx)
return converterMotionValues(ctx, ans), err
}}
properties[3] = ITProperty{"stepcount", 5,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchStepCount(ctx)
return converterU32(ctx, ans), err
}}
properties[4] = ITProperty{"version", 6,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.Version()
return converter1String(ctx, ans), err
}}
properties[5] = ITProperty{"address", 7,
func(ctx context.Context) (<-chan []byte, error) {
ans := dev.Address()
return converter1String(ctx, ans), nil
}}
}
var myfs *blefs.FS = nil; var myfs *blefs.FS = nil;
var inodemap map[string]uint64 = nil; var inodemap map[string]uint64 = nil;