diff --git a/README.md b/README.md index c82b51e..33b6b51 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,236 @@ -# remotefs-fuse - -

- logo -

- -

~ A FUSE Driver for remotefs-rs ~

- -

Developed by @veeso

-

Current version: WIP

- -

- License-MIT - Repo stars - Downloads counter - Latest version - - Ko-fi -

-

- Linux CI - MacOS CI - Windows CI - Docs -

- ---- - -## Get started - -Coming soon... - -## CLI Tool - -remotefs-fuse comes with a CLI tool **remotefs-fuse-cli** to mount remote file systems with FUSE. - -```sh -cargo install remotefs-fuse-cli -``` - -### Features - -remotefs-fuse-cli can be built with the features below; each feature enables a different file transfer protocol - -- `aws-s3` -- `ftp` -- `kube` -- `smb`: requires `libsmbclient` on MacOS and GNU/Linux systems -- `ssh` (enables **both sftp and scp**); requires `libssh2` on MacOS and GNU/Linux systems -- `webdav` - -All the features are enabled by default; so if you want to build it with only certain features, pass the `--no-default-features` option. - -### Usage - -```sh -remotefs-fuse-cli --to /mnt/to --volume [protocol-options...] -``` - -where protocol options are - -- aws-s3 - - `--bucket ` - - `--region ` (optional) - - `--endpoint ` (optional) - - `--profile ` (optional) - - `--access-key ` (optional) - - `--security-token ` (optional) - - `--new-path-style` use new path style -- ftp - - `--hostname ` - - `--port ` (default 21) - - `--username ` (default: `anonymous`) - - `--password ` (optional) - - `--secure` specify it if you want to use FTPS - - `--active` specify it if you want to use ACTIVE mode -- kube - - `--namespace ` (default: `default`) - - `--cluster-url ` -- memory: runs a virtual file system in memory -- smb - - `--address
` - - `--port ` (default: `139`; Linux/Mac only) - - `--share ` - - `--username ` (optional) - - `--password ` (optional) - - `--workgroup ` (optional; Linux/Mac only) -- scp / sftp - - `--hostname ` - - `--port ` (default `22`) - - `--username ` - - `--password ` -- webdav - - `--url ` - - `--username ` - - `--password ` - -## Changelog ⏳ - -View remotefs` changelog [HERE](CHANGELOG.md) - ---- - -## License 📃 - -remotefs is licensed under the MIT license. - -You can read the entire license [HERE](LICENSE) +# remotefs-fuse + +

+ logo +

+ +

~ A FUSE Driver for remotefs-rs ~

+ +

Developed by @veeso

+

Current version: 0.1.0

+ +

+ License-MIT + Repo stars + Downloads counter + Latest version + + Ko-fi +

+

+ Linux CI + MacOS CI + Windows CI + Docs +

