Giter Club home page Giter Club logo

fuse3's Introduction

fuse3

an async version fuse library for rust

Cargo Documentation License

feature

  • support unprivileged mode by using fusermount3
  • support readdirplus to improve read dir performance
  • support posix file lock
  • support handles the O_TRUNC open flag
  • support async direct IO
  • support enable no_open and no_open_dir option

still not support

  • ioctl implement
  • fuseblk mode
  • macos support

unstable

  • poll
  • notify_reply

Supported Rust Versions

The minimum supported version is 1.63.

License

MIT

fuse3's People

Contributors

aarond10 avatar asomers avatar kongzhanghao avatar majewsky avatar sherlock-holo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

fuse3's Issues

Problem with hard link support

I found that when running ln target_file link_file in a mounted directory where target_file exists and link_file does not exist, it shows an error: ln: failed to create hard link 'link_file' => 'target_file': No such file or directory. When checking the log, it shows that lookup(target_file) and lookup(link_file) are called, while lookup(target_file) finds the inode and lookup(link_file) doesn't find anything, which is expected. But then it says because link_file doesn't exist, there is an ENOENT error, which is very weird. If I create a file called link_file, it shows the error: ln: failed to create hard link 'link_file': file exists. Also, link() in the filesystem trait is never called and the opcode for it is never received.

Unused const `FUSE_WRITE_CACHE`

The function documentation of write_back() in file //src/mount_options.rs says that the write flag FUSE_WRITE_CACHE should be enabled to use the write back cache for buffered writes. However, it seems that flag FUSE_WRITE_CACHE in file //src/raw/abi.rs doesn't appear referenced anywhere else except in this comment. I'm wondering how this actually works to enable the write buffer?

Session::mount should provide feedback on success

Currently, Session::mount returns a future that does not complete until the file system is unmounted or an error occurs. That's fine for programs that mount one file system and do nothing else. But it's not so good for programs that do anything more. A program that mounts multiple file systems, or that does anything else, might want to know whether the mount succeeded. This is not possible with Session::mount's current signature.

To improve it, I suggest one of the following;

  • Session::mount does the mount_empty_check synchronously, calls libc::mount (or equivalent) synchronously, then returns Result<dyn impl Future<...>, io::Error>. An immediate error indicates that the mount failed. A Future indicates that the mount succeeded, and the future will complete when the file system is unmounted.
  • Session::mount does the mount_empty_check and libc::mount asynchronously, and returns a dyn impl Future<dyn impl Future<...>>. The outer future will complete as soon as libc::mount does. If libc::mount should succeed, then the inner future will complete when the file system is unmounted.
  • Session::mount does everything asynchronously, and returns an dyn impl Future<tokio::task::JoinHandle>. The future completes after libc::mount does, and the JoinHandle completes when the file system is unmounted. This is basically the same as the second option, but more explicit.

What do you think? Do any of these options sound acceptable to you?

fuse3 wrongly assumes sequential directory offsets in handle_readdir

The FUSE protocol allows each directory entry to include a off field in struct fuse_dirent. It's purpose is to uniquely identify a directory entry so that a future FUSE_READDIR operation may continue where a previous one left off. The FUSE file system is allowed to choose the format of those off values. In particular, sequentially increasing indexes are usually not adequate, because if a directory entry is deleted in between two FUSE_READDIR operations, using sequential off values could lead to skipping over a directory entry.
fuse3 currently manages the off value itself, in handle_readdir, and provides the file system with no means to override it. Instead, the off value should be specified by the file system as part of reply::DirectoryEntry.

Don't poll /dev/fuse for writing

