diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index afc64c6..6f13269 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,25 +1,25 @@ -name: windows - -on: [push, pull_request] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - name: install dokany - run: choco install dokany - - name: Build - run: cargo build --all-features - - name: Format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy -- -Dwarnings - - name: Run tests - run: cargo test --no-fail-fast - env: - RUST_LOG: trace +name: windows + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + - name: install dokany + run: choco install dokany + - name: Build + run: cargo build --all-features + - name: Format + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy -- -Dwarnings + - name: Run tests + run: cargo test --no-fail-fast + env: + RUST_LOG: trace diff --git a/README.md b/README.md index 95bf068..1dc639a 100644 --- a/README.md +++ b/README.md @@ -1,244 +1,244 @@ -# 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 or Dokany. - -```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...] -``` - -On Windows the mountpoint can be specified simply using the drive letter `--to M` will mount the FS to `M:\` - -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 - -> ❗ This doesn't apply to Windows. - -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. - -## Project stability - -Please consider this is an early-stage project and I haven't heavily tested it, in particular the Windows version. - -I suggest you to first test it on test filesystems to see whether the library behaves correctly with your system. - -## 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) +# 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.unmount().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 or Dokany. + +```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...] +``` + +On Windows the mountpoint can be specified simply using the drive letter `--to M` will mount the FS to `M:\` + +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 + +> ❗ This doesn't apply to Windows. + +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. + +## Project stability + +Please consider this is an early-stage project and I haven't heavily tested it, in particular the Windows version. + +I suggest you to first test it on test filesystems to see whether the library behaves correctly with your system. + +## 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/main.rs b/remotefs-fuse-cli/src/main.rs index 1568273..a579df7 100644 --- a/remotefs-fuse-cli/src/main.rs +++ b/remotefs-fuse-cli/src/main.rs @@ -58,7 +58,7 @@ fn main() -> anyhow::Result<()> { // setup signal handler ctrlc::set_handler(move || { log::info!("Received SIGINT, unmounting filesystem"); - umount.umount().expect("Failed to unmount"); + umount.unmount().expect("Failed to unmount"); })?; log::info!("Running filesystem event loop"); diff --git a/remotefs-fuse/src/lib.rs b/remotefs-fuse/src/lib.rs index 11c2265..5b2b3c2 100644 --- a/remotefs-fuse/src/lib.rs +++ b/remotefs-fuse/src/lib.rs @@ -75,7 +75,7 @@ //! //! // setup signal handler //! ctrlc::set_handler(move || { -//! umount.umount().expect("Failed to unmount"); +//! umount.unmount().expect("Failed to unmount"); //! })?; //! //! mount.run().expect("Failed to run filesystem event loop"); @@ -105,4 +105,4 @@ extern crate log; mod driver; mod mount; -pub use self::mount::{Mount, MountOption, Umount}; +pub use self::mount::{Mount, MountOption, Unmount}; diff --git a/remotefs-fuse/src/mount.rs b/remotefs-fuse/src/mount.rs index 48fe7c9..825cb56 100644 --- a/remotefs-fuse/src/mount.rs +++ b/remotefs-fuse/src/mount.rs @@ -26,7 +26,7 @@ where { /// Mount the filesystem implemented by [`Driver`] to the provided mountpoint. /// - /// You can specify the mount options using the `options` parameter. + /// You can specify the mount options using the `options` parameter as an array of [`MountOption`]. #[allow(clippy::self_named_constructors)] #[cfg(unix)] pub fn mount( @@ -49,7 +49,7 @@ where /// Mount the filesystem implemented by [`Driver`] to the provided mountpoint. /// - /// You can specify the mount options using the `options` parameter. + /// You can specify the mount options using the `options` parameter as an array of [`MountOption`]. #[cfg(windows)] #[allow(clippy::self_named_constructors)] pub fn mount( @@ -93,9 +93,9 @@ where /// Get a handle to unmount the filesystem. /// - /// To umount see [`Umount::umount`]. - pub fn unmounter(&mut self) -> Umount { - Umount { + /// To umount see [`Unmount::unmount`]. + pub fn unmounter(&mut self) -> Unmount { + Unmount { #[cfg(unix)] umount: self.session.unmount_callable(), #[cfg(windows)] @@ -105,16 +105,16 @@ where } /// A thread-safe handle to unmount the filesystem. -pub struct Umount { +pub struct Unmount { #[cfg(unix)] umount: fuser::SessionUnmounter, #[cfg(windows)] mountpoint: widestring::U16CString, } -impl Umount { +impl Unmount { /// Unmount the filesystem. - pub fn umount(&mut self) -> Result<(), std::io::Error> { + pub fn unmount(&mut self) -> Result<(), std::io::Error> { #[cfg(unix)] self.umount.unmount()?; diff --git a/remotefs-fuse/tests/driver/mod.rs b/remotefs-fuse/tests/driver/mod.rs index aef86e1..adc1723 100644 --- a/remotefs-fuse/tests/driver/mod.rs +++ b/remotefs-fuse/tests/driver/mod.rs @@ -1,108 +1,108 @@ -use std::path::{Path, PathBuf}; - -use remotefs::fs::{Metadata, UnixPex}; -use remotefs::{RemoteError, RemoteErrorType, RemoteFs}; -use remotefs_memory::{node, Inode, MemoryFs, Node, Tree}; - -pub fn mounted_file_path() -> &'static Path { - Path::new("/tmp/mounted.txt") -} - -#[cfg(unix)] -pub fn setup_driver() -> MemoryFs { - let gid = nix::unistd::getgid().as_raw(); - let uid = nix::unistd::getuid().as_raw(); - - let tree = Tree::new(node!( - PathBuf::from("/"), - Inode::dir(uid, gid, UnixPex::from(0o755)), - )); - - let mut fs = MemoryFs::new(tree) - .with_get_gid(|| nix::unistd::getgid().as_raw()) - .with_get_uid(|| nix::unistd::getuid().as_raw()); - - fs.connect().expect("Failed to connect to fs"); - - make_file_at(&mut fs, mounted_file_path(), b"Hello, world!"); - - fs -} - -#[cfg(windows)] -pub fn setup_driver() -> MemoryFs { - let tree = Tree::new(node!( - PathBuf::from("/"), - Inode::dir(0, 0, UnixPex::from(0o755)), - )); - - let mut fs = MemoryFs::new(tree); - - fs.connect().expect("Failed to connect to fs"); - - make_file_at(&mut fs, mounted_file_path(), b"Hello, world!"); - - fs -} - -/// Make file on the remote fs at `path` with `content` -/// -/// If the stems in the path do not exist, they will be created. -fn make_file_at(remote: &mut MemoryFs, path: &Path, content: &[u8]) { - let parent_dir = path.parent().expect("Path has no parent"); - make_dir_at(remote, parent_dir); - - let reader = std::io::Cursor::new(content.to_vec()); - - remote - .create_file( - path, - &Metadata::default().size(content.len() as u64), - Box::new(reader), - ) - .expect("Failed to create file"); -} - -/// Make directory on the remote fs at `path` -/// -/// All the stems in the path will be created if they do not exist. -#[cfg(windows)] -fn make_dir_at(remote: &mut MemoryFs, path: &Path) { - use path_slash::PathBufExt; - - let mut abs_path = Path::new("/").to_path_buf(); - for stem in path.iter() { - abs_path.push(stem); - // convert to slash - let abs_path = PathBuf::from(abs_path.to_slash_lossy().to_string()); - println!("Creating directory: {abs_path:?}"); - match remote.create_dir(&abs_path, UnixPex::from(0o755)) { - Ok(_) - | Err(RemoteError { - kind: RemoteErrorType::DirectoryAlreadyExists, - .. - }) => {} - Err(err) => panic!("Failed to create directory: {err}"), - } - } -} - -/// Make directory on the remote fs at `path` -/// -/// All the stems in the path will be created if they do not exist. -#[cfg(unix)] -fn make_dir_at(remote: &mut MemoryFs, path: &Path) { - let mut abs_path = Path::new("/").to_path_buf(); - for stem in path.iter() { - abs_path.push(stem); - println!("Creating directory: {abs_path:?}"); - match remote.create_dir(&abs_path, UnixPex::from(0o755)) { - Ok(_) - | Err(RemoteError { - kind: RemoteErrorType::DirectoryAlreadyExists, - .. - }) => {} - Err(err) => panic!("Failed to create directory: {err}"), - } - } -} +use std::path::{Path, PathBuf}; + +use remotefs::fs::{Metadata, UnixPex}; +use remotefs::{RemoteError, RemoteErrorType, RemoteFs}; +use remotefs_memory::{node, Inode, MemoryFs, Node, Tree}; + +pub fn mounted_file_path() -> &'static Path { + Path::new("/tmp/mounted.txt") +} + +#[cfg(unix)] +pub fn setup_driver() -> MemoryFs { + let gid = nix::unistd::getgid().as_raw(); + let uid = nix::unistd::getuid().as_raw(); + + let tree = Tree::new(node!( + PathBuf::from("/"), + Inode::dir(uid, gid, UnixPex::from(0o755)), + )); + + let mut fs = MemoryFs::new(tree) + .with_get_gid(|| nix::unistd::getgid().as_raw()) + .with_get_uid(|| nix::unistd::getuid().as_raw()); + + fs.connect().expect("Failed to connect to fs"); + + make_file_at(&mut fs, mounted_file_path(), b"Hello, world!"); + + fs +} + +#[cfg(windows)] +pub fn setup_driver() -> MemoryFs { + let tree = Tree::new(node!( + PathBuf::from("/"), + Inode::dir(0, 0, UnixPex::from(0o755)), + )); + + let mut fs = MemoryFs::new(tree); + + fs.connect().expect("Failed to connect to fs"); + + make_file_at(&mut fs, mounted_file_path(), b"Hello, world!"); + + fs +} + +/// Make file on the remote fs at `path` with `content` +/// +/// If the stems in the path do not exist, they will be created. +fn make_file_at(remote: &mut MemoryFs, path: &Path, content: &[u8]) { + let parent_dir = path.parent().expect("Path has no parent"); + make_dir_at(remote, parent_dir); + + let reader = std::io::Cursor::new(content.to_vec()); + + remote + .create_file( + path, + &Metadata::default().size(content.len() as u64), + Box::new(reader), + ) + .expect("Failed to create file"); +} + +/// Make directory on the remote fs at `path` +/// +/// All the stems in the path will be created if they do not exist. +#[cfg(windows)] +fn make_dir_at(remote: &mut MemoryFs, path: &Path) { + use path_slash::PathBufExt; + + let mut abs_path = Path::new("/").to_path_buf(); + for stem in path.iter() { + abs_path.push(stem); + // convert to slash + let abs_path = PathBuf::from(abs_path.to_slash_lossy().to_string()); + println!("Creating directory: {abs_path:?}"); + match remote.create_dir(&abs_path, UnixPex::from(0o755)) { + Ok(_) + | Err(RemoteError { + kind: RemoteErrorType::DirectoryAlreadyExists, + .. + }) => {} + Err(err) => panic!("Failed to create directory: {err}"), + } + } +} + +/// Make directory on the remote fs at `path` +/// +/// All the stems in the path will be created if they do not exist. +#[cfg(unix)] +fn make_dir_at(remote: &mut MemoryFs, path: &Path) { + let mut abs_path = Path::new("/").to_path_buf(); + for stem in path.iter() { + abs_path.push(stem); + println!("Creating directory: {abs_path:?}"); + match remote.create_dir(&abs_path, UnixPex::from(0o755)) { + Ok(_) + | Err(RemoteError { + kind: RemoteErrorType::DirectoryAlreadyExists, + .. + }) => {} + Err(err) => panic!("Failed to create directory: {err}"), + } + } +} diff --git a/remotefs-fuse/tests/fuse/mod.rs b/remotefs-fuse/tests/fuse/mod.rs index 18df339..adf9f03 100644 --- a/remotefs-fuse/tests/fuse/mod.rs +++ b/remotefs-fuse/tests/fuse/mod.rs @@ -4,12 +4,12 @@ use std::sync::{Arc, Mutex}; use std::thread::JoinHandle; use std::time::Duration; -use remotefs_fuse::{Mount, MountOption, Umount}; +use remotefs_fuse::{Mount, MountOption, Unmount}; use tempfile::TempDir; use crate::driver::mounted_file_path; -pub type UmountLock = Arc>>; +pub type UmountLock = Arc>>; /// Mounts the filesystem in a separate thread. /// @@ -60,7 +60,7 @@ fn umount(umount: UmountLock) { .unwrap() .as_mut() .unwrap() - .umount() + .unmount() .expect("Failed to unmount"); } diff --git a/remotefs-fuse/tests/integration_tests.rs b/remotefs-fuse/tests/integration_tests.rs index 336eba1..b9a0957 100644 --- a/remotefs-fuse/tests/integration_tests.rs +++ b/remotefs-fuse/tests/integration_tests.rs @@ -1,8 +1,8 @@ -#[cfg(windows)] -#[cfg(feature = "integration-tests")] -mod dokany; -#[cfg(feature = "integration-tests")] -pub mod driver; -#[cfg(unix)] -#[cfg(feature = "integration-tests")] -mod fuse; +#[cfg(windows)] +#[cfg(feature = "integration-tests")] +mod dokany; +#[cfg(feature = "integration-tests")] +pub mod driver; +#[cfg(unix)] +#[cfg(feature = "integration-tests")] +mod fuse;