+ +--- + +## Get started + +First of all you need to add **remotefs-fuse** to your project dependencies: + +```toml +remotefs-fuse = "^0.1.0" +``` + +these features are supported: + +- `no-log`: disable logging. By default, this library will log via the `log` crate. + +## Example + +```rust,no_run,ignore +use remotefs_fuse::Mount; + +let options = vec![ + #[cfg(unix)] + remotefs_fuse::MountOption::AllowRoot, + #[cfg(unix)] + remotefs_fuse::MountOption::RW, + #[cfg(unix)] + remotefs_fuse::MountOption::Exec, + #[cfg(unix)] + remotefs_fuse::MountOption::Sync, + #[cfg(unix)] + remotefs_fuse::MountOption::FSName(volume), +]; + +let remote = MyRemoteFsImpl::new(); +let mount_path = std::path::PathBuf::from("/mnt/remote"); +let mut mount = Mount::mount(remote, &mount_path, &options).expect("Failed to mount"); +let mut umount = mount.unmounter(); + +// setup signal handler +ctrlc::set_handler(move || { + umount.umount().expect("Failed to unmount"); +})?; + +mount.run().expect("Failed to run filesystem event loop"); +``` + +## Requirements + +- **Linux**: you need to have `fuse3` installed on your system. + + Of course, you also need to have the `FUSE` kernel module installed. + To build `remotefs-fuse` on Linux, you need to have the `libfuse3` development package installed. + + In Ubuntu, you can install it with: + + ```sh + sudo apt-get install fuse3 libfuse3-dev + ``` + + In CentOS, you can install it with: + + ```sh + sudo yum install fuse-devel + ``` + +- **macOS**: you need to have the `macfuse` service installed on your system. + + You can install it with: + + ```sh + brew install macfuse + ``` + +- **Windows**: you need to have the `dokany` service installed on your system. + + You can install it from + +## CLI Tool + +remotefs-fuse comes with a CLI tool **remotefs-fuse-cli** to mount remote file systems with FUSE. + +```sh +cargo install remotefs-fuse-cli +``` + +### Features + +remotefs-fuse-cli can be built with the features below; each feature enables a different file transfer protocol + +- `aws-s3` +- `ftp` +- `kube` +- `smb`: requires `libsmbclient` on MacOS and GNU/Linux systems +- `ssh` (enables **both sftp and scp**); requires `libssh2` on MacOS and GNU/Linux systems +- `webdav` + +All the features are enabled by default; so if you want to build it with only certain features, pass the `--no-default-features` option. + +### Usage + +```sh +remotefs-fuse-cli -o opt1 -o opt2=abc --to /mnt/to --volume [protocol-options...] +``` + +where protocol options are + +- aws-s3 + - `--bucket ` + - `--region ` (optional) + - `--endpoint ` (optional) + - `--profile ` (optional) + - `--access-key ` (optional) + - `--security-token ` (optional) + - `--new-path-style` use new path style +- ftp + - `--hostname ` + - `--port ` (default 21) + - `--username ` (default: `anonymous`) + - `--password ` (optional) + - `--secure` specify it if you want to use FTPS + - `--active` specify it if you want to use ACTIVE mode +- kube + - `--namespace ` (default: `default`) + - `--cluster-url ` +- memory: runs a virtual file system in memory +- smb + - `--address
` + - `--port ` (default: `139`; Linux/Mac only) + - `--share ` + - `--username ` (optional) + - `--password ` (optional) + - `--workgroup ` (optional; Linux/Mac only) +- scp / sftp + - `--hostname ` + - `--port ` (default `22`) + - `--username ` + - `--password ` +- webdav + - `--url ` + - `--username ` + - `--password ` + +Other options are: + +- `--uid `: specify the UID to overwrite when mounting the remote fs. See [UID and GID override](#uid-and-gid-override). +- `--gid `: specify the GID to overwrite when mounting the remote fs. See [UID and GID override](#uid-and-gid-override). +- `--default-mode `: set the default file mode to use when the remote fs doesn't support it. + +Mount options can be viewed in the docs at . + +## UID and GID override + +The possibility to override UID and GID is used because sometimes this scenario can happen: + +1. my UID is `1000` +2. I'm mounting for instance a SFTP file system and the remote user I used to sign in has UID `1002` +3. I'm unable to operate on the file system because UID `1000` can't operate to files owned by `1002` + +But of course this doesn't make sense: I signed in with user who owns those files, so I should be able to operate on them. +That's why I've added `Uid` and `Gid` into the `MountOption` variant. + +Setting the `Uid` option to `1002` you'll be able to operate on the File system as it should. + +> ❗ This doesn't apply to Windows. + +## Changelog ⏳ + +View remotefs-fuse`s changelog [HERE](CHANGELOG.md) + +--- + +## License 📃 + +remotefs-fuse is licensed under the MIT license. + +You can read the entire license [HERE](LICENSE) diff --git a/remotefs-fuse-cli/src/cli.rs b/remotefs-fuse-cli/src/cli.rs index d25b232..5a18f2d 100644 --- a/remotefs-fuse-cli/src/cli.rs +++ b/remotefs-fuse-cli/src/cli.rs @@ -15,6 +15,7 @@ mod webdav; use std::path::PathBuf; use argh::FromArgs; +use remotefs_fuse::MountOption; #[cfg(feature = "aws-s3")] use self::aws_s3::AwsS3Args; @@ -44,16 +45,24 @@ pub struct CliArgs { #[argh(option)] pub volume: String, /// uid to use for the mounted filesystem + #[cfg(unix)] #[argh(option)] pub uid: Option, /// gid to use for the mounted filesystem #[argh(option)] + #[cfg(unix)] pub gid: Option, /// default file permissions for those remote file protocols that don't support file permissions. /// /// this is a 3-digit octal number, e.g. 644 #[argh(option, from_str_fn(from_octal))] + #[cfg(unix)] pub default_mode: Option, + /// mount options + /// + /// Mount options are specific to the underlying filesystem and are passed as key=value pairs. + #[argh(option, short = 'o')] + pub option: Vec, /// enable verbose logging. /// /// use multiple times to increase verbosity @@ -63,6 +72,7 @@ pub struct CliArgs { remote: RemoteArgs, } +#[cfg(unix)] fn from_octal(s: &str) -> Result { u32::from_str_radix(s, 8).map_err(|_| "Invalid octal number".to_string()) } diff --git a/remotefs-fuse-cli/src/main.rs b/remotefs-fuse-cli/src/main.rs index f1376d9..2aa37a1 100644 --- a/remotefs-fuse-cli/src/main.rs +++ b/remotefs-fuse-cli/src/main.rs @@ -1,7 +1,7 @@ mod cli; mod remotefs_wrapper; -use remotefs_fuse::{Mount, MountOption}; +use remotefs_fuse::Mount; fn main() -> anyhow::Result<()> { let args = argh::from_env::(); @@ -13,25 +13,29 @@ fn main() -> anyhow::Result<()> { // make options let mut options = vec![ #[cfg(unix)] - MountOption::AllowRoot, + remotefs_fuse::MountOption::AllowRoot, #[cfg(unix)] - MountOption::RW, + remotefs_fuse::MountOption::RW, #[cfg(unix)] - MountOption::Exec, + remotefs_fuse::MountOption::Exec, #[cfg(unix)] - MountOption::Sync, + remotefs_fuse::MountOption::Sync, #[cfg(unix)] - MountOption::FSName(volume), + remotefs_fuse::MountOption::FSName(volume), ]; + options.extend(args.option.clone()); + #[cfg(unix)] if let Some(uid) = args.uid { log::info!("Default uid: {uid}"); options.push(MountOption::Uid(uid)); } + #[cfg(unix)] if let Some(gid) = args.gid { log::info!("Default gid: {gid}"); options.push(MountOption::Gid(gid)); } + #[cfg(unix)] if let Some(default_mode) = args.default_mode { log::info!("Default mode: {default_mode:o}"); options.push(MountOption::DefaultMode(default_mode)); diff --git a/remotefs-fuse/src/lib.rs b/remotefs-fuse/src/lib.rs index 0644b9c..4430084 100644 --- a/remotefs-fuse/src/lib.rs +++ b/remotefs-fuse/src/lib.rs @@ -4,7 +4,39 @@ //! # remotefs-fuse //! -//! TODO +//! **remotefs-fuse** is a library that allows you to mount a remote file system using **FUSE** on Linux and macOS and with +//! **Dokany** on Windows. +//! +//! ## Requirements +//! +//! - **Linux**: you need to have `fuse3` installed on your system. +//! +//! Of course, you also need to have the `FUSE` kernel module installed. +//! To build `remotefs-fuse` on Linux, you need to have the `libfuse3` development package installed. +//! +//! In Ubuntu, you can install it with: +//! +//! ```sh +//! sudo apt-get install fuse3 libfuse3-dev +//! ``` +//! +//! In CentOS, you can install it with: +//! +//! ```sh +//! sudo yum install fuse-devel +//! ``` +//! +//! - **macOS**: you need to have the `macfuse` service installed on your system. +//! +//! You can install it with: +//! +//! ```sh +//! brew install macfuse +//! ``` +//! +//! - **Windows**: you need to have the `dokany` service installed on your system. +//! +//! You can install it from //! //! ## Get started //! @@ -18,6 +50,37 @@ //! //! - `no-log`: disable logging. By default, this library will log via the `log` crate. //! +//! ## Example +//! +//! ```rust,no_run,ignore +//! use remotefs_fuse::Mount; +//! +//! let options = vec![ +//! #[cfg(unix)] +//! remotefs_fuse::MountOption::AllowRoot, +//! #[cfg(unix)] +//! remotefs_fuse::MountOption::RW, +//! #[cfg(unix)] +//! remotefs_fuse::MountOption::Exec, +//! #[cfg(unix)] +//! remotefs_fuse::MountOption::Sync, +//! #[cfg(unix)] +//! remotefs_fuse::MountOption::FSName(volume), +//! ]; +//! +//! let remote = todo!(); +//! let mount_path = std::path::PathBuf::from("/mnt/remote"); +//! let mut mount = Mount::mount(remote, &mount_path, &options).expect("Failed to mount"); +//! let mut umount = mount.unmounter(); +//! +//! // setup signal handler +//! ctrlc::set_handler(move || { +//! umount.umount().expect("Failed to unmount"); +//! })?; +//! +//! mount.run().expect("Failed to run filesystem event loop"); +//! ``` +//! #![doc(html_playground_url = "https://play.rust-lang.org")] #![doc( diff --git a/remotefs-fuse/src/mount/option.rs b/remotefs-fuse/src/mount/option.rs index da4b146..9eb35fe 100644 --- a/remotefs-fuse/src/mount/option.rs +++ b/remotefs-fuse/src/mount/option.rs @@ -1,189 +1,328 @@ -/// Mount options for mounting a FUSE filesystem -/// -/// Some of them are *nix-specific, and may not be available on other platforms, while other -/// are for Windows only. -#[derive(Debug, Eq, PartialEq, Hash, Clone)] -pub enum MountOption { - /* nix driver */ - /// Treat all files as if they are owned by the given user. - /// This flag can be useful when mounting for instance sftp volumes, - /// where the uid/gid of the files may be different from the user mounting the filesystem. - /// This doesn't change the ownership of the files, but allows the user to access them. - /// Of course, if the signed in user doesn't have the right permissions, the files will still be inaccessible. - Uid(u32), - /// Treat all files as if they are owned by the given user. - /// This flag can be useful when mounting for instance sftp volumes, - /// where the uid/gid of the files may be different from the user mounting the filesystem. - /// This doesn't change the ownership of the files, but allows the user to access them. - /// Of course, if the signed in user doesn't have the right permissions, the files will still be inaccessible. - Gid(u32), - /// Set the default file mode in case the filesystem doesn't provide one - /// If not set, the default is 0755 - DefaultMode(u32), - /* fuser */ - /// Set the name of the source in mtab - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - FSName(String), - /// Set the filesystem subtype in mtab - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - Subtype(String), - /// Allows passing an option which is not otherwise supported in these enums - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - Custom(String), - /// Allow all users to access files on this filesystem. By default access is restricted to the - /// user who mounted it - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - AllowOther, - /// Allow the root user to access this filesystem, in addition to the user who mounted it - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - AllowRoot, - /// Automatically unmount when the mounting process exits - /// - /// `AutoUnmount` requires `AllowOther` or `AllowRoot`. If `AutoUnmount` is set and neither `Allow...` is set, the FUSE configuration must permit `allow_other`, otherwise mounting will fail. - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - AutoUnmount, - /// Enable permission checking in the kernel - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - DefaultPermissions, - - /// Enable special character and block devices - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - Dev, - /// Disable special character and block devices - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - NoDev, - /// Honor set-user-id and set-groupd-id bits on files - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - Suid, - /// Don't honor set-user-id and set-groupd-id bits on files - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - NoSuid, - /// Read-only filesystem - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - RO, - /// Read-write filesystem - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - RW, - /// Allow execution of binaries - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - Exec, - /// Don't allow execution of binaries - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - NoExec, - /// Support inode access time - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - Atime, - /// Don't update inode access time - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - NoAtime, - /// All modifications to directories will be done synchronously - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - DirSync, - /// All I/O will be done synchronously - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - Sync, - /// All I/O will be done asynchronously - #[cfg(unix)] - #[cfg_attr(docsrs, doc(cfg(unix)))] - Async, - - // dokany - /// Only use a single thread to process events. This is highly not recommended as can easily create a bottleneck. - #[cfg(windows)] - #[cfg_attr(docsrs, doc(cfg(windows)))] - SingleThread, - /// Controls behavior of the volume. - #[cfg(windows)] - #[cfg_attr(docsrs, doc(cfg(windows)))] - Flags(u32), - /// Max timeout of each request before Dokan gives up to wait events to complete. - /// Timeout request is a sign that the userland implementation is no longer able to properly manage requests in time. - /// The driver will therefore unmount the device when a timeout trigger in order to keep the system stable. - /// - /// If zero, defaults to 15 seconds. - #[cfg(windows)] - #[cfg_attr(docsrs, doc(cfg(windows)))] - Timeout(std::time::Duration), - /// Allocation Unit Size of the volume. This will affect the file size. - #[cfg(windows)] - #[cfg_attr(docsrs, doc(cfg(windows)))] - AllocationUnitSize(u32), - /// Sector Size of the volume. This will affect the file size. - #[cfg(windows)] - #[cfg_attr(docsrs, doc(cfg(windows)))] - SectorSize(u32), -} - -#[cfg(unix)] -#[cfg_attr(docsrs, doc(cfg(unix)))] -impl TryFrom<&MountOption> for fuser::MountOption { - type Error = &'static str; - - fn try_from(value: &MountOption) -> Result { - Ok(match value { - MountOption::FSName(name) => fuser::MountOption::FSName(name.clone()), - MountOption::Subtype(name) => fuser::MountOption::Subtype(name.clone()), - MountOption::Custom(name) => fuser::MountOption::CUSTOM(name.clone()), - MountOption::AllowOther => fuser::MountOption::AllowOther, - MountOption::AllowRoot => fuser::MountOption::AllowRoot, - MountOption::AutoUnmount => fuser::MountOption::AutoUnmount, - MountOption::DefaultPermissions => fuser::MountOption::DefaultPermissions, - MountOption::Dev => fuser::MountOption::Dev, - MountOption::NoDev => fuser::MountOption::NoDev, - MountOption::Suid => fuser::MountOption::Suid, - MountOption::NoSuid => fuser::MountOption::NoSuid, - MountOption::RO => fuser::MountOption::RO, - MountOption::RW => fuser::MountOption::RW, - MountOption::Exec => fuser::MountOption::Exec, - MountOption::NoExec => fuser::MountOption::NoExec, - MountOption::Atime => fuser::MountOption::Atime, - MountOption::NoAtime => fuser::MountOption::NoAtime, - MountOption::DirSync => fuser::MountOption::DirSync, - MountOption::Sync => fuser::MountOption::Sync, - MountOption::Async => fuser::MountOption::Async, - _ => return Err("Unsupported mount option"), - }) - } -} - -#[cfg(windows)] -#[cfg_attr(docsrs, doc(cfg(windows)))] -impl MountOption { - pub fn into_dokan_options(options: &[MountOption]) -> dokan::MountOptions<'_> { - let mut dokan_options = dokan::MountOptions::default(); - - for option in options { - match option { - MountOption::SingleThread => dokan_options.single_thread = true, - MountOption::Flags(flags) => { - dokan_options.flags = dokan::MountFlags::from_bits_truncate(*flags) - } - MountOption::Timeout(timeout) => dokan_options.timeout = *timeout, - MountOption::AllocationUnitSize(size) => dokan_options.allocation_unit_size = *size, - MountOption::SectorSize(size) => dokan_options.sector_size = *size, - _ => {} - } - } - - dokan_options - } -} +use std::str::FromStr; +use std::time::Duration; + +/// Mount options for mounting a FUSE filesystem +/// +/// Some of them are *nix-specific, and may not be available on other platforms, while other +/// are for Windows only. +/// +/// [`MountOption`] implements [`FromStr`] with the syntax `key[=value]` for all options. +#[derive(Debug, Eq, PartialEq, Hash, Clone)] +pub enum MountOption { + /* nix driver */ + #[cfg(unix)] + /// Treat all files as if they are owned by the given user. + /// This flag can be useful when mounting for instance sftp volumes, + /// where the uid/gid of the files may be different from the user mounting the filesystem. + /// This doesn't change the ownership of the files, but allows the user to access them. + /// Of course, if the signed in user doesn't have the right permissions, the files will still be inaccessible. + Uid(u32), + #[cfg(unix)] + /// Treat all files as if they are owned by the given user. + /// This flag can be useful when mounting for instance sftp volumes, + /// where the uid/gid of the files may be different from the user mounting the filesystem. + /// This doesn't change the ownership of the files, but allows the user to access them. + /// Of course, if the signed in user doesn't have the right permissions, the files will still be inaccessible. + Gid(u32), + #[cfg(unix)] + /// Set the default file mode in case the filesystem doesn't provide one + /// If not set, the default is 0755 + DefaultMode(u32), + /* fuser */ + /// Set the name of the source in mtab + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + FSName(String), + /// Set the filesystem subtype in mtab + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + Subtype(String), + /// Allows passing an option which is not otherwise supported in these enums + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + Custom(String), + /// Allow all users to access files on this filesystem. By default access is restricted to the + /// user who mounted it + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + AllowOther, + /// Allow the root user to access this filesystem, in addition to the user who mounted it + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + AllowRoot, + /// Automatically unmount when the mounting process exits + /// + /// `AutoUnmount` requires `AllowOther` or `AllowRoot`. If `AutoUnmount` is set and neither `Allow...` is set, the FUSE configuration must permit `allow_other`, otherwise mounting will fail. + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + AutoUnmount, + /// Enable permission checking in the kernel + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + DefaultPermissions, + + /// Enable special character and block devices + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + Dev, + /// Disable special character and block devices + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + NoDev, + /// Honor set-user-id and set-groupd-id bits on files + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + Suid, + /// Don't honor set-user-id and set-groupd-id bits on files + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + NoSuid, + /// Read-only filesystem + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + RO, + /// Read-write filesystem + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + RW, + /// Allow execution of binaries + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + Exec, + /// Don't allow execution of binaries + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + NoExec, + /// Support inode access time + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + Atime, + /// Don't update inode access time + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + NoAtime, + /// All modifications to directories will be done synchronously + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + DirSync, + /// All I/O will be done synchronously + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + Sync, + /// All I/O will be done asynchronously + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + Async, + + // dokany + /// Only use a single thread to process events. This is highly not recommended as can easily create a bottleneck. + #[cfg(windows)] + #[cfg_attr(docsrs, doc(cfg(windows)))] + SingleThread, + /// Controls behavior of the volume. + #[cfg(windows)] + #[cfg_attr(docsrs, doc(cfg(windows)))] + Flags(u32), + /// Max timeout of each request before Dokan gives up to wait events to complete. + /// Timeout request is a sign that the userland implementation is no longer able to properly manage requests in time. + /// The driver will therefore unmount the device when a timeout trigger in order to keep the system stable. + /// + /// If zero, defaults to 15 seconds. + #[cfg(windows)] + #[cfg_attr(docsrs, doc(cfg(windows)))] + Timeout(std::time::Duration), + /// Allocation Unit Size of the volume. This will affect the file size. + #[cfg(windows)] + #[cfg_attr(docsrs, doc(cfg(windows)))] + AllocationUnitSize(u32), + /// Sector Size of the volume. This will affect the file size. + #[cfg(windows)] + #[cfg_attr(docsrs, doc(cfg(windows)))] + SectorSize(u32), +} + +#[cfg(unix)] +#[cfg_attr(docsrs, doc(cfg(unix)))] +impl TryFrom<&MountOption> for fuser::MountOption { + type Error = &'static str; + + fn try_from(value: &MountOption) -> Result { + Ok(match value { + MountOption::FSName(name) => fuser::MountOption::FSName(name.clone()), + MountOption::Subtype(name) => fuser::MountOption::Subtype(name.clone()), + MountOption::Custom(name) => fuser::MountOption::CUSTOM(name.clone()), + MountOption::AllowOther => fuser::MountOption::AllowOther, + MountOption::AllowRoot => fuser::MountOption::AllowRoot, + MountOption::AutoUnmount => fuser::MountOption::AutoUnmount, + MountOption::DefaultPermissions => fuser::MountOption::DefaultPermissions, + MountOption::Dev => fuser::MountOption::Dev, + MountOption::NoDev => fuser::MountOption::NoDev, + MountOption::Suid => fuser::MountOption::Suid, + MountOption::NoSuid => fuser::MountOption::NoSuid, + MountOption::RO => fuser::MountOption::RO, + MountOption::RW => fuser::MountOption::RW, + MountOption::Exec => fuser::MountOption::Exec, + MountOption::NoExec => fuser::MountOption::NoExec, + MountOption::Atime => fuser::MountOption::Atime, + MountOption::NoAtime => fuser::MountOption::NoAtime, + MountOption::DirSync => fuser::MountOption::DirSync, + MountOption::Sync => fuser::MountOption::Sync, + MountOption::Async => fuser::MountOption::Async, + _ => return Err("Unsupported mount option"), + }) + } +} + +#[cfg(windows)] +#[cfg_attr(docsrs, doc(cfg(windows)))] +impl MountOption { + pub fn into_dokan_options(options: &[MountOption]) -> dokan::MountOptions<'_> { + let mut dokan_options = dokan::MountOptions::default(); + + for option in options { + match option { + MountOption::SingleThread => dokan_options.single_thread = true, + MountOption::Flags(flags) => { + dokan_options.flags = dokan::MountFlags::from_bits_truncate(*flags) + } + MountOption::Timeout(timeout) => dokan_options.timeout = *timeout, + MountOption::AllocationUnitSize(size) => dokan_options.allocation_unit_size = *size, + MountOption::SectorSize(size) => dokan_options.sector_size = *size, + } + } + + dokan_options + } +} + +impl FromStr for MountOption { + type Err = String; + + fn from_str(s: &str) -> Result { + let (option, value) = match s.find('=') { + Some(index) => ( + (s[..index]).to_ascii_lowercase().to_string(), + Some(&s[index + 1..]), + ), + None => (s.to_ascii_lowercase().to_string(), None), + }; + + match (option.as_str(), value) { + #[cfg(unix)] + ("uid", Some(value)) => { + let value = value + .parse() + .map_err(|e| format!("Invalid uid value: {}", e))?; + Ok(MountOption::Uid(value)) + } + #[cfg(unix)] + ("uid", None) => Err("uid requires a value".to_string()), + #[cfg(unix)] + ("gid", Some(value)) => { + let value = value + .parse() + .map_err(|e| format!("Invalid gid value: {}", e))?; + Ok(MountOption::Gid(value)) + } + #[cfg(unix)] + ("gid", None) => Err("gid requires a value".to_string()), + #[cfg(unix)] + ("default_mode", Some(value)) => { + let value = u32::from_str_radix(value, 8) + .map_err(|e| format!("Invalid default_mode value: {}", e))?; + Ok(MountOption::DefaultMode(value)) + } + #[cfg(unix)] + ("default_mode", None) => Err("default_mode requires a value".to_string()), + #[cfg(unix)] + ("fsname", Some(value)) => Ok(MountOption::FSName(value.to_string())), + #[cfg(unix)] + ("fsname", None) => Err("fsname requires a value".to_string()), + #[cfg(unix)] + ("subtype", Some(value)) => Ok(MountOption::Subtype(value.to_string())), + #[cfg(unix)] + ("subtype", None) => Err("subtype requires a value".to_string()), + #[cfg(unix)] + ("custom", Some(value)) => Ok(MountOption::Custom(value.to_string())), + #[cfg(unix)] + ("custom", None) => Err("custom requires a value".to_string()), + #[cfg(unix)] + ("allow_other", None) => Ok(MountOption::AllowOther), + #[cfg(unix)] + ("allow_root", None) => Ok(MountOption::AllowRoot), + #[cfg(unix)] + ("auto_unmount", None) => Ok(MountOption::AutoUnmount), + #[cfg(unix)] + ("default_permissions", None) => Ok(MountOption::DefaultPermissions), + #[cfg(unix)] + ("dev", None) => Ok(MountOption::Dev), + #[cfg(unix)] + ("nodev", None) => Ok(MountOption::NoDev), + #[cfg(unix)] + ("suid", None) => Ok(MountOption::Suid), + #[cfg(unix)] + ("nosuid", None) => Ok(MountOption::NoSuid), + #[cfg(unix)] + ("ro", None) => Ok(MountOption::RO), + #[cfg(unix)] + ("rw", None) => Ok(MountOption::RW), + #[cfg(unix)] + ("exec", None) => Ok(MountOption::Exec), + #[cfg(unix)] + ("noexec", None) => Ok(MountOption::NoExec), + #[cfg(unix)] + ("atime", None) => Ok(MountOption::Atime), + #[cfg(unix)] + ("noatime", None) => Ok(MountOption::NoAtime), + #[cfg(unix)] + ("dirsync", None) => Ok(MountOption::DirSync), + #[cfg(unix)] + ("sync", None) => Ok(MountOption::Sync), + #[cfg(unix)] + ("async", None) => Ok(MountOption::Async), + #[cfg(windows)] + ("single_thread", None) => Ok(MountOption::SingleThread), + #[cfg(windows)] + ("flags", Some(value)) => { + let value = value + .parse() + .map_err(|e| format!("Invalid flags value: {}", e))?; + Ok(MountOption::Flags(value)) + } + #[cfg(windows)] + ("flags", None) => Err("flags requires a value".to_string()), + #[cfg(windows)] + ("timeout", Some(value)) => { + let value = Duration::from_millis( + value + .parse() + .map_err(|e| format!("Invalid timeout value: {}", e))?, + ); + Ok(MountOption::Timeout(value)) + } + #[cfg(windows)] + ("timeout", None) => Err("timeout requires a value".to_string()), + #[cfg(windows)] + ("allocation_unit_size", Some(value)) => { + let value = value + .parse() + .map_err(|e| format!("Invalid allocation_unit_size value: {}", e))?; + Ok(MountOption::AllocationUnitSize(value)) + } + #[cfg(windows)] + ("allocation_unit_size", None) => { + Err("allocation_unit_size requires a value".to_string()) + } + #[cfg(windows)] + ("sector_size", Some(value)) => { + let value = value + .parse() + .map_err(|e| format!("Invalid sector_size value: {}", e))?; + Ok(MountOption::SectorSize(value)) + } + #[cfg(windows)] + ("sector_size", None) => Err("sector_size requires a value".to_string()), + _ => Err(format!("Unknown mount option: {}", s)), + } + } +}