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 28 additions and 6 deletions
Showing only changes of commit a54ca7afdf - Show all commits

10
fuse.go
View File

@ -12,7 +12,15 @@ import (
func startFUSE(ctx context.Context, dev *infinitime.Device) error {
yannickulrich marked this conversation as resolved Outdated

This function should be called startFUSE instead to adhere to Go naming conventions

This function should be called `startFUSE` instead to adhere to Go naming conventions

Thanks, I'm relatively new to Go, sorry for these issues

Done in 673383f

Thanks, I'm relatively new to Go, sorry for these issues Done in 673383f
// This is where we'll mount the FS
os.Mkdir(k.String("fuse.mountpoint"), 0755)
yannickulrich marked this conversation as resolved Outdated

Use os.MkdirAll here instead so that it creates parent directories, and handle the error (just return it).

Use `os.MkdirAll` here instead so that it creates parent directories, and handle the error (just return it).

This is actually a bit more complicated than that. If the mountpoint already exists, fuse will crash. We also can't delete the mountpoint beforehand (rm: cannot remove '/tmp/itd/mnt': Transport endpoint is not connected). The best way to solve this should be calling the unmount function. How would you suggest going about doing this?

This is actually a bit more complicated than that. If the mountpoint already exists, fuse will crash. We also can't delete the mountpoint beforehand (`rm: cannot remove '/tmp/itd/mnt': Transport endpoint is not connected`). The best way to solve this should be calling the [unmount function](https://pkg.go.dev/github.com/hanwen/go-fuse/v2@v2.2.0/fuse#Server.Unmount). How would you suggest going about doing this?

Yeah, this one is going to be a bit more complicated. The FUSE library does have a different unmount function that you could call before trying to mount the fs, but it's not exported, so we'll need to do a small hack to get access to it anyway. In the fusefs package, add a file called unmount.go with the following contents:

unmount.go
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

Then, you can simply call that function at the top of startFUSE like so:

// Ignore the error because nothing might be mounted on the mountpoint
_ = fusefs.Unmount(k.String("fuse.mountpoint"))

I'll file an issue with the fuse library to export that function once this is merged.

The best way to solve this should be calling the unmount function.

I agree that this should be done. However, this doesn't solve the problem completely because if ITD panics for whatever reason, it won't get run and then the user will get a confusing message. Let me see what the best way would be to call this function on shutdown.

Yeah, this one is going to be a bit more complicated. The FUSE library does have [a different unmount function](https://github.com/hanwen/go-fuse/blob/0f728ba15b38579efefc3dc47821882ca18ffea7/fuse/mount_linux.go#L133) that you could call before trying to mount the fs, but it's not exported, so we'll need to do a small hack to get access to it anyway. In the `fusefs` package, add a file called `unmount.go` with the following contents: <details> <summary><code>unmount.go</code></summary> ```go 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 ``` </details> Then, you can simply call that function at the top of `startFUSE` like so: ```go // Ignore the error because nothing might be mounted on the mountpoint _ = fusefs.Unmount(k.String("fuse.mountpoint")) ``` I'll file an issue with the fuse library to export that function once this is merged. > The best way to solve this should be calling the unmount function. I agree that this should be done. However, this doesn't solve the problem completely because if ITD panics for whatever reason, it won't get run and then the user will get a confusing message. Let me see what the best way would be to call this function on shutdown.

Cool, thank you! I have implemented this 🙂

Cool, thank you! I have implemented this 🙂
root := fusefs.BuildRootNode(dev)
root, err := fusefs.BuildRootNode(dev)
if err != nil {
log.Error("Building root node failed").
Err(err).
Send()
return err
return err
yannickulrich marked this conversation as resolved Outdated

err is returned twice here

`err` is returned twice here

Oh, sorry, fixed now

Oh, sorry, fixed now
}
server, err := fs.Mount(k.String("fuse.mountpoint"), root, &fs.Options{
MountOptions: fuse.MountOptions{
// Set to true to see how the file system works.

View File

@ -39,11 +39,16 @@ type ITNode struct {
var myfs *blefs.FS = nil
var inodemap map[string]uint64 = nil
yannickulrich marked this conversation as resolved Outdated

This error needs to be handled, you can just return it in this case and update the code that calls this function to do the actual handling

This error needs to be handled, you can just return it in this case and update the code that calls this function to do the actual handling

Done in a54ca7a

Done in a54ca7a
func BuildRootNode(dev *infinitime.Device) *ITNode {
func BuildRootNode(dev *infinitime.Device) (*ITNode, error) {
var err error
inodemap = make(map[string]uint64)
myfs, _ = dev.FS()
myfs, err = dev.FS()
if err != nil {
log.Error("FUSE failed to get filesystem").Err(err).Send()
return nil, err
}
return &ITNode{kind: 0}
return &ITNode{kind: 0}, nil
}
var properties = make([]ITProperty, 6)
@ -118,12 +123,21 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
case 2:
// on device
files, _ := myfs.ReadDir(n.path)
files, err := myfs.ReadDir(n.path)
if err != nil {
log.Error("ReadDir failed").Str("path", n.path).Err(err).Send()
return nil, syscall.ENOENT
Elara6331 marked this conversation as resolved Outdated

How would you recommend doing this? I suppose it could fail for all sorts of reasons such as

  • no such file or directory ENOENT
  • generic input/output error EIO
  • invalid argument EINVAL
  • connection aborted ECONNABORTED
  • connection refused ECONNREFUSED
  • connection reset ECONNRESET
  • is actually a file EISNAM

In other places such as when we open a file, we could have

  • is actually a folder EISDIR
  • file exists EEXIST

Again, I'm sorry but I'm not a Go expert and don't really now how to do this properly.. especially when dealing with FSError

How would you recommend doing this? I suppose it could fail for all sorts of reasons such as * no such file or directory `ENOENT` * generic input/output error `EIO` * invalid argument `EINVAL` * connection aborted `ECONNABORTED` * connection refused `ECONNREFUSED` * connection reset `ECONNRESET` * is actually a file `EISNAM` In other places such as when we open a file, we could have * is actually a folder `EISDIR` * file exists `EEXIST` Again, I'm sorry but I'm not a Go expert and don't really now how to do this properly.. especially when dealing with `FSError`

The err can be several different kinds of errors, and FSError is just one of them. It's actually a type I made. You can see it here: https://gitea.arsenm.dev/Arsen6331/infinitime/src/commit/512d48bc2469/blefs/error.go#L20. It contains an error code you can check to see what went wrong, and you can scroll down to see the meaning of each code.

In this case, I'd use a Go type switch to check which error type actually occurred and then check the code or do whatever else needs to be done. Maybe there could be a function like syscallErr() that takes an error and returns the proper syscall error?

If you don't feel comfortable doing that, I can merge this and then implement it myself and send you the commit so you can see how I did it, or you can just try it yourself, whatever you feel would be better.

The `err` can be several different kinds of errors, and `FSError` is just one of them. It's actually a type I made. You can see it here: https://gitea.arsenm.dev/Arsen6331/infinitime/src/commit/512d48bc2469/blefs/error.go#L20. It contains an error code you can check to see what went wrong, and you can scroll down to see the meaning of each code. In this case, I'd use a Go type switch to check which error type actually occurred and then check the code or do whatever else needs to be done. Maybe there could be a function like `syscallErr()` that takes an `error` and returns the proper syscall error? If you don't feel comfortable doing that, I can merge this and then implement it myself and send you the commit so you can see how I did it, or you can just try it yourself, whatever you feel would be better.

Something like in 4c59561a99? There are a few TODO where I'm not sure what the correct POSIX error would be and improvised. If you have a better idea, feel free to change them though

Something like in 4c59561a99? There are a few `TODO` where I'm not sure what the correct POSIX error would be and improvised. If you have a better idea, feel free to change them though

That looks good, thanks

That looks good, thanks
}
log.Info("readdir").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, _ := entry.Info()
info, err := entry.Info()
if err != nil {
log.Error("Info failed").Str("path", n.path).Err(err).Send()
return nil, syscall.ENOENT
}
name := info.Name()
file := DirEntry{