diff --git a/concepts/filesystem.md b/concepts/filesystem.md new file mode 100644 index 0000000..a7cd86c --- /dev/null +++ b/concepts/filesystem.md @@ -0,0 +1,168 @@ +# thing to consider: +- [ ] How to prevent the file explorer to automatically generate thumbnails for the files + - DT_UNKNOWN in readdir should do the trick + - in linux can a fuse filesystem notify the file explorer that it does not support thumbnails? + - Yes, a FUSE filesystem can notify the file explorer that it does not support thumbnails. This can be achieved by implementing the readdir function in the FUSE filesystem and setting the d_type field of the dirent struct to DT_UNKNOWN for files that do not support thumbnails. By doing this, the file explorer will not attempt to generate a thumbnail for the file. + - does this have any implications other than not generating thumbnails for example how a file is opened? + - No, setting `d_type` to `DT_UNKNOWN` for files in a FUSE filesystem that do not support thumbnails should not have any implications other than not generating thumbnails. The `d_type` field of the `dirent` struct is used by the file explorer to determine the type of the file. If `d_type` is set to `DT_UNKNOWN`, the file explorer will not be able to determine the file type and will not use it to make any decisions about how to open the file. + - According to the `readdir` man page, the `d_type` field is not specified in POSIX.1 and is not present on all systems. It is an unstandardized field that is mainly available on BSD systems and some Linux filesystems like Btrfs, ext2, ext3, and ext4. If a filesystem does not fill `d_type` properly, all applications must properly handle a return of `DT_UNKNOWN`. Therefore, it is safe to set `d_type` to `DT_UNKNOWN` for files that do not support thumbnails in a FUSE filesystem. + - It is also worth noting that FUSE provides two ways to identify the file being operated upon: the `path` argument and the `file handle` in the `fuse_file_info` structure. The `path` argument is always available, but pathname lookup can be expensive. + + +# Things I need to implement with the file provider + + + + +## release + +`release`: This function is called when there are no more references to an open file, +which means all file descriptors are closed and all memory mappings are unmapped. For +every `open` call, there will be exactly one `release` call. The purpose of this function +is to clean up any resources associated with the open file and perform any finalization +tasks before the file is considered closed. Note that error values returned by `release` +are not propagated to the `close()` or `munmap()` system calls that triggered the release. + +```rust +fn release(&mut self, _req: &Request<'_>, ino: u64, fh: u64, flags: u32, lock_owner: u64, flush: bool, reply: ReplyEmpty) +``` + +## fsync + +`fsync`: This function is called to synchronize a file's in-memory state with the storage +device. It ensures that any pending writes are flushed to the storage device and that the +file data is consistent. The `fsync` function takes a parameter `datasync` that indicates +whether only the file data should be flushed (`true`) or both file data and metadata should +be flushed (`false`). + +```rust +fn fsync(&mut self, _req: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) +``` + +## open + +`open`: The `open` function is called when a file is opened in the filesystem. The main +purpose of this function is to check if the operation is permitted for the given flags +and return success (0) if the file can be opened. Optionally, a file handle may be +returned, which will be passed to subsequent read, write, flush, fsync, and release calls. +It is important to note that no creation or truncation flags (O_CREAT, O_EXCL, O_TRUNC) +will be passed to the `open` function. The filesystem implementation should only check if +the operation is allowed based on the provided flags [Source 2](https://metacpan.org/pod/Fuse). + +```rust +fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) +``` + +## flush + +`flush`: The `flush` function is called to synchronize any cached data before the file is +closed. It may be called multiple times before a file is closed. Note that this function +is not equivalent to `fsync()` and it's not a request to sync dirty data. It is called on +each `close()` of a file descriptor, as opposed to `release`, which is called on the close +of the last file descriptor for a file. Under Linux, errors returned by `flush()` will be +passed to userspace as errors from `close()`. However, many applications ignore errors on +`close()`, and on non-Linux systems, `close()` may succeed even if `flush()` returns an +error. For these reasons, filesystems should not assume that errors returned by `flush` +will ever be noticed or even delivered +[Source 4](https://libfuse.github.io/doxygen/structfuse__operations.html). + +```rust +fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) +``` + +## write + +`write`: The `write` function is called when a user process wants to write data to a file +in the filesystem. The main purpose of this function is to write the given data at the +specified offset in the file, and return the number of bytes successfully written. The +function should handle partial writes and update the file size if necessary. The write +operation should respect the file's open mode (e.g., O_WRONLY or O_RDWR) and any file locks. + +```rust +fn write(&mut self, _req: &Request<'_>, ino: u64, fh: u64, offset: i64, data: Vec, flags: i32, reply: ReplyWrite) +``` + +## read + +`read`: The `read` function is called when a user process wants to read data from a file +in the filesystem. The main purpose of this function is to read the specified number of +bytes from the file starting at the given offset and return the data to the caller. The +function should handle partial reads and return an appropriate amount of data if the +requested size exceeds the file's remaining size from the specified offset. The read +operation should respect the file's open mode (e.g., O_RDONLY or O_RDWR) and any file locks. + +```rust +fn read(&mut self, _req: &Request<'_>, ino: u64, fh: u64, offset: i64, size: u32, reply: ReplyData) +``` + +_________________________________________________________ + +# Some others I should probably check up on: + +## getattr + +`getattr`: This function is called to get the attributes of a file or directory, such as +its size, creation time, owner, and permissions. The filesystem implementation should +look up the attributes for the specified inode number and return them in a `FileAttr` structure. + +```rust +fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) +``` + +## readdir + +`readdir`: This function is called when a user process wants to list the contents of a +directory. The filesystem implementation should return the list of entries in the +specified directory, including files, directories, and symbolic links. + +```rust +fn readdir(&mut self, _req: &Request<'_>, ino: u64, fh: u64, offset: i64, mut reply: ReplyDirectory) +``` + +## mkdir + +`mkdir`: This function is called when a user process wants to create a new directory. +The filesystem implementation should create the new directory with the specified name, +mode, and parent directory. + +```rust +fn mkdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, reply: ReplyEntry) +``` + +## rmdir + +`rmdir`: This function is called when a user process wants to remove a directory. The +filesystem implementation should remove the specified directory if it is empty. + +```rust +fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) +``` + +## create + +`create`: This function is called when a user process wants to create a new file. The +filesystem implementation should create the new file with the specified name, mode, and parent directory, and return a +file handle. + +```rust +fn create(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, flags: i32, reply: ReplyCreate) +``` + +## unlink + +`unlink`: This function is called when a user process wants to remove a file. The filesystem +implementation should remove the specified file. + +```rust +fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) +``` + +## rename + +`rename`: This function is called when a user process wants to rename a file or directory. +The filesystem implementation should move the specified file or directory from its current +location to the new location, updating the parent directory as needed. + +```rust +fn rename(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, newparent: u64, newname: &OsStr, reply: ReplyEmpty) +``` diff --git a/concepts/todos.md b/concepts/todos.md new file mode 100644 index 0000000..fb6b170 --- /dev/null +++ b/concepts/todos.md @@ -0,0 +1,70 @@ +# TODOs + +### Priorities: +- critical: this is critical and needs to be fixed asap as it creates extreme problems like crashes or data loss regularly +- 1: this is needed before it can be released +- 2: this is important but not critical/not necessary for a release +- 3: nice to have + +### Prio critical: + +- + +### Prio 1: +- start parameters +- settings +- implement the changes api to check for changes when accessing files +- file operations: + - create + - delete + - move/rename + +### Prio 2: +- implement notifying the user of needed actions like conflicts or required authentications via the [notify-rust](https://docs.rs/notify-rust/latest/notify_rust/#example-3-ask-the-user-to-do-something) crate + - maybe also let the user decide if he wants notifications like this or just wants to stay in the CLI (start param?) + + +### Prio 3: + + + + + + + + + + + + +## Done TODOs: + + +### Prio critical: +- fix freezing + - freeze happens after a few requests. I have no Idea why but run_async_blocking never + returns sometimes for some reason. + - it always happens after adding about 7-8 times a character to the end of a string causing a lookup + - example going from ``cat /tmp/fuse/3/sample_folder/hello_a`` to + ``cat /tmp/fuse/3/sample_folder/hello_aaaaaaaaa`` + - the lookups don't have to be successful (im not sure if they need to fail to freeze the system) + - when looking at the tokio-console it shows 4 tasks and 4 resources after start, after + each character added it jumps up about 8 resources to 12 but then goes back down to 4 + after a few seconds for the first 7 to 8 attempts. When it hangs up it only goes down to 5, + sometimes 6 resources, not 4. + - => DONE + - This issue was solved with the tokio [blocking_recv](https://docs.rs/tokio/latest/tokio/sync/mpsc/struct.Receiver.html#method.blocking_recv) + and [blocking_send](https://docs.rs/tokio/latest/tokio/sync/mpsc/struct.Sender.html#method.blocking_send) + + +### Prio 1: + +- + +### Prio 2: + +- + +### Prio 3: + +- \ No newline at end of file diff --git a/src/fs/drive2/filesystem.rs b/src/fs/drive2/filesystem.rs new file mode 100644 index 0000000..e674be7 --- /dev/null +++ b/src/fs/drive2/filesystem.rs @@ -0,0 +1,480 @@ +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fmt::{Display, Formatter}; +use std::time::{Duration, SystemTime}; + +use anyhow::{anyhow, Context}; +use bimap::BiMap; +use fuser::{ + FileAttr, Filesystem, KernelConfig, ReplyAttr, ReplyData, ReplyDirectory, ReplyEmpty, + ReplyEntry, ReplyOpen, ReplyWrite, Request, TimeOrNow, +}; +use libc::c_int; +use std::sync::mpsc::{channel, Receiver, Sender}; +use tokio::fs::File; +use tracing::field::debug; +use tracing::{debug, error, instrument, trace}; + +pub use handle_flags::HandleFlags; + +use crate::fs::drive_file_provider::{ + ProviderLookupRequest, ProviderMetadataRequest, ProviderOpenFileRequest, + ProviderReadContentRequest, ProviderReadDirRequest, ProviderReleaseFileRequest, + ProviderRequest, ProviderResponse, ProviderSetAttrRequest, ProviderWriteContentRequest, +}; +use crate::google_drive::DriveId; +use crate::{ + match_provider_response, prelude::*, receive_response, reply_error_e, reply_error_e_consuming, + reply_error_o, send_request, +}; + +//TODO2: decide if 1 second is a good TTL for all cases +const TTL: Duration = Duration::from_secs(2); + +mod handle_flags; + +#[derive(Debug)] +struct FileHandleData { + flags: HandleFlags, +} + +#[derive(Debug)] +struct Entry { + attr: FileAttr, +} + +#[derive(Debug)] +pub struct DriveFilesystem { + file_provider_sender: tokio::sync::mpsc::Sender, + + entry_ids: BiMap, + ino_to_file_handles: HashMap>, + next_ino: u64, +} +//region DriveFilesystem ino_to_file_handle +impl DriveFilesystem { + fn get_fh_from_ino(&self, ino: u64) -> Option<&Vec> { + self.ino_to_file_handles.get(&ino) + } + fn get_ino_from_fh(&self, fh: u64) -> Option { + for (ino, fhs) in self.ino_to_file_handles.iter() { + if fhs.contains(&fh) { + return Some(*ino); + } + } + None + } + fn remove_fh(&mut self, fh: u64) -> Result<()> { + let ino = self + .get_ino_from_fh(fh) + .context("could not find ino for fh")?; + + let x = self + .ino_to_file_handles + .get_mut(&ino) + .context("could not find fh for ino")?; + x.retain(|&x| x != fh); + // let data = self + // .file_handles + // .remove(&fh) + // .context("could not find handle data for fh")?; + // Ok(data) + Ok(()) + } + fn add_fh(&mut self, ino: u64, fh: u64, handle: FileHandleData) -> Result<()> { + let fhs = self.ino_to_file_handles.get_mut(&ino); //.or_insert_with(||vec![fh]); + if let Some(fhs) = fhs { + if !fhs.contains(&fh) { + fhs.push(fh); + } else { + error!("fh {} already exists for ino {}", fh, ino); + return Err(anyhow!("fh {} already exists for ino {}", fh, ino)); + } + } else { + self.ino_to_file_handles.insert(ino, vec![fh]); + } + debug!("added fh {} to ino {}", fh, ino); + Ok(()) + } +} +//endregion +//region DriveFilesystem ino_to_id +impl DriveFilesystem { + fn get_id_from_ino(&self, ino: u64) -> Option<&DriveId> { + self.entry_ids.get_by_left(&ino) + } + fn get_ino_from_id(&mut self, id: DriveId) -> u64 { + let x = self.entry_ids.get_by_right(&id); + if let Some(ino) = x { + return *ino; + } + self.add_id(id) + } + fn remove_id(&mut self, id: DriveId) -> Result { + if let Some((ino, _)) = self.entry_ids.remove_by_right(&id) { + Ok(ino) + } else { + Err(anyhow!("could not find id {}", id)) + } + } + fn add_id(&mut self, id: DriveId) -> u64 { + let ino = self.generate_ino(); + trace!("adding new ino for drive id: {} => {}", id, ino); + self.entry_ids.insert(ino, id); + ino + } +} +//endregion +impl Display for DriveFilesystem { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DriveFilesystem(entry ids: {})", self.entry_ids.len()) + } +} + +impl DriveFilesystem { + pub fn new(file_provider_sender: tokio::sync::mpsc::Sender) -> Self { + Self { + file_provider_sender, + entry_ids: BiMap::new(), + ino_to_file_handles: HashMap::new(), + next_ino: 222, + } + } + fn generate_ino(&mut self) -> u64 { + let ino = self.next_ino; + self.next_ino += 1; + ino + } +} + +impl Filesystem for DriveFilesystem { + //region init + fn init( + &mut self, + _req: &Request<'_>, + _config: &mut KernelConfig, + ) -> std::result::Result<(), c_int> { + self.entry_ids.insert(1, DriveId::from("root")); + Ok(()) + } + //endregion + //region lookup + fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { + let (provider_res_tx, mut provider_rx) = tokio::sync::mpsc::channel(1); + + let parent_id = self.entry_ids.get_by_left(&parent); + reply_error_o!( + parent_id, + reply, + libc::ENOENT, + "Failed to find drive_id for parent ino: {}", + parent + ); + + let v = ProviderRequest::Lookup(ProviderLookupRequest::new( + parent_id, + name.to_os_string(), + provider_res_tx, + )); + send_request!(self.file_provider_sender, v, reply); + + receive_response!(provider_rx, response, reply); + match_provider_response!(response, reply, ProviderResponse::Lookup(metadata), { + if let Some(metadata) = metadata { + let mut attr = metadata.attr; + attr.ino = self.get_ino_from_id(metadata.id); + reply.entry(&TTL, &attr, 0); //TODO3: generation + } else { + reply.error(libc::ENOENT); + } + }); + debug!("done with lookup!"); + } + //endregion + //region getattr + #[instrument(skip(_req), fields(% self))] + fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) { + let (provider_res_tx, mut provider_rx) = tokio::sync::mpsc::channel(1); + let drive_id = self.entry_ids.get_by_left(&ino); + reply_error_o!( + drive_id, + reply, + libc::ENOENT, + "Failed to find drive_id for ino: {}", + ino + ); + debug!("getting attributes"); + + let v = ProviderRequest::Metadata(ProviderMetadataRequest::new(drive_id, provider_res_tx)); + send_request!(self.file_provider_sender, v, reply); + receive_response!(provider_rx, response, reply); + match_provider_response!(response, reply, ProviderResponse::Metadata(metadata), { + trace!("Received ProviderResponse::Metadata({:?})", metadata); + let mut attr = metadata.attr; + attr.ino = ino; + trace!("responding with attr: {:?}", attr); + reply.attr(&TTL, &attr); + }); + } + //endregion + //region setattr + #[instrument(skip(_req), fields(% self))] + fn setattr( + &mut self, + _req: &Request<'_>, + ino: u64, + mode: Option, + uid: Option, + gid: Option, + size: Option, + _atime: Option, + _mtime: Option, + _ctime: Option, + fh: Option, + _crtime: Option, + _chgtime: Option, + _bkuptime: Option, + flags: Option, + reply: ReplyAttr, + ) { + let (provider_res_tx, mut provider_rx) = tokio::sync::mpsc::channel(1); + let drive_id = self.entry_ids.get_by_left(&ino); + reply_error_o!( + drive_id, + reply, + libc::ENOENT, + "Failed to find drive_id for ino: {}", + ino + ); + debug!("getting attributes"); + let v = ProviderRequest::SetAttr(ProviderSetAttrRequest::new( + drive_id, + mode, + uid, + gid, + size, + flags, + fh, + provider_res_tx, + )); + send_request!(self.file_provider_sender, v, reply); + receive_response!(provider_rx, response, reply); + match_provider_response!(response, reply, ProviderResponse::SetAttr(metadata), { + trace!("Received ProviderResponse::SetAttr({:?})", metadata); + let mut attr = metadata.attr; + attr.ino = ino; + trace!("responding with attr: {:?}", attr); + reply.attr(&TTL, &attr); + }); + } + //endregion + //region open + #[instrument(skip(_req), fields(%self))] + fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) { + let (provider_res_tx, mut provider_rx) = tokio::sync::mpsc::channel(1); + // let fh_id = self.generate_fh(); + // // let flags = HandleFlags::from(flags); + // let handle_data = FileHandleData { flags }; + // self.add_fh(ino, fh_id, handle_data); + + let drive_id = self.entry_ids.get_by_left(&ino); + reply_error_o!( + drive_id, + reply, + libc::ENOENT, + "Failed to find drive_id for ino: {}", + ino + ); + let v = ProviderRequest::OpenFile(ProviderOpenFileRequest::new( + drive_id, + flags, + provider_res_tx, + )); + send_request!(self.file_provider_sender, v, reply); + receive_response!(provider_rx, response, reply); + match_provider_response!(response, reply, ProviderResponse::OpenFile(fh, flags), { + trace!("got OpenFile result: fh: {}, flags: {:?}", fh, flags); + let x = self.ino_to_file_handles.get_mut(&ino); + if let Some(x) = x { + x.push(fh); + } else { + self.ino_to_file_handles.insert(ino, vec![fh]); + } + reply.opened(fh, flags.into()); + }); + } + //endregion + //region read + #[instrument(skip(_req), fields(% self))] + fn read( + &mut self, + _req: &Request<'_>, + ino: u64, + fh: u64, + offset: i64, + size: u32, + flags: i32, + lock_owner: Option, + reply: ReplyData, + ) { + let (provider_res_tx, mut provider_rx) = tokio::sync::mpsc::channel(1); + let drive_id = self.entry_ids.get_by_left(&ino); + reply_error_o!( + drive_id, + reply, + libc::ENOENT, + "Failed to find drive_id for ino: {}", + ino + ); + + let v = ProviderRequest::ReadContent(ProviderReadContentRequest::new( + drive_id, + offset as u64, + size as usize, + fh, + provider_res_tx, + )); + send_request!(self.file_provider_sender, v, reply); + receive_response!(provider_rx, response, reply); + match_provider_response!(response, reply, ProviderResponse::ReadContent(content), { + reply.data(content.as_slice()); + trace!("Received ProviderResponse::Ok"); + }); + } + //endregion + //region write + #[instrument(skip(_req), fields(% self, data = data.len()))] + fn write( + &mut self, + _req: &Request<'_>, + ino: u64, + fh: u64, + offset: i64, + data: &[u8], + write_flags: u32, + flags: i32, + lock_owner: Option, + reply: ReplyWrite, + ) { + let (provider_res_tx, mut provider_rx) = tokio::sync::mpsc::channel(1); + let drive_id = self.entry_ids.get_by_left(&ino); + reply_error_o!( + drive_id, + reply, + libc::ENOENT, + "Failed to find drive_id for ino: {}", + ino + ); + let v = ProviderRequest::WriteContent(ProviderWriteContentRequest::new( + drive_id, + offset as u64, + fh, + data.to_vec(), + provider_res_tx, + )); + send_request!(self.file_provider_sender, v, reply); + receive_response!(provider_rx, response, reply); + match_provider_response!(response, reply, ProviderResponse::WriteSize(content), { + reply.written(content); + trace!("Received ProviderResponse::WriteSize({})", content); + }); + } + //endregion + //region release + #[instrument(skip(_req), fields(%self))] + fn release( + &mut self, + _req: &Request<'_>, + ino: u64, + fh: u64, + _flags: i32, + _lock_owner: Option, + _flush: bool, + reply: ReplyEmpty, + ) { + let (provider_res_tx, mut provider_rx) = tokio::sync::mpsc::channel(1); + let drive_id = self.get_id_from_ino(ino); + reply_error_o!( + drive_id, + reply, + libc::ENOENT, + "Failed to find drive_id for ino: {}", + ino + ); + + let v = ProviderRequest::ReleaseFile(ProviderReleaseFileRequest::new( + drive_id.clone(), + fh, + provider_res_tx, + )); + send_request!(self.file_provider_sender, v, reply); + receive_response!(provider_rx, response, reply); + match_provider_response!(response, reply, ProviderResponse::ReleaseFile, { + let handle_data = self.remove_fh(fh); + reply_error_e_consuming!( + handle_data, + reply, + libc::ENOENT, + "Failed to find file_handle for fh: {}", + fh + ); + reply.ok(); + debug!("Released file_handle for fh: {}", fh); + }); + } + //endregion + //region readdir + #[instrument(skip(_req, reply), fields(% self))] + fn readdir( + &mut self, + _req: &Request<'_>, + ino: u64, + fh: u64, + offset: i64, + mut reply: ReplyDirectory, + ) { + let (provider_res_tx, mut provider_rx) = tokio::sync::mpsc::channel(1); + let drive_id = self.entry_ids.get_by_left(&ino); + reply_error_o!( + drive_id, + reply, + libc::ENOENT, + "Failed to find drive_id for ino: {}", + ino + ); + + let v = ProviderRequest::ReadDir(ProviderReadDirRequest::new( + drive_id, + offset as u64, + provider_res_tx, + )); + send_request!(self.file_provider_sender, v, reply); + receive_response!(provider_rx, response, reply); + + match_provider_response!(response, reply, ProviderResponse::ReadDir(response), { + let mut counter = 0; + debug!( + "received ProviderReadDirResponse with {} entries", + response.entries.len() + ); + for entry in response.entries { + let entry_ino = self.get_ino_from_id(entry.id.clone()); + counter += 1; + debug!( + "adding entry to output: ino:{}, counter:{}, entry: {:?}", + entry_ino, counter, entry + ); + let buffer_full = reply.add(entry_ino, counter, entry.attr.kind, &entry.name); + if buffer_full { + debug!("buffer full after {}", counter); + break; + } + } + debug!("sending ok"); + reply.ok(); + return; + }); + } + + //endregion +} diff --git a/src/fs/drive2/filesystem/handle_flags.rs b/src/fs/drive2/filesystem/handle_flags.rs new file mode 100644 index 0000000..3fe8d33 --- /dev/null +++ b/src/fs/drive2/filesystem/handle_flags.rs @@ -0,0 +1,154 @@ +use tracing::debug; + +#[derive(Debug, Copy, Clone, Default)] +pub struct HandleFlags { + // File status flags used for open() and fcntl() are as follows: + /// append mode. + o_append: bool, + /// [SIO](https://pubs.opengroup.org/onlinepubs/009695399/help/codes.html#SIO) Write according to synchronized I/O data integrity completion. + o_dsync: bool, + /// Non-blocking mode. + o_nonblock: bool, + /// [SIO](https://pubs.opengroup.org/onlinepubs/009695399/help/codes.html#SIO) Synchronized read I/O operations. + o_rsync: bool, + /// Write according to synchronized I/O file integrity completion. + o_sync: bool, + + // Mask for use with file access modes is as follows: + /// Mask for file access modes. + // O_ACCMODE + + // File access modes used for open() and fcntl() are as follows: + + /// Open for reading only. + o_rdonly: bool, + /// Open for reading and writing. + o_rdwr: bool, + /// Open for writing only. + o_wronly: bool, +} + +impl HandleFlags { + pub(crate) fn can_write(&self) -> bool { + self.o_wronly || self.o_rdwr + } + + pub(crate) fn can_read(&self) -> bool { + self.o_rdonly || self.o_rdwr + } +} + +impl From for HandleFlags { + fn from(value: i32) -> Self { + debug!("Creating HandleFlags from an i32: {:x}", value); + let s = Self { + o_append: value & libc::O_APPEND != 0, + o_dsync: value & libc::O_DSYNC != 0, + o_nonblock: value & libc::O_NONBLOCK != 0, + o_rsync: value & libc::O_RSYNC != 0, + o_sync: value & libc::O_SYNC != 0, + o_rdonly: value & libc::O_ACCMODE == libc::O_RDONLY, + o_rdwr: value & libc::O_ACCMODE == libc::O_RDWR, + o_wronly: value & libc::O_ACCMODE == libc::O_WRONLY, + }; + #[cfg(test)] + { + let o_accmode = value & libc::O_ACCMODE; + let o_rdonly = o_accmode == libc::O_RDONLY; + let o_rdwr = o_accmode == libc::O_RDWR; + let o_wronly = o_accmode == libc::O_WRONLY; + debug!( + "accmode {:x} rdonly {} rdwr {} wronly {}", + o_accmode, o_rdonly, o_rdwr, o_wronly + ); + } + debug!("created HandleFlags: {:?}", s); + s + } +} + +impl Into for HandleFlags { + fn into(self) -> i32 { + let mut flags = 0; + if self.o_append { + flags |= libc::O_APPEND; + } + if self.o_dsync { + flags |= libc::O_DSYNC; + } + if self.o_nonblock { + flags |= libc::O_NONBLOCK; + } + if self.o_rsync { + flags |= libc::O_RSYNC; + } + if self.o_sync { + flags |= libc::O_SYNC; + } + if self.o_rdonly { + flags |= libc::O_RDONLY; + } + if self.o_rdwr { + flags |= libc::O_RDWR; + } + if self.o_wronly { + flags |= libc::O_WRONLY; + } + flags + } +} + +impl Into for HandleFlags { + fn into(self) -> u32 { + let i_num: i32 = self.into(); + i_num as u32 + } +} +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn handle_flags_ro() { + crate::tests::init_logs(); + let flags = 0; + let handle_flags = HandleFlags::from(flags); + debug!("flags: {:x} => {:?}", flags, handle_flags); + assert!(handle_flags.can_read()); + assert!(!handle_flags.can_write()); + let flags = 32768; + let handle_flags = HandleFlags::from(flags); + debug!("flags: {:x} => {:?}", flags, handle_flags); + assert!(handle_flags.can_read()); + assert!(!handle_flags.can_write()); + } + #[test] + fn handle_flags_wo() { + crate::tests::init_logs(); + let flags = 1; + let handle_flags = HandleFlags::from(flags); + debug!("flags: {:x} => {:?}", flags, handle_flags); + assert!(handle_flags.can_write()); + assert!(!handle_flags.can_read()); + } + #[test] + fn handle_flags_rw() { + crate::tests::init_logs(); + let flags = 2; + let handle_flags = HandleFlags::from(flags); + debug!("flags: {:x} => {:?}", flags, handle_flags); + debug!("test432"); + assert!(handle_flags.can_write()); + assert!(handle_flags.can_read()); + } + #[test] + fn handle_flags_into_rw() { + crate::tests::init_logs(); + debug!("test123"); + let mut x = HandleFlags::default(); + x.o_rdwr = true; + assert!(x.can_write()); + assert!(x.can_read()); + let flags: i32 = x.into(); + assert_eq!(2, flags); + } +} diff --git a/src/fs/drive2/mod.rs b/src/fs/drive2/mod.rs new file mode 100644 index 0000000..20a39ef --- /dev/null +++ b/src/fs/drive2/mod.rs @@ -0,0 +1,2 @@ +mod filesystem; +pub use filesystem::*; diff --git a/src/fs/drive_file_provider/entry.rs b/src/fs/drive_file_provider/entry.rs new file mode 100644 index 0000000..34f40aa --- /dev/null +++ b/src/fs/drive_file_provider/entry.rs @@ -0,0 +1,17 @@ +use crate::google_drive::DriveId; +use crate::prelude::*; +use fuser::FileAttr; +use std::path::PathBuf; + +#[derive(Debug, Clone)] +pub struct DriveEntry { + pub id: DriveId, + + pub name: String, + pub local_path: Option, + pub attr: FileAttr, + pub drive_metadata: Option, + pub has_upstream_content_changes: bool, + pub md5_checksum: Option, + pub local_md5_checksum: Option, +} diff --git a/src/fs/drive_file_provider/mod.rs b/src/fs/drive_file_provider/mod.rs new file mode 100644 index 0000000..0e0231a --- /dev/null +++ b/src/fs/drive_file_provider/mod.rs @@ -0,0 +1,5 @@ +mod provider; +pub use provider::*; +pub use request::*; +mod entry; +mod request; diff --git a/src/fs/drive_file_provider/provider.rs b/src/fs/drive_file_provider/provider.rs new file mode 100644 index 0000000..e6b3b1c --- /dev/null +++ b/src/fs/drive_file_provider/provider.rs @@ -0,0 +1,973 @@ +use std::collections::HashMap; +use std::ffi::{OsStr, OsString}; +use std::fmt::{Debug, Formatter}; +use std::fs::Permissions; +use std::io::SeekFrom; +use std::os::unix::prelude::{MetadataExt, PermissionsExt}; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; + +use anyhow::{anyhow, Context, Error}; +use fuser::{FileAttr, FileType}; +use libc::c_int; +use tokio::fs::{File, OpenOptions}; +use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::sync::Mutex; +use tokio::task::JoinHandle; +use tokio::time::sleep; +use tokio::{fs, join}; +use tracing::{debug, error, info, instrument, trace, warn}; + +use crate::fs::drive2::HandleFlags; +use crate::fs::drive_file_provider::{ + FileMetadata, ProviderLookupRequest, ProviderMetadataRequest, ProviderOpenFileRequest, + ProviderReadContentRequest, ProviderReadDirRequest, ProviderReadDirResponse, + ProviderReleaseFileRequest, ProviderRequest, ProviderRequestStruct, ProviderResponse, + ProviderSetAttrRequest, ProviderWriteContentRequest, +}; +use crate::google_drive::{DriveId, GoogleDrive}; +use crate::prelude::*; +use crate::{send_error_response, send_response}; + +#[derive(Debug)] +pub enum ProviderCommand { + Stop, + PauseSync, +} +#[derive(Debug)] +pub struct FileRequest { + pub file_id: DriveId, + pub response_sender: Sender, +} + +#[derive(Debug, Clone)] +pub struct FileData { + // pub local_path: PathBuf, + pub metadata: DriveFileMetadata, + pub changed_metadata: DriveFileMetadata, + /// marks if a file should be kept up to date locally, even without internet connection + /// the file is accessible. + /// + /// This can lead to conflicts of being locally edited and remote! + pub perma: bool, + pub attr: FileAttr, + pub is_local: bool, +} + +#[derive(Debug)] +pub struct FileHandleData { + flags: HandleFlags, + file: Option, + path: PathBuf, + creating: bool, + marked_for_open: bool, + has_content_changed: bool, +} + +pub struct DriveFileProvider { + drive: GoogleDrive, + cache_dir: PathBuf, + perma_dir: PathBuf, + + // file_request_receiver: std::sync::mpsc::Receiver, + running_requests: HashMap>>, + alt_root_id: DriveId, + entries: HashMap, + parents: HashMap>, + children: HashMap>, + + file_handles: HashMap, + next_fh: u64, +} +impl Debug for DriveFileProvider { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DriveFileProvider") + // .field("running_requests", &self.running_requests.len()) + .field("entries", &self.entries.len()) + .field("children", &self.children.len()) + .field("parents", &self.parents.len()) + .field("file_handles", &self.file_handles.len()) + .field("next_fh", &self.next_fh) + // .field("cache_dir", &self.cache_dir) + // .field("perma_dir", &self.perma_dir) + .finish() + } +} +impl DriveFileProvider { + pub fn new( + drive: GoogleDrive, + cache_dir: PathBuf, + perma_dir: PathBuf, + // file_request_receiver: std::sync::mpsc::Receiver, + ) -> Self { + Self { + drive, + cache_dir, + perma_dir, + // file_request_receiver, + running_requests: HashMap::new(), + alt_root_id: DriveId::root(), + entries: HashMap::new(), + parents: HashMap::new(), + children: HashMap::new(), + file_handles: HashMap::new(), + next_fh: 111, + } + } + fn add_parent_child_relation(&mut self, parent_id: DriveId, child_id: DriveId) { + trace!( + "adding child-parent relation for child: {:<50} and parent: {:<50}", + child_id, + parent_id + ); + if let Some(parents) = self.parents.get_mut(&child_id) { + parents.push(parent_id.clone()); + } else { + self.parents + .insert(child_id.clone(), vec![parent_id.clone()]); + } + if let Some(children) = self.children.get_mut(&parent_id) { + children.push(child_id); + } else { + self.children.insert(parent_id, vec![child_id]); + } + } + #[instrument(skip(self, request_reciever, command_receiver))] + pub async fn listen( + &mut self, + request_reciever: Receiver, + command_receiver: Receiver, + ) { + debug!("listen"); + tokio::select! { + _ = Self::listen_for_stop(command_receiver) => { + trace!("DriveFileProvider::listen_for_stop() finished"); + self.cleanup().await; + }, + _ = self.listen_for_file_requests(request_reciever) => {trace!("DriveFileProvider::listen_for_file_requests() finished");}, + } + } + pub async fn listen_for_stop(mut command_receiver: Receiver) { + let signal = command_receiver.recv().await; + if let Some(signal) = signal { + match signal { + ProviderCommand::Stop => { + debug!("provider received stop command"); + } + _ => { + error!("unknown signal"); + todo!() + } + } + } + // sleep(std::time::Duration::from_secs( + // 10 * 60 * 60 * 24, /*10 days*/ + // )) + // .await; + debug!("listen for stop finished"); + // //TODO: implement waiting for the stop signal instead of just waiting for 10 days + } + pub async fn cleanup(&mut self) { + debug!("cleanup got called"); + todo!("cleanup") + } + #[instrument(skip(self, rx))] + pub async fn listen_for_file_requests( + &mut self, + rx: tokio::sync::mpsc::Receiver, + ) { + debug!("initializing entries"); + let init_res = self.initialize_entries().await; + if let Err(e) = init_res { + error!("got an error at initialize_entries: {}", e); + todo!("maybe implement error handling for this (or just leave it, idc)") + } + debug!("listening for file requests"); + let mut rx = rx; + while let Some(file_request) = rx.recv().await { + debug!("got file request: {:?}", file_request); + let result = match file_request { + ProviderRequest::OpenFile(r) => self.open_file(r).await, + ProviderRequest::ReleaseFile(r) => self.release_file(r).await, + ProviderRequest::Metadata(r) => self.metadata(r).await, + ProviderRequest::ReadContent(r) => self.read_content(r).await, + ProviderRequest::WriteContent(r) => self.write_content(r).await, + ProviderRequest::ReadDir(r) => self.read_dir(r).await, + ProviderRequest::Lookup(r) => self.lookup(r).await, + ProviderRequest::SetAttr(r) => self.set_attr(r).await, + _ => { + error!("DriveFileProvider::listen_for_file_requests() received unknown request: {:?}", file_request); + todo!("handle this unknown request") + } + }; + if let Err(e) = result { + error!("file request handler returned an error: {}", e); + } + debug!("processed file request, waiting for more..."); + } + debug!("Received None from file request receiver, that means all senders have been dropped. Ending listener"); + } + + //region request handlers + //region lookup + #[instrument(skip(request))] + async fn lookup(&self, request: ProviderLookupRequest) -> Result<()> { + let name = request.name.into_string(); + if name.is_err() { + return send_error_response!(request, anyhow!("invalid name"), libc::EINVAL); + } + let name = name.unwrap(); + let parent_id = self.get_correct_id(request.parent); + debug!("looking up {} under id {}", name, parent_id); + let children = self.children.get(&parent_id); + + // let mut result = vec![]; + for child in children.unwrap_or(&vec![]) { + if let Some(child) = self.entries.get(child) { + if child + .metadata + .name + .as_ref() + .unwrap_or(&"NO_NAME".to_string()) + .eq_ignore_ascii_case(&name) + { + // let response = result.push(Self::create_file_metadata_from_entry(child)); + let result = Self::create_file_metadata_from_entry(child); + let response = ProviderResponse::Lookup(Some(result)); + return send_response!(request, response); + } + } + } + info!("could not find file: {} in {}", name, parent_id); + let response = ProviderResponse::Lookup(None); + return send_response!(request, response); + } + //endregion + //region read dir + #[instrument(skip(request))] + async fn read_dir(&mut self, request: ProviderReadDirRequest) -> Result<()> { + let parent_id = self.get_correct_id(request.file_id.clone()); + debug!( + "got read dir request for id: {} with offset: {}", + parent_id, request.offset + ); + if let Some(children) = self.children.get(&parent_id) { + let response = children + .iter() + .map(|id| (id, self.entries.get(id))) + .filter(|(_id, e)| e.is_some()) + .map(|(id, e)| (id, e.unwrap())) + .map(|(id, e)| FileMetadata { + id: id.clone(), + name: e + .metadata + .name + .as_ref() + .unwrap_or(&"NO_NAME".to_string()) + .clone(), + attr: e.attr.clone(), + }) + .skip(request.offset as usize) + .collect::>(); + debug!("returning {} entries", response.len()); + let response = ProviderReadDirResponse { entries: response }; + return send_response!(request, ProviderResponse::ReadDir(response)); + } + debug!("found no entries to return"); + for e in self.entries.iter() { + debug!("entry: {}: {:?}", e.0, e.1); + debug!("children: {:?}", self.children.get(e.0)); + debug!("parents: {:?}", self.parents.get(e.0)); + } + return send_response!( + request, + ProviderResponse::ReadDir(ProviderReadDirResponse { entries: vec![] }) + ); + } + //endregion + //region open file + #[instrument(skip(request))] + async fn open_file(&mut self, request: ProviderOpenFileRequest) -> Result<()> { + let file_id = &self.get_correct_id(request.file_id.clone()); + let wait_res = self + .wait_for_running_drive_request_if_exists(&file_id) + .await; + if let Err(e) = wait_res { + return send_error_response!(request, e, libc::EIO); + } + let target_path = self.construct_path(&file_id); + if let Err(e) = target_path { + return send_error_response!(request, e, libc::EIO); + } + let target_path = target_path.unwrap(); + if !self + .entries + .get(file_id) + .map(|e| e.is_local) + .unwrap_or(false) + { + debug!("file not local, downloading..."); + let drive = self.drive.clone(); + self.start_download_call(&request, drive, &target_path) + .await?; + } + let handle_flags = HandleFlags::from(request.flags); + let fh = self.create_fh(handle_flags, target_path, false, true); + send_response!(request, ProviderResponse::OpenFile(fh, handle_flags)) + } + //endregion + //region release file + #[instrument(skip(request))] + async fn release_file(&mut self, request: ProviderReleaseFileRequest) -> Result<()> { + let file_id = &self.get_correct_id(request.file_id.clone()); + let wait_res = self + .wait_for_running_drive_request_if_exists(&file_id) + .await; + if let Err(e) = wait_res { + return send_error_response!(request, e, libc::EIO); + } + let entry = self.entries.get(file_id).context("could not get entry"); + if let Err(e) = entry { + return send_error_response!(request, e, libc::EIO); + } + let entry = entry.unwrap(); + let file_handle = self + .file_handles + .remove(&request.fh) + .context("could not get entry"); + if let Err(e) = file_handle { + return send_error_response!(request, e, libc::EIO); + } + let file_handle = file_handle.unwrap(); + if file_handle.has_content_changed { + debug!("uploading changes to google drive for file: {}", file_id); + let drive = self.drive.clone(); + let start_result = self.start_upload_call(file_id.clone(), drive).await; + if let Err(e) = start_result { + error!("got error from starting the upload: {:?}", e); + return send_error_response!(request, e, libc::EIO); + } + } + return send_response!(request, ProviderResponse::ReleaseFile); + } + //endregion + //region metadata + #[instrument(skip(request))] + async fn metadata(&self, request: ProviderMetadataRequest) -> Result<()> { + let file_id = &self.get_correct_id(request.file_id.clone()); + debug!("metadata got called"); + let entry = self.entries.get(file_id); + if entry.is_none() { + return send_error_response!( + request, + anyhow!("could not find entry with id"), + libc::ENOENT + ); + } + let entry = entry.unwrap(); + let response = ProviderResponse::Metadata(Self::create_file_metadata_from_entry(entry)); + + send_response!(request, response) + } + + //endregion + //region set_attr + async fn set_attr(&mut self, request: ProviderSetAttrRequest) -> Result<()> { + let file_id = &self.get_correct_id(request.file_id.clone()); + let wait_res = self + .wait_for_running_drive_request_if_exists(&file_id) + .await; + if let Err(e) = wait_res { + return send_error_response!(request, e, libc::EIO); + } + debug!("set_attr got called"); + let entry = self.entries.get(file_id); + if entry.is_none() { + return send_error_response!( + request, + anyhow!("could not find entry with id"), + libc::ENOENT + ); + } + let entry = entry.unwrap(); + let mut attr = entry.attr.clone(); + + if let Some(size) = request.size { + attr.size = size; + let x = self + .set_underlying_file_size(&file_id, request.fh, size) + .await; + if let Err(e) = x { + error!( + "got an error while setting the underlying file size: {:?}", + e + ); + return send_error_response!(request, e, libc::EIO); + } + } + if let Some(flags) = request.flags { + attr.flags = flags; + } + if let Some(mode) = request.mode { + //TODO2: check if setting attr.perm to mode in setattr is correct (probably) + // and if i can just cast it to u16 (from u32) (i have no Idea) + attr.perm = mode as u16; + // TODO3: check if the file below even needs me to set the permissions + // on the underlying file or if this is not needed at all since + // permissions don't get transferred to gdrive and locally i + // have the info in the entries + + // if let Some(fh) = request.fh { + // let handle = self.file_handles.get_mut(&fh); + // if let Some(handle) = handle { + // if let Some(file) = &mut handle.file { + // let perms = Permissions::from_mode(mode); + // let x = file.set_permissions(perms).await; + // if x.is_err() { + // warn!("got an error result while setting len of file: {:?}", x); + // } + // } + // } + // } + } + // if let Some(fh) = request.fh { + // //TODO2: implement something for fh in setattr + // warn!( + // "fh was set in setattr but I don't know what to do with this: {:?}", + // request + // ); + // } + if let Some(gid) = request.gid { + //TODO2: implement something for gid in setattr + warn!( + "gid was set in setattr but I don't know what to do with this: {:?}", + request + ); + } + if let Some(uid) = request.uid { + //TODO2: implement something for uid in setattr + warn!( + "uid was set in setattr but I don't know what to do with this: {:?}", + request + ); + } + + let entry = self + .entries + .get_mut(file_id) + .expect("got it in here before"); + entry.attr = attr; + + let response = ProviderResponse::SetAttr(Self::create_file_metadata_from_entry(entry)); + + send_response!(request, response) + } + + async fn set_underlying_file_size( + &mut self, + file_id: &&DriveId, + fh: Option, + size: u64, + ) -> Result<()> { + let mut was_applied = false; + if let Some(fh) = fh { + let handle = self.file_handles.get_mut(&fh); + if let Some(handle) = handle { + if let Some(file) = &mut handle.file { + let x = file + .set_len(size) + .await + .context("could not set the len of the file"); + if x.is_err() { + warn!("got an error result while setting len of file: {:?}", x); + } else { + was_applied = true; + } + } + } + } + if !was_applied { + let target_path = self.construct_path(&file_id)?; + fs::OpenOptions::new() + .write(true) + .open(target_path) + .await + .context("could not open file to set the size")? + .set_len(size) + .await + .context("could not set the size of the file")?; + } + Ok(()) + } + //endregion + //region read content + #[instrument(skip(request))] + async fn read_content(&mut self, request: ProviderReadContentRequest) -> Result<()> { + let file_id = &self.get_correct_id(request.file_id.clone()); + let wait_res = self + .wait_for_running_drive_request_if_exists(&file_id) + .await; + if let Err(e) = wait_res { + return send_error_response!(request, e, libc::EIO); + } + + let data = self.read_content_from_file(&request).await; + if let Err(e) = data { + return send_error_response!(request, e, libc::EIO); + } + let data = data.unwrap(); + send_response!(request, ProviderResponse::ReadContent(data)) + } + //endregion + //region write content + #[instrument(skip(request))] + async fn write_content(&mut self, request: ProviderWriteContentRequest) -> Result<()> { + let file_id = &self.get_correct_id(request.file_id.clone()); + let wait_res = self.wait_for_running_drive_request_if_exists(file_id).await; + if let Err(e) = wait_res { + return send_error_response!(request, e, libc::EIO); + } + + let size_written = self + .write_content_from_file(file_id.clone(), &request) + .await; + if let Err(e) = size_written { + return send_error_response!(request, e, libc::EIO); + } + let size_written = size_written.unwrap(); + return send_response!(request, ProviderResponse::WriteSize(size_written)); + } + //endregion + + //endregion + //region request helpers + + /// gets the file-handle and opens the file if it is marked for open. + /// + /// If it is not marked for open but the file is None this returns an error + #[instrument] + async fn get_and_open_file_handle(&mut self, fh: u64) -> Result<&mut FileHandleData> { + let file_handle = self.file_handles.get_mut(&fh); + if file_handle.is_none() { + error!("Failed to find file_handle for fh: {}", fh); + return Err(anyhow!("Failed to find file_handle for fh: {}", fh)); + } + let file_handle = file_handle.unwrap(); + if file_handle.file.is_none() { + debug!("file is none, opening..."); + let flags = file_handle.flags; + let opened_file = OpenOptions::new() + .write(flags.can_write()) + .read(flags.can_read()) + .open(&file_handle.path) + .await; + if let Err(e) = &opened_file { + let e = anyhow!("error opening the file{}", e); + error!("{}", e); + return Err(e); + } + let opened_file = opened_file.unwrap(); + file_handle.file = Some(opened_file); + file_handle.marked_for_open = false; + } else { + error!("File handle does not have a file"); + return Err(anyhow!("File handle does not have a file")); + } + Ok(file_handle) + } + + async fn write_content_from_file( + &mut self, + file_id: DriveId, + request: &ProviderWriteContentRequest, + ) -> Result { + let file_handle = self.get_and_open_file_handle(request.fh).await?; + let file = file_handle.file.as_mut().unwrap(); + if !file_handle.flags.can_write() { + error!("File handle does not have read permissions"); + return Err(anyhow!("File handle does not have read permissions")); + } + debug!( + "writing to file at local path: {}", + file_handle.path.display() + ); + let file: &mut tokio::fs::File = file; + trace!("seeking position: {}", request.offset); + file.seek(SeekFrom::Start(request.offset)).await?; + trace!("writing data: {:?}", request.data); + let m = file.metadata().await.unwrap(); + debug!( + "metadata before write: size: {}; modified: {:?}", + m.size(), + m.modified() + ); + let size_written = file.write(&request.data).await?; + file.sync_all().await?; + let m = file.metadata().await.unwrap(); + debug!( + "metadata after write: size: {}; modified: {:?}", + m.size(), + m.modified() + ); + trace!("wrote data: size: {}", size_written); + file_handle.has_content_changed = true; + let entry = self.entries.get_mut(&file_id); + if entry.is_none() { + error!("could not find entry"); + return Err(anyhow!("could not find entry to update metadata on")); + } + let entry = entry.unwrap(); + let now = SystemTime::now(); + entry.attr.size += size_written as u64; + entry.attr.atime = now; + entry.attr.mtime = now; + + Ok(size_written as u32) + } + + async fn read_content_from_file( + &mut self, + request: &ProviderReadContentRequest, + ) -> Result> { + let file_handle = self.get_and_open_file_handle(request.fh).await?; + let file = file_handle.file.as_mut().expect("we just opened this..."); + if !file_handle.flags.can_read() { + error!("File handle does not have read permissions"); + return Err(anyhow!("File handle does not have read permissions")); + } + trace!("seeking position in file: {}", request.offset); + file.seek(SeekFrom::Start(request.offset)).await?; + let mut buf = vec![0; request.size as usize]; + trace!("reading to buffer: size: {}", request.size); + let size_read = file.read(&mut buf).await?; + if size_read != request.size { + warn!( + "did not read the targeted size: target size: {}, actual size: {}", + request.size, size_read + ); + } + Ok(buf) + } + fn create_file_metadata_from_entry(entry: &FileData) -> FileMetadata { + FileMetadata { + attr: entry.attr.clone(), + name: entry + .changed_metadata + .name + .as_ref() + .unwrap_or( + entry + .metadata + .name + .as_ref() + .unwrap_or(&"NO_NAME".to_string()), + ) + .clone(), + id: DriveId::from(entry.metadata.id.as_ref().unwrap()), + } + } + //endregion + // region send response + // + // async fn send_response( + // request: &dyn ProviderRequestStruct, + // response_data: ProviderResponse, + // ) -> Result<()> { + // let result_send_response = request.get_response_sender().send(response_data).; + // + // if let Err(e) = result_send_response { + // error!("Failed to send result response: {:?}", e); + // return Err(anyhow!("Failed to send result response: {:?}", e)); + // } + // Ok(()) + // } + // + // macro_rules! reply_error_e { + // ($result_in:ident, $reply:ident, $error_code:expr, $error_msg:expr) => { + // reply_error_e!($result_in, $reply, $error_code, $error_msg,); + // }; + // async fn send_error_response( + // request: &dyn ProviderRequestStruct, + // e: Error, + // code: c_int, + // ) -> Result<()> { + // let error_send_response = request + // .get_response_sender() + // .send(ProviderResponse::Error(e, code)); + // if let Err(e) = error_send_response { + // error!("Failed to send error response: {:?}", e); + // return Err(anyhow!("Failed to send error response: {:?}", e)); + // } + // Ok(()) + // } + //endregion + //region drive helpers + + /// starts a download of the specified file and puts it in the running_requests map + /// + /// - will return an Error if another request is already running for the same id, so all callers should make sure of that + async fn start_download_call( + &mut self, + request: &ProviderOpenFileRequest, + drive: GoogleDrive, + target_path: &PathBuf, + ) -> Result<()> { + let file_id = self.get_correct_id(request.file_id.clone()); + let id = file_id.clone(); + let entry = self.entries.get_mut(&id).context("could not find entry")?; + entry.is_local = true; + + if let Some(_handle) = self.running_requests.get(&id) { + return send_error_response!( + request, + anyhow!("Id already has a request running"), + libc::EIO, + ); + } + let target_path = target_path.clone(); + let handle: JoinHandle> = tokio::spawn(async move { + let _metadata: DriveFileMetadata = drive.download_file(file_id, &target_path).await?; + Ok(()) + }); + + self.running_requests.insert(id, handle); + Ok(()) + } + + /// - will return an Error if another request is already running for the same id, so all callers should make sure of that + async fn start_upload_call(&mut self, id: DriveId, drive: GoogleDrive) -> Result<()> { + if self.running_requests.contains_key(&id) { + return Err(anyhow!("Id already has a request running")); + } + + let file_data = self + .entries + .get(&id) + .context("could not find data for id")?; + let mut metadata = file_data.changed_metadata.clone(); + + let target_path = self.construct_path(&id)?; + debug!( + "starting upload in the background for path: '{}' and metadata: {:?}", + target_path.display(), + metadata + ); + metadata.id = Some(id.clone().into()); + metadata.mime_type = file_data.metadata.mime_type.clone(); + let metadata = remove_volatile_metadata(metadata); + let handle: JoinHandle> = tokio::spawn(async move { + //TODO1: only send the changed metadata over (+id), not all of it (currently only all data that could change and where changes should be written to the drive), since google drive only wants the changes + drive + .upload_file_content_from_path(metadata, &target_path) + .await?; + Ok(()) + }); + self.running_requests.insert(id, handle); + Ok(()) + } + + /// Checks if a drive request for this ID is running and if there is, waits for it. + /// + /// After awaiting, it removes the request from the map + async fn wait_for_running_drive_request_if_exists(&mut self, file_id: &DriveId) -> Result<()> { + if let Some(handle) = self.running_requests.get_mut(&file_id) { + debug!("DriveFileProvider::open_file() waiting for download/upload to finish"); + let handle_result = handle.await?; + if let Err(e) = handle_result { + error!("async request had an error: {:?}", e); + } + self.running_requests.remove(&file_id); + } + Ok(()) + } + //endregion + + fn create_fh( + &mut self, + flags: HandleFlags, + path: PathBuf, + create: bool, + mark_for_open: bool, + ) -> u64 { + let fh = self.next_fh; + self.next_fh += 1; + let file_handle = FileHandleData { + creating: create, + flags, + file: None, + path, + marked_for_open: mark_for_open, + has_content_changed: false, + }; + self.file_handles.insert(fh, file_handle); + fh + } + /// constructs the path where the file is stored locally. This is not necessarily a + /// path with the correct file ending or folder structure, it could just be a unique id. + fn construct_path(&self, id: &DriveId) -> Result { + let metadata = self.entries.get(id).context("No data found for id")?; + //TODO: check if every drive_id is actually a valid filepath/does + // not contain characters that cannot be used in a path + if metadata.perma { + Ok(self.perma_dir.join(id.as_str())) + } else { + Ok(self.cache_dir.join(id.as_str())) + } + } + async fn initialize_entries(&mut self) -> Result<()> { + self.add_root_entry() + .await + .expect("adding the root entry has to work, otherwise nothing else works"); + let entries = self.drive.list_all_files().await?; + for entry in entries { + let id = &entry.id; + if let Some(id) = id { + let id = DriveId::from(id); + let attr = self.create_file_attr_from_metadata(&entry); + if attr.is_err() { + error!( + "error while creating FileAttr from metadata: {:?} entry: {:?}", + attr, entry + ); + continue; + } + let attr = attr.unwrap(); + if let Some(parents) = &entry.parents { + for parent in parents { + let parent_id = DriveId::from(parent); + self.add_parent_child_relation(parent_id, id.clone()); + } + } else { + //file is at root level + self.add_parent_child_relation( + self.get_correct_id(DriveId::root()), + id.clone(), + ); + } + let entry_data = FileData { + metadata: entry, + changed_metadata: Default::default(), + perma: false, //TODO: read the perma marker from somewhere (maybe only after all files have been checked?) + attr, + is_local: false, + }; + self.entries.insert(id, entry_data); + } + } + for (i, (id, data)) in self.entries.iter().enumerate() { + println!("entry {:3} id: {:>40} data: {:?}", i, id, data); + } + Ok(()) + } + fn create_file_attr_from_metadata(&self, metadata: &DriveFileMetadata) -> Result { + let kind = convert_mime_type_to_file_type( + metadata.mime_type.as_ref().unwrap_or(&"NONE".to_string()), + )?; + // let permissions= todo!("read default permissions from a file or read specific permissions for id from somewhere (if the permissions were set in a previous sessions and stuff like that should be carried over to the next session"); + let permissions = match kind { + FileType::Directory => 0o755, + _ => 0o644, + }; + let attributes = FileAttr { + ino: 0, + size: (*metadata.size.as_ref().unwrap_or(&0)) as u64, + blocks: 0, + atime: metadata + .viewed_by_me_time + .map(SystemTime::from) + .unwrap_or(UNIX_EPOCH), + mtime: metadata + .modified_time + .map(SystemTime::from) + .unwrap_or(UNIX_EPOCH), + ctime: SystemTime::now(), + crtime: metadata + .created_time + .map(SystemTime::from) + .unwrap_or(UNIX_EPOCH), + kind, + perm: permissions, + nlink: 1, + uid: 0, + gid: 0, + rdev: 0, + blksize: 4096, + flags: 0, + }; + Ok(attributes) + } + async fn add_root_entry(&mut self) -> Result<()> { + let metadata = self + .drive + .get_metadata_for_file(self.get_correct_id(DriveId::root())) + .await?; + let attr = self.create_file_attr_from_metadata(&metadata)?; + let returned_id = metadata.id.as_ref().unwrap().clone(); + let data = FileData { + metadata, + changed_metadata: Default::default(), + attr, + perma: false, + is_local: false, + }; + + let root_id = DriveId::from(returned_id); + self.alt_root_id = root_id.clone(); + self.entries.insert(root_id, data); + Ok(()) + } + fn get_correct_id(&self, id: DriveId) -> DriveId { + if id == DriveId::root() { + return self.alt_root_id.clone(); + } + return id; + } +} + +fn remove_volatile_metadata(metadata: DriveFileMetadata) -> DriveFileMetadata { + let mut metadata = metadata; + metadata.size = None; + metadata.created_time = None; + metadata.trashed_time = None; + metadata.trashed = None; + metadata.modified_by_me_time = None; + metadata.modified_time = None; + metadata.shared_with_me_time = None; + metadata.viewed_by_me_time = None; + metadata.explicitly_trashed = None; + metadata.md5_checksum = None; + metadata.parents = None; + // parents have to be set differently: "The parents field is not directly writable in update requests. Use the addParents and removeParents parameters instead." + metadata.kind = None; + + metadata +} + +fn convert_mime_type_to_file_type(mime_type: &str) -> Result { + Ok(match mime_type { + "application/vnd.google-apps.folder" => FileType::Directory, + "application/vnd.google-apps.document" + | "application/vnd.google-apps.spreadsheet" + | "application/vnd.google-apps.drawing" + | "application/vnd.google-apps.form" + | "application/vnd.google-apps.presentation" + | "application/vnd.google-apps.drive-sdk" + | "application/vnd.google-apps.script" + | "application/vnd.google-apps.*" + //TODO: add all relevant mime types to ignore or match only the start or something + => return Err(anyhow!("google app files are not supported (docs, sheets, etc)")), + _ => FileType::RegularFile, + }) +} + +// TODOs: +// TODO: actually upload the changes to google drive at release (start it in there, don't wait for it to finish) +// TODO: implement the changes api again (maybe with periodic updates?) +// TODO: decide what to do with a fsync +// TODO: create a way to write to a file and read +// - read and write at least kind of work ('echo "hi" >> file' does work, opening editors like vim, nano or gui editors like kate dont, they hang up at write, open or just don't write something correct) +// probably truncate flags or something +// - when running 'echo "1231234" > file' first a setattr gets called, setting the size to 0, and then stuff gets written +// TODO: conform to the flags passed with open like 'read-write' or 'readonly' diff --git a/src/fs/drive_file_provider/request.rs b/src/fs/drive_file_provider/request.rs new file mode 100644 index 0000000..5e90a57 --- /dev/null +++ b/src/fs/drive_file_provider/request.rs @@ -0,0 +1,301 @@ +use std::ffi::OsString; +use std::fmt::Debug; +use std::path::PathBuf; + +use anyhow::Error; +use fuser::{FileAttr, Filesystem}; +use libc::c_int; +use tokio::sync::mpsc::Sender; + +use crate::fs::drive2::HandleFlags; +use crate::fs::drive_file_provider::FileHandleData; +use crate::google_drive::DriveId; +use crate::prelude::*; + +#[derive(Debug)] +pub enum ProviderResponse { + OpenFile(u64, HandleFlags), + ReleaseFile, + SetAttr(FileMetadata), + Metadata(FileMetadata), + Lookup(Option), + ReadContent(Vec), + ReadDir(ProviderReadDirResponse), + WriteSize(u32), + // Ok, + Error(Error, c_int), + Unknown, +} + +#[derive(Debug)] +pub enum ProviderRequest { + OpenFile(ProviderOpenFileRequest), + Lookup(ProviderLookupRequest), + ReleaseFile(ProviderReleaseFileRequest), + Metadata(ProviderMetadataRequest), + SetAttr(ProviderSetAttrRequest), + ReadContent(ProviderReadContentRequest), + ReadDir(ProviderReadDirRequest), + WriteContent(ProviderWriteContentRequest), + Unknown, +} +pub trait ProviderRequestStruct { + fn get_file_id(&self) -> &DriveId; + fn get_response_sender(&self) -> &Sender; +} +//region ProviderRequest structs +#[derive(Debug)] +pub struct ProviderMetadataRequest { + pub file_id: DriveId, + pub response_sender: Sender, +} + +impl ProviderMetadataRequest { + pub(crate) fn new(id: impl Into, response_sender: Sender) -> Self { + Self { + file_id: id.into(), + response_sender, + } + } +} + +#[derive(Debug)] +pub struct ProviderSetAttrRequest { + pub file_id: DriveId, + + pub mode: Option, + pub uid: Option, + pub gid: Option, + pub size: Option, + + pub flags: Option, + pub fh: Option, + pub response_sender: Sender, +} + +impl ProviderSetAttrRequest { + pub(crate) fn new( + id: impl Into, + mode: Option, + uid: Option, + gid: Option, + size: Option, + flags: Option, + fh: Option, + response_sender: Sender, + ) -> Self { + Self { + file_id: id.into(), + mode, + uid, + gid, + size, + flags, + fh, + response_sender, + } + } +} + +impl ProviderRequestStruct for ProviderMetadataRequest { + fn get_file_id(&self) -> &DriveId { + &self.file_id + } + + fn get_response_sender(&self) -> &Sender { + &self.response_sender + } +} + +#[derive(Debug)] +pub struct ProviderOpenFileRequest { + pub file_id: DriveId, + pub flags: i32, + pub response_sender: Sender, +} + +#[derive(Debug)] +pub struct ProviderLookupRequest { + pub name: OsString, + pub parent: DriveId, + pub response_sender: Sender, +} + +impl ProviderOpenFileRequest { + pub(crate) fn new( + id: impl Into, + flags: i32, + response_sender: Sender, + ) -> Self { + Self { + file_id: id.into(), + flags, + response_sender, + } + } +} +impl ProviderLookupRequest { + pub(crate) fn new( + parent_id: impl Into, + name: OsString, + response_sender: Sender, + ) -> Self { + Self { + parent: parent_id.into(), + name, + response_sender, + } + } +} +// +// impl ProviderRequestStruct for ProviderOpenFileRequest { +// fn get_file_id(&self) -> &DriveId { +// &self.file_id +// } +// +// fn get_response_sender(&self) -> &tokio::sync::mpsc::Sender { +// &self.response_sender +// } +// } +#[derive(Debug)] +pub struct ProviderReleaseFileRequest { + pub file_id: DriveId, + pub fh: u64, + // pub flags: u32, + // pub lock_owner: u64, + // pub flush: bool, + pub response_sender: Sender, +} + +impl ProviderReleaseFileRequest { + pub fn new(id: DriveId, fh: u64, response_sender: Sender) -> Self { + Self { + file_id: id, + fh, + response_sender, + } + } +} + +#[derive(Debug)] +pub struct ProviderReadContentRequest { + pub file_id: DriveId, + pub offset: u64, + pub size: usize, + pub fh: u64, + pub response_sender: Sender, +} +impl ProviderRequestStruct for ProviderReadContentRequest { + fn get_file_id(&self) -> &DriveId { + &self.file_id + } + + fn get_response_sender(&self) -> &Sender { + &self.response_sender + } +} + +impl ProviderReadContentRequest { + pub(crate) fn new( + id: impl Into, + offset: u64, + size: usize, + fh: u64, + response_sender: Sender, + ) -> Self { + Self { + file_id: id.into(), + offset, + size, + fh, + response_sender, + } + } +} + +#[derive(Debug)] +pub struct ProviderReadDirRequest { + pub file_id: DriveId, + pub offset: u64, + pub response_sender: Sender, +} + +impl ProviderRequestStruct for ProviderReadDirRequest { + fn get_file_id(&self) -> &DriveId { + &self.file_id + } + + fn get_response_sender(&self) -> &Sender { + &self.response_sender + } +} +impl ProviderReadDirRequest { + pub(crate) fn new( + id: impl Into, + offset: u64, + response_sender: Sender, + ) -> Self { + Self { + file_id: id.into(), + offset, + response_sender, + } + } +} + +#[derive(Debug)] +pub struct ProviderWriteContentRequest { + pub file_id: DriveId, + pub offset: u64, + pub fh: u64, + pub data: Vec, + pub response_sender: Sender, +} + +impl ProviderRequestStruct for ProviderWriteContentRequest { + fn get_file_id(&self) -> &DriveId { + &self.file_id + } + + fn get_response_sender(&self) -> &Sender { + &self.response_sender + } +} +impl ProviderWriteContentRequest { + pub(crate) fn new( + id: impl Into, + offset: u64, + fh: u64, + data: Vec, + response_sender: Sender, + ) -> Self { + Self { + file_id: id.into(), + offset, + fh, + data, + response_sender, + } + } +} + +// endregion +//region ProviderResponse structs + +pub struct ProviderReadDirResponse { + pub entries: Vec, +} +impl Debug for ProviderReadDirResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProviderReadDirResponse").finish() + } +} +//endregion +#[derive(Debug, Clone)] +pub struct FileMetadata { + pub id: DriveId, + pub name: String, + // pub local_path: Option, + pub attr: FileAttr, + // md5_checksum: Option, +} diff --git a/src/fs/mod.rs b/src/fs/mod.rs index fad62f0..d837e0f 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -3,3 +3,6 @@ pub use inode::*; mod inode; pub mod drive; +pub mod drive2; + +pub mod drive_file_provider; diff --git a/src/lib.rs b/src/lib.rs index a26dba4..aa248ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,8 @@ use prelude::*; use crate::config::common_file_filter::CommonFileFilter; use crate::fs::drive::{DriveFileUploader, DriveFilesystem, FileUploaderCommand, SyncSettings}; +use crate::fs::drive_file_provider::{ProviderCommand, ProviderRequest}; +use crate::fs::{drive2, drive_file_provider}; use crate::google_drive::GoogleDrive; pub mod async_helper; @@ -26,8 +28,55 @@ pub mod common; pub mod config; pub mod fs; pub mod google_drive; +mod macros; pub mod prelude; +pub async fn sample_drive2_fs() -> Result<()> { + // let mountpoint = "/tmp/fuse/3"; + let mountpoint = Path::new("/tmp/fuse/3"); + let perma_dir = "/tmp/fuse/2"; + use crate::fs::drive2; + use crate::fs::drive_file_provider; + use std::sync::mpsc::channel; + + let cache_dir = get_cache_dir()?; + + let drive = GoogleDrive::new().await?; + let test = drive.list_all_files().await; + debug!("test!"); + for entry in test.unwrap() { + debug!("entry: {:?}", entry); + } + debug!("test!"); + let (provider_tx, provider_rx) = tokio::sync::mpsc::channel(1); + let filesystem = drive2::DriveFilesystem::new(provider_tx); + let mount_options = vec![MountOption::RW]; + let mut mount = fuser::Session::new(filesystem, &mountpoint, &mount_options)?; + let (command_tx, command_rx) = tokio::sync::mpsc::channel(1); + + let provider_join_handle: JoinHandle<()> = tokio::spawn(drive2_provider( + drive, + cache_dir.path().to_path_buf(), + PathBuf::from(perma_dir), + provider_rx, + command_rx, + )); + let mut session_unmounter = mount.unmount_callable(); + debug!("running mount and listener"); + tokio::select!( + _= async move {mount.run()} => { + debug!("mount.run finished first!"); + let _ = command_tx.send(ProviderCommand::Stop); + let _ = session_unmounter.unmount(); + }, + _=provider_join_handle => { + debug!("provider finished first!"); + let _ = session_unmounter.unmount(); + } + ); + + Ok(()) +} pub async fn sample_drive_fs() -> Result<()> { let mountpoint = "/tmp/fuse/3"; let upload_ignore_path = Path::new("config/.upload_ignore"); @@ -115,3 +164,36 @@ async fn end_program_signal_awaiter( info!("unmounted"); Ok(()) } +async fn drive2_provider( + drive: GoogleDrive, + cache_dir: PathBuf, + perma_dir: PathBuf, + provider_rx: tokio::sync::mpsc::Receiver, + command_rx: tokio::sync::mpsc::Receiver, +) { + use std::sync::mpsc::channel; + let mut provider = drive_file_provider::DriveFileProvider::new(drive, cache_dir, perma_dir); + provider.listen(provider_rx, command_rx).await; +} +#[cfg(test)] +pub mod tests { + pub fn init_logs() { + use tracing::Level; + use tracing_subscriber::fmt; + use tracing_subscriber::EnvFilter; + // Create a new subscriber with the default configuration + let subscriber = fmt::Subscriber::builder() + .with_test_writer() + // .with_thread_ids(true) + .with_env_filter(EnvFilter::from_default_env()) + .with_max_level(Level::DEBUG) + .with_line_number(true) + .with_target(true) + .with_file(true) + // .with_span_events(fmt::format::FmtSpan::NONE) + .finish(); + + // Install the subscriber as the default for this thread + let _ = tracing::subscriber::set_global_default(subscriber); //.expect("setting default subscriber failed"); + } +} diff --git a/src/macros/filesystem_side.rs b/src/macros/filesystem_side.rs new file mode 100644 index 0000000..6c47da1 --- /dev/null +++ b/src/macros/filesystem_side.rs @@ -0,0 +1,58 @@ +#[macro_export] +macro_rules! match_provider_response { + ($response: ident, $reply: ident, $target: pat, $target_body: block) => { + match $response { + $target => $target_body, + ProviderResponse::Error(e, code) => { + error!("received ProviderResponse::Error: {}", e); + $reply.error(code); + return; + } + _ => { + error!("Received unexpected ProviderResponse: {:?}", $response); + $reply.error(libc::EIO); + return; + } + }; + }; +} + +#[macro_export] +macro_rules! receive_response { + ($rx: ident, $response: ident, $reply: ident) => { + tracing::info!("receiving response"); + // let $response = run_async_blocking($rx.recv()); + + let sync_code = std::thread::spawn(move || $rx.blocking_recv()); + let $response = sync_code.join().unwrap(); + tracing::info!("received response"); + // $rx.close(); + // tracing::info!("closed receiver"); + + reply_error_o!( + $response, + $reply, + libc::EIO, + "Failed to receive ProviderResponse", + ); + }; +} + +#[macro_export] +macro_rules! send_request { + ($tx: expr, $data:ident, $reply: ident) => { + tracing::info!("sending request"); + { + let sender = $tx.clone(); + let send_res = std::thread::spawn(move || sender.blocking_send($data)); + let send_res = send_res.join().unwrap(); + reply_error_e_consuming!( + send_res, + $reply, + libc::EIO, + "Failed to send ProviderRequest", + ); + } + tracing::info!("sent request"); + }; +} diff --git a/src/macros/mod.rs b/src/macros/mod.rs new file mode 100644 index 0000000..95abb84 --- /dev/null +++ b/src/macros/mod.rs @@ -0,0 +1,6 @@ +#[macro_use] +mod reply_error; +#[macro_use] +mod provider_side; +#[macro_use] +mod filesystem_side; diff --git a/src/macros/provider_side.rs b/src/macros/provider_side.rs new file mode 100644 index 0000000..eb15c3e --- /dev/null +++ b/src/macros/provider_side.rs @@ -0,0 +1,34 @@ +#[macro_export] +macro_rules! send_error_response { + ($request:ident, $e:expr, $code:expr,) => { + send_error_response!($request, $e, $code) + }; + ($request:ident, $e:expr, $code:expr) => {{ + let error_send_response = $request + .response_sender + .send(ProviderResponse::Error($e, $code)) + .await; + if let Err(e) = error_send_response { + error!("Failed to send error response: {:?}", e); + return Err(anyhow!("Failed to send error response: {:?}", e)); + } + Ok(()) + }}; +} +#[macro_export] +macro_rules! send_response { + ($request:ident, $response:expr,) => { + send_response!($request, $response) + }; + + ($request:ident, $response:expr) => {{ + tracing::info!("sending response"); + let result_send_response = $request.response_sender.send($response).await; + if let Err(e) = result_send_response { + error!("Failed to send result response: {:?}", e); + return Err(anyhow!("Failed to send result response: {:?}", e)); + } + tracing::info!("sent response"); + Ok(()) + }}; +} diff --git a/src/macros/reply_error.rs b/src/macros/reply_error.rs new file mode 100644 index 0000000..6b9dc35 --- /dev/null +++ b/src/macros/reply_error.rs @@ -0,0 +1,70 @@ +// this macro will generate the following code if called with the following arguments: +// call: +// let response: Option = ...; +// reply_error_o!(response, response_out, libc::EIO, "Failed to receive ProviderResponse"); +// result: +// if response.is_none() { +// error!("Failed to receive ProviderResponse"); +// reply.error(libc::EIO); +// return; +// } +// let response_out = response.unwrap(); + +#[macro_export] +macro_rules! reply_error_o_consuming { + ($option_in:ident, $reply:ident, $error_code:expr, $error_msg:expr) => { + reply_error_o!($option_in, _unused_very_long_name_that_probably_has_no_conflicts_but_even_if_it_starts_with_an_underscore_and_should_not_be_used_anyway, $reply, $error_code, $error_msg,); + }; + + ($option_in:ident, $reply:ident, $error_code:expr, $error_msg:expr, $($arg:tt)*) => { + if $option_in.is_none() { + error!($error_msg, $($arg)*); + $reply.error($error_code); + return; + } + }; +} + +#[macro_export] +macro_rules! reply_error_o { + ($option_in:ident, $reply:ident, $error_code:expr, $error_msg:expr) => { + reply_error_o!($option_in, $reply, $error_code, $error_msg,); + }; + ($option_in:ident, $reply:ident, $error_code:expr, $error_msg:expr, $($arg:tt)*) => { + if $option_in.is_none() { + error!($error_msg, $($arg)*); + $reply.error($error_code); + return; + } + let $option_in = $option_in.unwrap(); + }; +} + +#[macro_export] +macro_rules! reply_error_e_consuming { + ($result_in:ident, $reply:ident, $error_code:expr, $error_msg:expr) => { + reply_error_e_consuming!($result_in, $reply, $error_code, $error_msg,); + }; + + ($result:ident, $reply:ident, $error_code:expr, $error_msg:expr, $($arg:tt)*) => { + if let Err(e) = $result { + error!("{}; e:{}",format!($error_msg, $($arg)*), e); + $reply.error($error_code); + return; + } + }; +} +#[macro_export] +macro_rules! reply_error_e { + ($result_in:ident, $reply:ident, $error_code:expr, $error_msg:expr) => { + reply_error_e!($result_in, $reply, $error_code, $error_msg,); + }; + ($result:ident, $reply:ident, $error_code:expr, $error_msg:expr, $($arg:tt)*) => { + if let Err(e) = $result { + error!("{}; e:{}",format!($error_msg, $($arg)*), e); + $reply.error($error_code); + return; + } + let $result = $result.unwrap(); + }; +} diff --git a/src/main.rs b/src/main.rs index b2fdf36..e15966c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use tracing::span; async fn main() { // drive_syncer::init_logger(); init_tracing(); + sample_logging().await; // drive_syncer::sample().await.unwrap(); // drive_syncer::google_drive::sample().await.unwrap(); // drive_syncer::watch_file_reading().await.unwrap(); @@ -13,8 +14,8 @@ async fn main() { // drive_syncer::sample_fs().await.unwrap(); - sample_logging().await; - drive_syncer::sample_drive_fs().await.unwrap(); + // drive_syncer::sample_drive_fs().await.unwrap(); + drive_syncer::sample_drive2_fs().await.unwrap(); } fn init_tracing() {