/dev/fuse may block on reads, which is why fuse3 polls it with a tokio AsyncFd. But it will never, ever block on writes (at least on FreeBSD; I don't know about other operating systems). That's why FreeBSD didn't even support polling it for writeability until a few days ago. So fuse3 doesn't need to poll it; it can just charge ahead and write. So I think fuse3 shouldn't. It can change the FuseConnection struct to be like this:

#[derive(Debug)]
pub struct FuseConnection {
    rfd: AsyncFd<RawFd>,
    wfd: RawFd,
    read: Mutex<()>,
    write: Mutex<()>,
}

, using the wrapped file descriptor for reading and the unwrapped copy for writing. That would be more efficient than polling for writeability, and it would also work on older versions of FreeBSD.

Use Timespec instead of SystemTime in raw::reply::FileAttr

std::time::SystemTime may be convenient for passthrough file systems, where the time will come from std::fs::Metadata already in that format. But it's not so convenient for raw file systems. Raw file systems more likely store the timestamps in a serialized format. Converting between serialized timestamps and SystemTime requires needless comparisons with UNIX_EPOCH. It would be more convenient if raw::reply::FileAttr used libc::timespec, which is pretty close to what the FUSE protocol wants anyway.

What is the status of fuse3?

I'm looking at Rust fuse libs for a distributed FS and wonder what is the status of fuse3?

cargo build in ./examples succeeds (with a handful of warnings) but am not sure how to run them?

cargo build in ./ fails with an error:

error[E0277]: the trait bound `(): std::future::Future` is not satisfied
  --> src/spawn.rs:16:38
   |
16 | pub fn spawn_blocking<F, T>(f: F) -> impl Future<Output = T>
   |                                      ^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::future::Future` is not implemented for `()`
   |
   = note: the return type of a function must have a statically known size

error: aborting due to previous error; 35 warnings emitted

For more information about this error, try `rustc --explain E0277`.
error: could not compile `fuse3`.

I'm still new to Rust so maybe missing something obvious. Thanks.

Double data copy due to combined header and data buffers

Most of the methods of Session create a Vec and copy two slices into it: the fuse_out_header and an opcode-specific response. However, this Vec is short-lived: its contents get copied again by nix::unistd::write to the fuse device.
I suggest eliminating the first data copy by changing the Session::response_sender's type to include separate buffers for the header and the data. Then use nix::unistd::writev to pass them to the kernel. It would look like this:

pub struct Session<'a, FS> {
    ...
    response_sender: UnboundedSender<(fuse_out_header, Vec)>,
    ...
}
impl FuseConnection {
        pub async fn write(&self, response: (fuse_out_header, Vec) -> Result<usize, io::Error> {
            let _guard = self.write.lock().await;

            let header_data = get_bincode_config()
                .serialize(&response.0)
                .unwrap();
            let iovecs = [IoVec::from_slice(&header_data), IoVec::from_slice(&response.1)];
            loop {
                let mut write_guard = self.fd.writable().await?;
                if let Ok(result) = write_guard.try_io(|fd| {
                    unistd::writev(fd.as_raw_fd(), &iovecs).map_err(io::Error::from)
                }) {
                    return result;
                } else {
                    continue;
                }
            }
        }

}

Double data copy due to use of Bytes

The ReplyData and ReplyXAttr types use bytes::Bytes to pass data from the filesystem to fuse3. Because those methods are async fn, it makes sense that the buffer type must be 'static. However, using bytes::Bytes is unnecessarily restrictive. My file system does not use Bytes internally, so I must do a data copy from my own buffer type into a Bytes, only for fuse3 to immediately copy it again with Vec::extend_from_slice.
The extra data copy could be eliminated if Filesystem has a generic parameter type Buffer: AsRef<[u8]> + 'static, and the ReplyData and ReplyXAttr types use that instead of Bytes.

Needs CI

fuse3 could benefit from continuous integration. Even without any tests, it would be good to compile-check it, at least. I suggest Cirrus CI, because it works well with Linux, FreeBSD, and OSX. If you follow the instructions at https://cirrus-ci.org/guide/quick-start/ and stop at "Post Installation", I'll submit a .cirrus.yml file.

update MSRV

I think we can follow the nix MSRV, because we depend on nix, otherwise it may cause some problems when user using a low version rustc

How to cleanly unmount? How to detect external unmount?

  1. Is there any way to unmount cleanly? Currently I have to fusermount -u every time I exit. I tried dropping MountHandle on custom CTRL-C handler but it doesn't seem to do the trick.
  2. Is there any way to detect when fusermount -u has been called on my mount point? The MountHandle seems to stay alive while I'd expect the .await to resolve when unmounted.

I don't have a deep mental model of FUSE so please bear with me if my questions don't make sense.

Is there a way to trigger `destroy()` in `FileSystem` trait on Linux after mounting?

After the filesystem is mounted on Linux with mount_with_unprivileged(), I'm trying to trigger the destroy() method in FileSystem trait to actually close the filesystem. I've been trying with fusermount -u /path/ and umount /path/, but both commands didn't trigger destroy(). I've also tried killing the fuse process, but it still didn't work. May I know if there is a way to trigger destroy()?

Latest version doesn't build on Debian 11

Reproduction steps:

  1. Install debian:11 (you can try this in docker)
  2. Install rust (rustup) version 1.63.0
  3. Create an empty crate with fuse3 (either v5.0 or the latest from git) and the tokio-runtime features
  4. Build

Error:

error[E0425]: cannot find function `getuid` in module `unistd`
   --> /home/patrick/.cargo/git/checkouts/fuse3-ea5edbf459049c77/0466328/src/mount_options.rs:217:52
    |
217 |                 self.uid.unwrap_or_else(|| unistd::getuid().as_raw())
    |                                                    ^^^^^^
    |
   ::: /home/patrick/.cargo/registry/src/github.com-1ecc6299db9ec823/nix-0.25.0/src/unistd.rs:662:1
    |
662 | pub fn getcwd() -> Result<PathBuf> {
    | ---------------------------------- similarly named function `getcwd` defined here
    |
help: a function with a similar name exists
    |
217 |                 self.uid.unwrap_or_else(|| unistd::getcwd().as_raw())
    |                                                    ~~~~~~
help: consider importing this function
    |
1   | use libc::getuid;
    |
help: if you import `getuid`, refer to it directly
    |
217 -                 self.uid.unwrap_or_else(|| unistd::getuid().as_raw())
217 +                 self.uid.unwrap_or_else(|| getuid().as_raw())
    |

error[E0425]: cannot find function `getgid` in module `unistd`
   --> /home/patrick/.cargo/git/checkouts/fuse3-ea5edbf459049c77/0466328/src/mount_options.rs:221:52
    |
221 |                 self.gid.unwrap_or_else(|| unistd::getgid().as_raw())
    |                                                    ^^^^^^
    |
   ::: /home/patrick/.cargo/registry/src/github.com-1ecc6299db9ec823/nix-0.25.0/src/unistd.rs:662:1
    |
662 | pub fn getcwd() -> Result<PathBuf> {
    | ---------------------------------- similarly named function `getcwd` defined here
    |
help: a function with a similar name exists
    |
221 |                 self.gid.unwrap_or_else(|| unistd::getcwd().as_raw())
    |                                                    ~~~~~~
help: consider importing this function
    |
1   | use libc::getgid;
    |
help: if you import `getgid`, refer to it directly
    |
221 -                 self.gid.unwrap_or_else(|| unistd::getgid().as_raw())
221 +                 self.gid.unwrap_or_else(|| getgid().as_raw())
    |

For more information about this error, try `rustc --explain E0425`.
error: could not compile `fuse3` due to 2 previous errors

example memfs privileged mount will stuck

change example memfs mount_with_unprivileged to use mount will mount success, but when try to access the mount point such as ls will stuck, the strace show it stucks at

statx(AT_FDCWD, "ttt", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT, STATX_MODE,

Enhancement request: allow vectored buffers with ReplyData

It's possible that a FUSE server may store file data in discrete pieces. Ideally the data would only need to be copied once, when writing to /dev/fuse with writev. But fuse3's ReplyData struct cannot accept vectored data. Instead, it forces the file system to do an extra data copy if the data is vectored.

As an enhancement, it would be great if fuse3 had a way for read to return vectored data.

Enhancement request: use i32 as error type for Filesystem

fuse3 currently defines a custom Error type, a wrapper around errno, which implements From<i32>. That is pretty convenient to use for a crate whose native errors are also i32, or whose custom error type can implement Into<fuse3::Errno>. However, it's a problem for a crate whose custom error type comes from a third crate, because Rust's trait impl rules don't allow a crate to implement From or Into to convert between two external types.
Looking at the source, it seems the fuse3::Error type only exists for the sake of a few one-line convenience methods like is_not_exist and new_not_exist. I think the switch could be done with almost no net change in line count. If you agree, I can prepare a PR.

Extra data copy during write

During a write, fuse3 first copies data from the kernel into userland in Session::dispatch. Then it passes a slice of that buffer to handle_write, which ends up copying the data again into a new Vec. It then passes that data as a slice to Filesystem::write, where it might well be copied again. The same thing happens in setxattr.

Instead, Session::dispatch should read from /dev/fuse using readv into a header-sized buffer and a large data buffer. Then it should pass the data buffer by value to Filesystem::write using a Vec. That would eliminate one data copy, and possibly two, depending on how the file system implements write.

Requesting new minor release bump on crates.io

Would it be possible to bump the minor version to 0.5.2 and push it to crates.io? I have an project that is dependent on these changes and would love to be able to just refer to a stable crates.io release.

setxattr wrongly restricts the attributes value to a OsStr

The handle_setxattr method tries to convert the extended attribute's value to an OsString, and this is what it passes to the file system's setxattr method. But that's wrong. It's perfectly valid for an extended attribute to have binary data with one, many, or no NUL characters. Also, this signature is asymmetric with getxattr, which returns an extended attribute's value as Bytes.

setxattr should be changed to pass the value as a &[u8], just as write does.

`readlink()` doesn't work properly when `FUSE_CACHE_SYMLINKS` is enabled

I found that after a symlink is created, for example with command ln -s name link, the command ls -la will show the symlink as path -> '' rather than path -> name. I removed line 754 of file /src/raw/session.rs (i.e., reply_flags |= FUSE_CACHE_SYMLINKS), the symlink can then be created and read normally. What would be the possible cause of this? I'm also investigating it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.