From 26d8b694b9c40d97a5eae163920a5c17c4017c47 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Tue, 26 Nov 2024 16:40:32 +0100 Subject: [PATCH 01/19] Configure default log filter --- bindings/electron/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bindings/electron/src/lib.rs b/bindings/electron/src/lib.rs index d48ad8b81ef..239b3f15f30 100644 --- a/bindings/electron/src/lib.rs +++ b/bindings/electron/src/lib.rs @@ -31,7 +31,9 @@ fn init_sentry() { #[neon::main] pub fn main(mut cx: ModuleContext) -> NeonResult<()> { - let mut builder = env_logger::Builder::from_default_env(); + let env = env_logger::Env::default() + .default_filter_or("libparsec_platform_mountpoint=trace,fuser=trace"); + let mut builder = env_logger::Builder::from_env(env); // FIXME: This is a workaround to be able to get logs from libparsec // Since electron seems to block stderr writes from libparsec. // But only on unix system, on windows it works fine the logs are display on cmd. From c686b38302438b6224ed4b9d9d8382a58843286a Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Thu, 28 Nov 2024 09:31:10 +0100 Subject: [PATCH 02/19] To remove: enable sign macos for PR --- .github/workflows/package-client.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/package-client.yml b/.github/workflows/package-client.yml index e09e9ce40c4..b7fef648d22 100644 --- a/.github/workflows/package-client.yml +++ b/.github/workflows/package-client.yml @@ -373,6 +373,7 @@ jobs: run: | if ${{ matrix.platform == 'macos' }}; then # export DEBUG='electron-notarize*' + export CSC_FOR_PULL_REQUEST='${{ github.actor == 'FirelightFlagboy' }}' export CSC_LINK='${{ secrets.MACOS_CERT }}' export CSC_KEY_PASSWORD='${{ secrets.MACOS_CERT_PASSWD }}' export CSC_NAME='${{ secrets.MACOS_CERT_COMMON_NAME }}' From e634b7d2a0d0263e5c7836f3d85c879e06d41a85 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Thu, 28 Nov 2024 10:11:37 +0100 Subject: [PATCH 03/19] Add newsfragments for 8907 --- newsfragments/8907.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/8907.bugfix.rst diff --git a/newsfragments/8907.bugfix.rst b/newsfragments/8907.bugfix.rst new file mode 100644 index 00000000000..1d5eca0859e --- /dev/null +++ b/newsfragments/8907.bugfix.rst @@ -0,0 +1 @@ +Fix bug on macOS where a drag&drop on the mount point caused a datetime overflow preventing the operation to finish. From 2696cd627f22181b077ef6462807335ab8d24fa6 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Thu, 28 Nov 2024 10:28:15 +0100 Subject: [PATCH 04/19] Allows to simply enable caching during packaging of client --- .github/workflows/package-client.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/package-client.yml b/.github/workflows/package-client.yml index b7fef648d22..a543a8226f5 100644 --- a/.github/workflows/package-client.yml +++ b/.github/workflows/package-client.yml @@ -233,6 +233,7 @@ jobs: strategy: fail-fast: false matrix: + cache-rust: [false] include: - name: 🏁 Windows platform: windows @@ -245,6 +246,7 @@ jobs: platform: macos os: macos-13 raw_latest_file: latest-mac.yml + cache-rust: true extension: "*" # Use wildcard to match dmg and zip extension os_alias: mac artifact_tag: macos-dmg @@ -333,6 +335,10 @@ jobs: working-directory: bindings/electron timeout-minutes: 1 + - uses: actions-rust-lang/setup-rust-toolchain@11df97af8e8102fd60b60a77dfbf58d40cd843b8 # pin v1.10.1 + if: matrix.cache-rust || false + timeout-minutes: 10 + - name: Build Electron bindings run: npm run build:release working-directory: bindings/electron From c139f0e3b16c735474d1c48fef53cdb3a767bc17 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Mon, 25 Nov 2024 10:36:06 +0100 Subject: [PATCH 05/19] Ignore `RUSTSEC-2024-0398` Related to #8990 --- .github/workflows/package-client.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/package-client.yml b/.github/workflows/package-client.yml index a543a8226f5..fd6046f27c0 100644 --- a/.github/workflows/package-client.yml +++ b/.github/workflows/package-client.yml @@ -233,30 +233,31 @@ jobs: strategy: fail-fast: false matrix: - cache-rust: [false] include: - name: 🏁 Windows - platform: windows - os: windows-2022 - raw_latest_file: latest.yml + artifact_tag: windows-exe + cache-rust: false extension: exe os_alias: win - artifact_tag: windows-exe + os: windows-2022 + platform: windows + raw_latest_file: latest.yml - name: 🍎 macOS - platform: macos - os: macos-13 - raw_latest_file: latest-mac.yml + artifact_tag: macos-dmg cache-rust: true extension: "*" # Use wildcard to match dmg and zip extension os_alias: mac - artifact_tag: macos-dmg + os: macos-13 + platform: macos + raw_latest_file: latest-mac.yml - name: 🐧 AppImage 4 Linux - platform: linux + artifact_tag: linux-appimage + cache-rust: false + extension: AppImage + os_alias: linux os: ubuntu-22.04 + platform: linux raw_latest_file: latest-linux.yml - os_alias: linux - extension: AppImage - artifact_tag: linux-appimage name: "${{matrix.name }}: ⚡ Package electron" runs-on: ${{ matrix.os }} timeout-minutes: 60 From 62621ed41481852d1006b7b31d119b98a07b1613 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Thu, 19 Dec 2024 15:31:04 +0100 Subject: [PATCH 06/19] Disable some mountpoint options on macOS --- libparsec/crates/platform_mountpoint/src/unix/mount.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/mount.rs b/libparsec/crates/platform_mountpoint/src/unix/mount.rs index 02a963632d6..f68cdd96ffc 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/mount.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/mount.rs @@ -57,10 +57,8 @@ impl Mountpoint { fuser::MountOption::CUSTOM(format!("volname={workspace_name}")), fuser::MountOption::DefaultPermissions, fuser::MountOption::NoSuid, + #[cfg(not(target_os = "macos"))] fuser::MountOption::Async, - #[cfg(not(skip_fuse_atime_option))] - fuser::MountOption::Atime, - #[cfg(skip_fuse_atime_option)] fuser::MountOption::NoAtime, fuser::MountOption::Exec, // TODO: Should detect and re-mount when the workspace switched between read-only and read-write From 3034b2049763e078b09d95d45fe3235b3d26b7e8 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Thu, 19 Dec 2024 16:54:56 +0100 Subject: [PATCH 07/19] Set current block usage to 0 --- libparsec/crates/platform_mountpoint/src/unix/filesystem.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index afb49088d3f..3d3e1ac2688 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -414,7 +414,7 @@ impl fuser::Filesystem for Filesystem { // Also, the total size of a workspace is not limited // For the moment let's settle on 0 MB used for 1 TB available reply.statfs( - 2 * 1024u64.pow(2), // 2 MBlocks is 1 TB + 0, // 0 for no block used 2 * 1024u64.pow(2), // 2 MBlocks is 1 TB 2 * 1024u64.pow(2), // 2 MBlocks is 1 TB 0, From 47a4a7a5fe494d9424a566c268b189d6bd565382 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 20 Dec 2024 10:36:49 +0100 Subject: [PATCH 08/19] Add inode usage to statfs --- .../crates/platform_mountpoint/src/unix/filesystem.rs | 8 ++++++-- libparsec/crates/platform_mountpoint/src/unix/inode.rs | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index 3d3e1ac2688..4bd7008b247 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -410,6 +410,10 @@ impl fuser::Filesystem for Filesystem { fn statfs(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyStatfs) { log::debug!("[FUSE] statfs(ino: {:#x?})", ino); + let (inode_used, inode_remaining) = { + let guard = self.inodes.lock().expect("mutex is poisoned"); + guard.usage() + }; // We have currently no way of easily getting the size of workspace // Also, the total size of a workspace is not limited // For the moment let's settle on 0 MB used for 1 TB available @@ -417,8 +421,8 @@ impl fuser::Filesystem for Filesystem { 0, // 0 for no block used 2 * 1024u64.pow(2), // 2 MBlocks is 1 TB 2 * 1024u64.pow(2), // 2 MBlocks is 1 TB - 0, - 0, + inode_used as u64, + inode_remaining as u64, 512 * 1024, // 512 KB, i.e the default block size 255, // 255 bytes as maximum length for filenames 512 * 1024, // 512 KB, i.e the default block size diff --git a/libparsec/crates/platform_mountpoint/src/unix/inode.rs b/libparsec/crates/platform_mountpoint/src/unix/inode.rs index c37936c347b..e1937f80dd9 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/inode.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/inode.rs @@ -148,4 +148,12 @@ impl InodesManager { }) .collect(); } + + /// Return the number of used and remaining inodes + #[must_use] + pub(super) fn usage(&self) -> (usize, usize) { + let used = self.opened.len(); + let remaining = Inode::MAX as usize - used; + (used, remaining) + } } From d2e11a7ced24ef15046b574d3ad732c5de8cc96c Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 20 Dec 2024 11:30:38 +0100 Subject: [PATCH 09/19] Add trace to see which inode are associated with which path --- libparsec/crates/platform_mountpoint/src/unix/inode.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libparsec/crates/platform_mountpoint/src/unix/inode.rs b/libparsec/crates/platform_mountpoint/src/unix/inode.rs index e1937f80dd9..e55a283c355 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/inode.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/inode.rs @@ -93,6 +93,7 @@ impl InodesManager { index as Inode }; + log::trace!("Give inode {inode} to {path}"); self.opened.insert(path, (Counter::default(), inode)); inode } @@ -110,6 +111,7 @@ impl InodesManager { if counter.is_zero() { self.opened.remove(path); self.paths_store.stack_unused_inodes.push(inode); + log::trace!("Free inode {inode} associated with {path}"); } } } From 26921486223b1ece964eabd31b02550cb98deb44 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 20 Dec 2024 11:56:58 +0100 Subject: [PATCH 10/19] Fix #8942: Fix the `ctime` of files set to the `created` time instead of the `updated` time. --- libparsec/crates/platform_mountpoint/src/unix/filesystem.rs | 6 ++++-- newsfragments/8942.bugfix.rst | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 newsfragments/8942.bugfix.rst diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index 4bd7008b247..872613ee891 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -32,6 +32,8 @@ const TTL: std::time::Duration = std::time::Duration::ZERO; /// same time. const GENERATION: u64 = 0; const BLOCK_SIZE: u64 = 512; +/// Default permissions for files and folders. +/// Equivalent to `chmod` flags `all=,u=rwx`. const PERMISSIONS: u16 = 0o700; fn os_name_to_entry_name(name: &OsStr) -> EntryNameResult { @@ -49,7 +51,7 @@ fn file_stat_to_file_attr(stat: FileStat, inode: Inode, uid: u32, gid: u32) -> f blocks: (stat.size + BLOCK_SIZE - 1) / BLOCK_SIZE, atime: updated, mtime: updated, - ctime: created, + ctime: updated, crtime: created, kind: fuser::FileType::RegularFile, perm: PERMISSIONS, @@ -78,7 +80,7 @@ fn entry_stat_to_file_attr(stat: EntryStat, inode: Inode, uid: u32, gid: u32) -> blocks: (size + BLOCK_SIZE - 1) / BLOCK_SIZE, atime: updated, mtime: updated, - ctime: created, + ctime: updated, crtime: created, kind: fuser::FileType::RegularFile, perm: PERMISSIONS, diff --git a/newsfragments/8942.bugfix.rst b/newsfragments/8942.bugfix.rst new file mode 100644 index 00000000000..e0f4ece9f68 --- /dev/null +++ b/newsfragments/8942.bugfix.rst @@ -0,0 +1 @@ +Fix mountpoint files' date attributes on unix systems From 06f22e5a453c39f8bcf1e81edb6b80398a950ecf Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 20 Dec 2024 14:04:13 +0100 Subject: [PATCH 11/19] Add error when not manually sending a reply --- libparsec/crates/platform_mountpoint/src/unix/filesystem.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index 872613ee891..eb5b15693fb 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -190,7 +190,10 @@ macro_rules! reply_on_drop_guard { // Already replied None => (), // Not replied time to do it with ourself ! - Some(reply) => reply.error(libc::EIO), + Some(reply) => { + log::error!("Reply not sent, sending generic error"); + reply.error(libc::EIO) + } } } } From 15abd2ae8a513b52ae58c6dafbb6cb0e2a48ae35 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 20 Dec 2024 16:24:52 +0100 Subject: [PATCH 12/19] Factorize file stat generation for fuser --- bindings/electron/src/index.d.ts | 8 +- bindings/electron/src/meths.rs | 128 +--------------- bindings/generator/api/workspace.py | 60 ++++---- bindings/web/src/meths.rs | 139 +----------------- cli/tests/integration/workspace/import.rs | 2 +- client/src/plugins/libparsec/definitions.ts | 8 +- .../src/workspace/transactions/fd_stat.rs | 2 +- .../src/workspace/transactions/stat_entry.rs | 43 +++--- .../client/tests/unit/client/with_monitors.rs | 2 +- .../client/tests/unit/workspace/fd_flush.rs | 4 +- .../client/tests/unit/workspace/fd_write.rs | 4 +- .../unit/workspace/folder_transactions.rs | 5 +- .../client/tests/unit/workspace/open_file.rs | 19 ++- .../tests/unit/workspace/read_folder.rs | 98 ++++++------ .../tests/unit/workspace/remove_entry.rs | 2 +- .../client/tests/unit/workspace/stat_entry.rs | 125 ++++++++++------ .../src/unix/filesystem.rs | 27 +--- .../tests/unit/operations/flush_file.rs | 2 +- .../tests/unit/operations/open_file.rs | 22 +-- 19 files changed, 236 insertions(+), 464 deletions(-) diff --git a/bindings/electron/src/index.d.ts b/bindings/electron/src/index.d.ts index 911039b4247..d50f5a03b6b 100644 --- a/bindings/electron/src/index.d.ts +++ b/bindings/electron/src/index.d.ts @@ -1129,14 +1129,8 @@ export type DeviceSaveStrategy = export interface EntryStatFile { tag: "File" confinement_point: string | null - id: string parent: string - created: number - updated: number - base_version: number - is_placeholder: boolean - need_sync: boolean - size: number + base: FileStat } export interface EntryStatFolder { tag: "Folder" diff --git a/bindings/electron/src/meths.rs b/bindings/electron/src/meths.rs index 6998a9a46a5..7f4e0961b16 100644 --- a/bindings/electron/src/meths.rs +++ b/bindings/electron/src/meths.rs @@ -4767,18 +4767,6 @@ fn variant_entry_stat_js_to_rs<'a>( } } }; - let id = { - let js_val: Handle = obj.get(cx, "id")?; - { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; let parent = { let js_val: Handle = obj.get(cx, "parent")?; { @@ -4791,74 +4779,14 @@ fn variant_entry_stat_js_to_rs<'a>( } } }; - let created = { - let js_val: Handle = obj.get(cx, "created")?; - { - let v = js_val.value(cx); - let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { - libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) - .map_err(|_| "Out-of-bound datetime") - }; - match custom_from_rs_f64(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let updated = { - let js_val: Handle = obj.get(cx, "updated")?; - { - let v = js_val.value(cx); - let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { - libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) - .map_err(|_| "Out-of-bound datetime") - }; - match custom_from_rs_f64(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let base_version = { - let js_val: Handle = obj.get(cx, "baseVersion")?; - { - let v = js_val.value(cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } - }; - let is_placeholder = { - let js_val: Handle = obj.get(cx, "isPlaceholder")?; - js_val.value(cx) - }; - let need_sync = { - let js_val: Handle = obj.get(cx, "needSync")?; - js_val.value(cx) - }; - let size = { - let js_val: Handle = obj.get(cx, "size")?; - { - let v = js_val.value(cx); - if v < (u64::MIN as f64) || (u64::MAX as f64) < v { - cx.throw_type_error("Not an u64 number")? - } - let v = v as u64; - v - } + let base = { + let js_val: Handle = obj.get(cx, "base")?; + struct_file_stat_js_to_rs(cx, js_val)? }; Ok(libparsec::EntryStat::File { confinement_point, - id, parent, - created, - updated, - base_version, - is_placeholder, - need_sync, - size, + base, }) } "EntryStatFolder" => { @@ -4978,14 +4906,8 @@ fn variant_entry_stat_rs_to_js<'a>( match rs_obj { libparsec::EntryStat::File { confinement_point, - id, parent, - created, - updated, - base_version, - is_placeholder, - need_sync, - size, + base, .. } => { let js_tag = JsString::try_new(cx, "EntryStatFile").or_throw(cx)?; @@ -5004,16 +4926,6 @@ fn variant_entry_stat_rs_to_js<'a>( None => JsNull::new(cx).as_value(cx), }; js_obj.set(cx, "confinementPoint", js_confinement_point)?; - let js_id = JsString::try_new(cx, { - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; - match custom_to_rs_string(id) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(cx)?; - js_obj.set(cx, "id", js_id)?; let js_parent = JsString::try_new(cx, { let custom_to_rs_string = |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; @@ -5024,34 +4936,8 @@ fn variant_entry_stat_rs_to_js<'a>( }) .or_throw(cx)?; js_obj.set(cx, "parent", js_parent)?; - let js_created = JsNumber::new(cx, { - let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { - Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) - }; - match custom_to_rs_f64(created) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }); - js_obj.set(cx, "created", js_created)?; - let js_updated = JsNumber::new(cx, { - let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { - Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) - }; - match custom_to_rs_f64(updated) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }); - js_obj.set(cx, "updated", js_updated)?; - let js_base_version = JsNumber::new(cx, base_version as f64); - js_obj.set(cx, "baseVersion", js_base_version)?; - let js_is_placeholder = JsBoolean::new(cx, is_placeholder); - js_obj.set(cx, "isPlaceholder", js_is_placeholder)?; - let js_need_sync = JsBoolean::new(cx, need_sync); - js_obj.set(cx, "needSync", js_need_sync)?; - let js_size = JsNumber::new(cx, size as f64); - js_obj.set(cx, "size", js_size)?; + let js_base = struct_file_stat_rs_to_js(cx, base)?; + js_obj.set(cx, "base", js_base)?; } libparsec::EntryStat::Folder { confinement_point, diff --git a/bindings/generator/api/workspace.py b/bindings/generator/api/workspace.py index de39204871d..b966c1e628b 100644 --- a/bindings/generator/api/workspace.py +++ b/bindings/generator/api/workspace.py @@ -278,6 +278,33 @@ class InvalidManifest: class Internal: pass +class FileDescriptor(U32BasedType): + custom_from_rs_u32 = "|raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }" + custom_to_rs_u32 = "|fd: libparsec::FileDescriptor| -> Result<_, &'static str> { Ok(fd.0) }" + + +class WorkspaceFdStatError(ErrorVariant): + class BadFileDescriptor: + pass + + class Internal: + pass + + +class FileStat(Structure): + id: VlobID + created: DateTime + updated: DateTime + base_version: VersionInt + is_placeholder: bool + need_sync: bool + size: SizeInt + + +async def workspace_fd_stat( + workspace: Handle, fd: FileDescriptor +) -> Result[FileStat, WorkspaceFdStatError]: + raise NotImplementedError class WorkspaceStatEntryError(ErrorVariant): class Offline: @@ -308,14 +335,8 @@ class Internal: class EntryStat(Variant): class File: confinement_point: Optional[VlobID] - id: VlobID parent: VlobID - created: DateTime - updated: DateTime - base_version: VersionInt - is_placeholder: bool - need_sync: bool - size: SizeInt + base: FileStat class Folder: confinement_point: Optional[VlobID] @@ -468,10 +489,6 @@ class OpenOptions(Structure): create_new: bool -class FileDescriptor(U32BasedType): - custom_from_rs_u32 = "|raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }" - custom_to_rs_u32 = "|fd: libparsec::FileDescriptor| -> Result<_, &'static str> { Ok(fd.0) }" - class WorkspaceOpenFileError(ErrorVariant): class Offline: @@ -545,28 +562,7 @@ async def workspace_fd_close( raise NotImplementedError -class WorkspaceFdStatError(ErrorVariant): - class BadFileDescriptor: - pass - - class Internal: - pass - -class FileStat(Structure): - id: VlobID - created: DateTime - updated: DateTime - base_version: VersionInt - is_placeholder: bool - need_sync: bool - size: SizeInt - - -async def workspace_fd_stat( - workspace: Handle, fd: FileDescriptor -) -> Result[FileStat, WorkspaceFdStatError]: - raise NotImplementedError class WorkspaceFdFlushError(ErrorVariant): diff --git a/bindings/web/src/meths.rs b/bindings/web/src/meths.rs index f34e106ad5c..4d76460e395 100644 --- a/bindings/web/src/meths.rs +++ b/bindings/web/src/meths.rs @@ -5239,21 +5239,6 @@ fn variant_entry_stat_js_to_rs(obj: JsValue) -> Result() - .ok() - .and_then(|s| s.as_string()) - .ok_or_else(|| TypeError::new("Not a string")) - .and_then(|x| { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(x).map_err(|e| TypeError::new(e.as_ref())) - }) - .map_err(|_| TypeError::new("Not a valid VlobID"))? - }; let parent = { let js_val = Reflect::get(&obj, &"parent".into())?; js_val @@ -5269,80 +5254,14 @@ fn variant_entry_stat_js_to_rs(obj: JsValue) -> Result()?.value_of(); - let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { - libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) - .map_err(|_| "Out-of-bound datetime") - }; - let v = custom_from_rs_f64(v).map_err(|e| TypeError::new(e.as_ref()))?; - v - } - }; - let updated = { - let js_val = Reflect::get(&obj, &"updated".into())?; - { - let v = js_val.dyn_into::()?.value_of(); - let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { - libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) - .map_err(|_| "Out-of-bound datetime") - }; - let v = custom_from_rs_f64(v).map_err(|e| TypeError::new(e.as_ref()))?; - v - } - }; - let base_version = { - let js_val = Reflect::get(&obj, &"baseVersion".into())?; - { - let v = js_val - .dyn_into::() - .map_err(|_| TypeError::new("Not a number"))? - .value_of(); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - return Err(JsValue::from(TypeError::new("Not an u32 number"))); - } - v as u32 - } - }; - let is_placeholder = { - let js_val = Reflect::get(&obj, &"isPlaceholder".into())?; - js_val - .dyn_into::() - .map_err(|_| TypeError::new("Not a boolean"))? - .value_of() - }; - let need_sync = { - let js_val = Reflect::get(&obj, &"needSync".into())?; - js_val - .dyn_into::() - .map_err(|_| TypeError::new("Not a boolean"))? - .value_of() - }; - let size = { - let js_val = Reflect::get(&obj, &"size".into())?; - { - let v = js_val - .dyn_into::() - .map_err(|_| TypeError::new("Not a number"))? - .value_of(); - if v < (u64::MIN as f64) || (u64::MAX as f64) < v { - return Err(JsValue::from(TypeError::new("Not an u64 number"))); - } - v as u64 - } + let base = { + let js_val = Reflect::get(&obj, &"base".into())?; + struct_file_stat_js_to_rs(js_val)? }; Ok(libparsec::EntryStat::File { confinement_point, - id, parent, - created, - updated, - base_version, - is_placeholder, - need_sync, - size, + base, }) } "EntryStatFolder" => { @@ -5471,14 +5390,8 @@ fn variant_entry_stat_rs_to_js(rs_obj: libparsec::EntryStat) -> Result { Reflect::set(&js_obj, &"tag".into(), &"EntryStatFile".into())?; @@ -5495,16 +5408,6 @@ fn variant_entry_stat_rs_to_js(rs_obj: libparsec::EntryStat) -> Result JsValue::NULL, }; Reflect::set(&js_obj, &"confinementPoint".into(), &js_confinement_point)?; - let js_id = JsValue::from_str({ - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; - match custom_to_rs_string(id) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - } - .as_ref() - }); - Reflect::set(&js_obj, &"id".into(), &js_id)?; let js_parent = JsValue::from_str({ let custom_to_rs_string = |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; @@ -5515,36 +5418,8 @@ fn variant_entry_stat_rs_to_js(rs_obj: libparsec::EntryStat) -> Result Result { - Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) - }; - let v = match custom_to_rs_f64(created) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - }; - JsValue::from(v) - }; - Reflect::set(&js_obj, &"created".into(), &js_created)?; - let js_updated = { - let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { - Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) - }; - let v = match custom_to_rs_f64(updated) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - }; - JsValue::from(v) - }; - Reflect::set(&js_obj, &"updated".into(), &js_updated)?; - let js_base_version = JsValue::from(base_version); - Reflect::set(&js_obj, &"baseVersion".into(), &js_base_version)?; - let js_is_placeholder = is_placeholder.into(); - Reflect::set(&js_obj, &"isPlaceholder".into(), &js_is_placeholder)?; - let js_need_sync = need_sync.into(); - Reflect::set(&js_obj, &"needSync".into(), &js_need_sync)?; - let js_size = JsValue::from(size); - Reflect::set(&js_obj, &"size".into(), &js_size)?; + let js_base = struct_file_stat_rs_to_js(base)?; + Reflect::set(&js_obj, &"base".into(), &js_base)?; } libparsec::EntryStat::Folder { confinement_point, diff --git a/cli/tests/integration/workspace/import.rs b/cli/tests/integration/workspace/import.rs index cff6928bb43..15ed1d13aeb 100644 --- a/cli/tests/integration/workspace/import.rs +++ b/cli/tests/integration/workspace/import.rs @@ -73,7 +73,7 @@ async fn workspace_import_file(tmp_path: TmpPath) { assert_eq!(entries.len(), 1); let (name, stat) = &entries[0]; assert_eq!(name.as_ref(), "test.txt"); - assert!(matches!(stat, libparsec::EntryStat::File { size, .. } if size == &13)); + assert!(matches!(stat, libparsec::EntryStat::File { base, .. } if base.size == 13)); } #[rstest::rstest] diff --git a/client/src/plugins/libparsec/definitions.ts b/client/src/plugins/libparsec/definitions.ts index 46d665a2e1c..85f250b942e 100644 --- a/client/src/plugins/libparsec/definitions.ts +++ b/client/src/plugins/libparsec/definitions.ts @@ -1323,14 +1323,8 @@ export enum EntryStatTag { export interface EntryStatFile { tag: EntryStatTag.File confinementPoint: VlobID | null - id: VlobID parent: VlobID - created: DateTime - updated: DateTime - baseVersion: VersionInt - isPlaceholder: boolean - needSync: boolean - size: SizeInt + base: FileStat } export interface EntryStatFolder { tag: EntryStatTag.Folder diff --git a/libparsec/crates/client/src/workspace/transactions/fd_stat.rs b/libparsec/crates/client/src/workspace/transactions/fd_stat.rs index e5863d0744a..0769d1dde3f 100644 --- a/libparsec/crates/client/src/workspace/transactions/fd_stat.rs +++ b/libparsec/crates/client/src/workspace/transactions/fd_stat.rs @@ -4,7 +4,7 @@ use libparsec_types::prelude::*; use crate::workspace::WorkspaceOps; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct FileStat { pub id: VlobID, pub created: DateTime, diff --git a/libparsec/crates/client/src/workspace/transactions/stat_entry.rs b/libparsec/crates/client/src/workspace/transactions/stat_entry.rs index 75d131f290b..b8fa0c92fc1 100644 --- a/libparsec/crates/client/src/workspace/transactions/stat_entry.rs +++ b/libparsec/crates/client/src/workspace/transactions/stat_entry.rs @@ -7,6 +7,7 @@ use crate::{ certif::{InvalidCertificateError, InvalidKeysBundleError, InvalidManifestError}, workspace::{ store::{GetManifestError, PathConfinementPoint, ResolvePathError}, + transactions::fd_stat::FileStat, WorkspaceOps, }, }; @@ -18,14 +19,8 @@ pub enum EntryStat { /// manifest that contains a child with a confined name in the path leading /// to our entry. confinement_point: Option, - id: VlobID, parent: VlobID, - created: DateTime, - updated: DateTime, - base_version: VersionInt, - is_placeholder: bool, - need_sync: bool, - size: SizeInt, + base: FileStat, }, // Here Folder can also be the root of the workspace (i.e. WorkspaceManifest) Folder { @@ -46,7 +41,7 @@ pub enum EntryStat { impl EntryStat { pub fn id(&self) -> VlobID { match self { - EntryStat::File { id, .. } => *id, + EntryStat::File { base, .. } => base.id, EntryStat::Folder { id, .. } => *id, } } @@ -173,14 +168,16 @@ pub(crate) async fn stat_entry_by_id( EntryStat::File { confinement_point: confinement_point.into(), - id: manifest.base.id, parent: manifest.parent, - created: manifest.base.created, - updated: manifest.updated, - base_version: manifest.base.version, - is_placeholder: manifest.base.version == 0, - need_sync: manifest.need_sync, - size: manifest.size, + base: FileStat { + id: manifest.base.id, + created: manifest.base.created, + updated: manifest.updated, + base_version: manifest.base.version, + is_placeholder: manifest.base.version == 0, + need_sync: manifest.need_sync, + size: manifest.size, + }, } } }; @@ -244,14 +241,16 @@ pub(crate) async fn stat_entry( EntryStat::File { confinement_point: confinement_point.into(), - id: manifest.base.id, parent: manifest.parent, - created: manifest.base.created, - updated: manifest.updated, - base_version: manifest.base.version, - is_placeholder: manifest.base.version == 0, - need_sync: manifest.need_sync, - size: manifest.size, + base: FileStat { + id: manifest.base.id, + created: manifest.base.created, + updated: manifest.updated, + base_version: manifest.base.version, + is_placeholder: manifest.base.version == 0, + need_sync: manifest.need_sync, + size: manifest.size, + }, } } }; diff --git a/libparsec/crates/client/tests/unit/client/with_monitors.rs b/libparsec/crates/client/tests/unit/client/with_monitors.rs index cb50e82abb8..3a16ae89c74 100644 --- a/libparsec/crates/client/tests/unit/client/with_monitors.rs +++ b/libparsec/crates/client/tests/unit/client/with_monitors.rs @@ -89,7 +89,7 @@ async fn multi_devices(env: &TestbedEnv) { .into_iter() .map(|(name, stat)| { let id = match stat { - EntryStat::File { id, .. } => id, + EntryStat::File { base, .. } => base.id, EntryStat::Folder { id, .. } => id, }; (name, id) diff --git a/libparsec/crates/client/tests/unit/workspace/fd_flush.rs b/libparsec/crates/client/tests/unit/workspace/fd_flush.rs index 3a11ea896e2..31420b9322e 100644 --- a/libparsec/crates/client/tests/unit/workspace/fd_flush.rs +++ b/libparsec/crates/client/tests/unit/workspace/fd_flush.rs @@ -21,7 +21,7 @@ async fn write_then_flush(env: &TestbedEnv) { // 1) Open the file and do the write & flush let initial_size = match ops.stat_entry_by_id(wksp1_bar_txt_id).await.unwrap() { - EntryStat::File { size, .. } => size, + EntryStat::File { base, .. } => base.size, EntryStat::Folder { .. } => unreachable!(), }; @@ -44,7 +44,7 @@ async fn write_then_flush(env: &TestbedEnv) { // Flush makes metadata visible let size = match ops.stat_entry_by_id(wksp1_bar_txt_id).await.unwrap() { - EntryStat::File { size, .. } => size, + EntryStat::File { base, .. } => base.size, EntryStat::Folder { .. } => unreachable!(), }; p_assert_eq!(size, initial_size + 3); diff --git a/libparsec/crates/client/tests/unit/workspace/fd_write.rs b/libparsec/crates/client/tests/unit/workspace/fd_write.rs index 9a51801c072..266d2449ff8 100644 --- a/libparsec/crates/client/tests/unit/workspace/fd_write.rs +++ b/libparsec/crates/client/tests/unit/workspace/fd_write.rs @@ -215,7 +215,7 @@ async fn write_zero( let spy = ops.event_bus.spy.start_expecting(); let size = match ops.stat_entry_by_id(wksp1_bar_txt_id).await.unwrap() { - EntryStat::File { size, .. } => size, + EntryStat::File { base, .. } => base.size, EntryStat::Folder { .. } => unreachable!(), }; @@ -302,7 +302,7 @@ async fn write_data( let mut spy = ops.event_bus.spy.start_expecting(); let size = match ops.stat_entry_by_id(wksp1_bar_txt_id).await.unwrap() { - EntryStat::File { size, .. } => size, + EntryStat::File { base, .. } => base.size, EntryStat::Folder { .. } => unreachable!(), }; diff --git a/libparsec/crates/client/tests/unit/workspace/folder_transactions.rs b/libparsec/crates/client/tests/unit/workspace/folder_transactions.rs index 5ee7f8497d1..3eb4aabf8d3 100644 --- a/libparsec/crates/client/tests/unit/workspace/folder_transactions.rs +++ b/libparsec/crates/client/tests/unit/workspace/folder_transactions.rs @@ -7,7 +7,7 @@ use libparsec_types::prelude::*; use super::utils::{ls, workspace_ops_factory, workspace_ops_with_prevent_sync_pattern_factory}; use crate::{ - workspace::{EntryStat, MoveEntryMode}, + workspace::{EntryStat, FileStat, MoveEntryMode}, EventWorkspaceOpsOutboundSyncNeeded, WorkspaceOps, }; @@ -560,9 +560,8 @@ fn check_stat_with_caller( ( EntryType::File, EntryStat::File { - id, - need_sync, confinement_point, + base: FileStat { id, need_sync, .. }, .. }, ) diff --git a/libparsec/crates/client/tests/unit/workspace/open_file.rs b/libparsec/crates/client/tests/unit/workspace/open_file.rs index 9e391df3c8e..990b58746f7 100644 --- a/libparsec/crates/client/tests/unit/workspace/open_file.rs +++ b/libparsec/crates/client/tests/unit/workspace/open_file.rs @@ -11,7 +11,8 @@ use libparsec_types::prelude::*; use super::utils::{assert_ls, ls, workspace_ops_factory}; use crate::{ workspace::{ - EntryStat, OpenOptions, WorkspaceFdReadError, WorkspaceFdWriteError, WorkspaceOpenFileError, + EntryStat, FileStat, OpenOptions, WorkspaceFdReadError, WorkspaceFdWriteError, + WorkspaceOpenFileError, }, EventWorkspaceOpsOutboundSyncNeeded, }; @@ -201,7 +202,7 @@ async fn open_with_create(#[values(true, false)] file_already_exists: bool, env: .await .unwrap(); let new_file_id = match stat { - EntryStat::File { id, .. } => id, + EntryStat::File { base, .. } => base.id, EntryStat::Folder { .. } => unreachable!(), }; spy.assert_next(|e: &EventWorkspaceOpsOutboundSyncNeeded| { @@ -261,7 +262,7 @@ async fn open_with_create_new(#[values(true, false)] file_already_exists: bool, .await .unwrap(); let new_file_id = match stat { - EntryStat::File { id, .. } => id, + EntryStat::File { base, .. } => base.id, EntryStat::Folder { .. } => unreachable!(), }; spy.assert_next(|e: &EventWorkspaceOpsOutboundSyncNeeded| { @@ -305,10 +306,14 @@ async fn open_with_truncate(env: &TestbedEnv) { match ops.stat_entry_by_id(wksp1_bar_txt_id).await.unwrap() { EntryStat::File { - base_version, - is_placeholder, - need_sync, - size, + base: + FileStat { + base_version, + is_placeholder, + need_sync, + size, + .. + }, .. } => { p_assert_eq!(need_sync, true); diff --git a/libparsec/crates/client/tests/unit/workspace/read_folder.rs b/libparsec/crates/client/tests/unit/workspace/read_folder.rs index 3d832adb608..aede928e6ea 100644 --- a/libparsec/crates/client/tests/unit/workspace/read_folder.rs +++ b/libparsec/crates/client/tests/unit/workspace/read_folder.rs @@ -8,7 +8,7 @@ use libparsec_types::prelude::*; use super::utils::workspace_ops_factory; use crate::workspace::{ - transactions::FolderReaderStatNextOutcome, EntryStat, MoveEntryMode, OpenOptions, + transactions::FolderReaderStatNextOutcome, EntryStat, FileStat, MoveEntryMode, OpenOptions, }; fn expect_entry(stat: FolderReaderStatNextOutcome<'_>) -> (&EntryName, EntryStat) { @@ -43,14 +43,16 @@ async fn ok_with_local_cache(#[values(true, false)] target_is_root: bool, env: & "bar.txt".parse().unwrap(), EntryStat::File { confinement_point: None, - id: wksp1_bar_txt_id, parent: wksp1_id, - created: "2000-01-07T00:00:00Z".parse().unwrap(), - updated: "2000-01-07T00:00:00Z".parse().unwrap(), - base_version: 1, - is_placeholder: false, - need_sync: false, - size: 11, + base: FileStat { + id: wksp1_bar_txt_id, + created: "2000-01-07T00:00:00Z".parse().unwrap(), + updated: "2000-01-07T00:00:00Z".parse().unwrap(), + base_version: 1, + is_placeholder: false, + need_sync: false, + size: 11, + }, }, ), ( @@ -76,14 +78,16 @@ async fn ok_with_local_cache(#[values(true, false)] target_is_root: bool, env: & "egg.txt".parse().unwrap(), EntryStat::File { confinement_point: None, - id: wksp1_foo_egg_txt_id, parent: wksp1_foo_id, - created: "2000-01-09T00:00:00Z".parse().unwrap(), - updated: "2000-01-09T00:00:00Z".parse().unwrap(), - base_version: 1, - is_placeholder: false, - need_sync: false, - size: 0, + base: FileStat { + id: wksp1_foo_egg_txt_id, + created: "2000-01-09T00:00:00Z".parse().unwrap(), + updated: "2000-01-09T00:00:00Z".parse().unwrap(), + base_version: 1, + is_placeholder: false, + need_sync: false, + size: 0, + }, }, ), ( @@ -192,14 +196,16 @@ async fn ok_no_local_cache(#[values(true, false)] target_is_root: bool, env: &Te "bar.txt".parse().unwrap(), EntryStat::File { confinement_point: None, - id: wksp1_bar_txt_id, parent: wksp1_id, - created: "2000-01-07T00:00:00Z".parse().unwrap(), - updated: "2000-01-07T00:00:00Z".parse().unwrap(), - base_version: 1, - is_placeholder: false, - need_sync: false, - size: 11, + base: FileStat { + id: wksp1_bar_txt_id, + created: "2000-01-07T00:00:00Z".parse().unwrap(), + updated: "2000-01-07T00:00:00Z".parse().unwrap(), + base_version: 1, + is_placeholder: false, + need_sync: false, + size: 11, + }, }, ), ( @@ -225,14 +231,16 @@ async fn ok_no_local_cache(#[values(true, false)] target_is_root: bool, env: &Te "egg.txt".parse().unwrap(), EntryStat::File { confinement_point: None, - id: wksp1_foo_egg_txt_id, parent: wksp1_foo_id, - created: "2000-01-09T00:00:00Z".parse().unwrap(), - updated: "2000-01-09T00:00:00Z".parse().unwrap(), - base_version: 1, - is_placeholder: false, - need_sync: false, - size: 0, + base: FileStat { + id: wksp1_foo_egg_txt_id, + created: "2000-01-09T00:00:00Z".parse().unwrap(), + updated: "2000-01-09T00:00:00Z".parse().unwrap(), + base_version: 1, + is_placeholder: false, + need_sync: false, + size: 0, + }, }, ), ( @@ -427,14 +435,16 @@ async fn read_folder_with_confined_entries( "egg.txt.tmp".parse().unwrap(), EntryStat::File { confinement_point: expected_egg_txt_confinement_point, - id: wksp1_foo_egg_txt_id, parent: wksp1_foo_id, - created: "2000-01-09T00:00:00Z".parse().unwrap(), - updated: "2000-01-09T00:00:00Z".parse().unwrap(), - base_version: 1, - is_placeholder: false, - need_sync: false, - size: 0, + base: FileStat { + id: wksp1_foo_egg_txt_id, + created: "2000-01-09T00:00:00Z".parse().unwrap(), + updated: "2000-01-09T00:00:00Z".parse().unwrap(), + base_version: 1, + is_placeholder: false, + need_sync: false, + size: 0, + }, }, ), ( @@ -564,14 +574,16 @@ async fn read_folder_containing_under_modification_file( "bar.txt".parse().unwrap(), EntryStat::File { confinement_point: None, - id: wksp1_bar_txt_id, parent: wksp1_id, - created: "2000-01-07T00:00:00Z".parse().unwrap(), - updated: "2020-01-01T00:00:00Z".parse().unwrap(), - base_version: 1, - is_placeholder: false, - need_sync: true, - size: expected_size, + base: FileStat { + id: wksp1_bar_txt_id, + created: "2000-01-07T00:00:00Z".parse().unwrap(), + updated: "2020-01-01T00:00:00Z".parse().unwrap(), + base_version: 1, + is_placeholder: false, + need_sync: true, + size: expected_size, + }, }, ), ( diff --git a/libparsec/crates/client/tests/unit/workspace/remove_entry.rs b/libparsec/crates/client/tests/unit/workspace/remove_entry.rs index c67a65c0362..26d45dbe968 100644 --- a/libparsec/crates/client/tests/unit/workspace/remove_entry.rs +++ b/libparsec/crates/client/tests/unit/workspace/remove_entry.rs @@ -225,7 +225,7 @@ async fn remove_file_with_local_changes( ); p_assert_matches!( ops.stat_entry_by_id(wksp1_foo_egg_txt_id).await.unwrap(), - EntryStat::File { need_sync, .. } if !need_sync + EntryStat::File { base, .. } if !base.need_sync ); p_assert_matches!( ops.store diff --git a/libparsec/crates/client/tests/unit/workspace/stat_entry.rs b/libparsec/crates/client/tests/unit/workspace/stat_entry.rs index 05a9847f160..be444798098 100644 --- a/libparsec/crates/client/tests/unit/workspace/stat_entry.rs +++ b/libparsec/crates/client/tests/unit/workspace/stat_entry.rs @@ -4,7 +4,9 @@ use libparsec_tests_fixtures::prelude::*; use libparsec_types::prelude::*; use super::utils::workspace_ops_factory; -use crate::workspace::{store::PathConfinementPoint, EntryStat, OpenOptions, WorkspaceOps}; +use crate::workspace::{ + store::PathConfinementPoint, EntryStat, FileStat, OpenOptions, WorkspaceOps, +}; #[parsec_test(testbed = "minimal_client_ready", with_server)] async fn stat_entry(#[values(true, false)] local_cache: bool, env: &TestbedEnv) { @@ -97,14 +99,16 @@ async fn stat_entry(#[values(true, false)] local_cache: bool, env: &TestbedEnv) info, EntryStat::File{ confinement_point, - id, parent, - created, - updated, - base_version, - is_placeholder, - need_sync, - size, + base: FileStat { + id, + created, + updated, + base_version, + is_placeholder, + need_sync, + size, + } } if { p_assert_eq!(confinement_point, None); @@ -257,14 +261,17 @@ async fn stat_entry_by_id( info, EntryStat::File{ confinement_point, - id, parent, - created, - updated, - base_version, - is_placeholder, - need_sync, - size, + base: FileStat { + + id, + created, + updated, + base_version, + is_placeholder, + need_sync, + size, + } } if { p_assert_eq!(confinement_point, expected_confinement_point); @@ -384,11 +391,15 @@ async fn stat_entry_on_confined_entry( info, EntryStat::File{ confinement_point, - id, parent, - base_version, - is_placeholder, - need_sync, + base: FileStat { + + id, + base_version, + is_placeholder, + need_sync, + .. + }, .. } if { @@ -408,11 +419,15 @@ async fn stat_entry_on_confined_entry( info, EntryStat::File{ confinement_point, - id, parent, - base_version, - is_placeholder, - need_sync, + base: FileStat { + + id, + base_version, + is_placeholder, + need_sync, + .. + }, .. } if { @@ -444,11 +459,15 @@ async fn stat_entry_on_confined_entry( info, EntryStat::File{ confinement_point, - id, parent, - base_version, - is_placeholder, - need_sync, + base: FileStat { + id, + base_version, + is_placeholder, + need_sync, + .. + + }, .. } if { @@ -468,11 +487,15 @@ async fn stat_entry_on_confined_entry( info, EntryStat::File{ confinement_point, - id, parent, - base_version, - is_placeholder, - need_sync, + base:FileStat { + + id, + base_version, + is_placeholder, + need_sync, + .. + }, .. } if { @@ -504,11 +527,15 @@ async fn stat_entry_on_confined_entry( info, EntryStat::File{ confinement_point, - id, parent, - base_version, - is_placeholder, - need_sync, + base: FileStat { + + id, + base_version, + is_placeholder, + need_sync, + .. + }, .. } if { @@ -528,11 +555,15 @@ async fn stat_entry_on_confined_entry( info, EntryStat::File{ confinement_point, - id, parent, - base_version, - is_placeholder, - need_sync, + base: FileStat { + + id, + base_version, + is_placeholder, + need_sync, + .. + }, .. } if { @@ -598,14 +629,16 @@ async fn stat_entry_on_under_modification_file( let expected_stat = EntryStat::File { confinement_point: None, - id: wksp1_bar_txt_id, parent: wksp1_id, - created: "2000-01-07T00:00:00Z".parse().unwrap(), - updated: "2020-01-01T00:00:00Z".parse().unwrap(), - base_version: 1, - is_placeholder: false, - need_sync: true, - size: expected_size, + base: FileStat { + id: wksp1_bar_txt_id, + created: "2000-01-07T00:00:00Z".parse().unwrap(), + updated: "2020-01-01T00:00:00Z".parse().unwrap(), + base_version: 1, + is_placeholder: false, + need_sync: true, + size: expected_size, + }, }; p_assert_eq!(stat, expected_stat); diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index eb5b15693fb..530e023ca7b 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -66,32 +66,7 @@ fn file_stat_to_file_attr(stat: FileStat, inode: Inode, uid: u32, gid: u32) -> f fn entry_stat_to_file_attr(stat: EntryStat, inode: Inode, uid: u32, gid: u32) -> fuser::FileAttr { match stat { - EntryStat::File { - created, - updated, - size, - .. - } => { - let created: std::time::SystemTime = created.into(); - let updated: std::time::SystemTime = updated.into(); - fuser::FileAttr { - ino: inode, - size, - blocks: (size + BLOCK_SIZE - 1) / BLOCK_SIZE, - atime: updated, - mtime: updated, - ctime: updated, - crtime: created, - kind: fuser::FileType::RegularFile, - perm: PERMISSIONS, - nlink: 1, - uid, - gid, - rdev: 0, - blksize: BLOCK_SIZE as u32, - flags: 0, - } - } + EntryStat::File { base, .. } => file_stat_to_file_attr(base, inode, uid, gid), EntryStat::Folder { created, updated, .. diff --git a/libparsec/crates/platform_mountpoint/tests/unit/operations/flush_file.rs b/libparsec/crates/platform_mountpoint/tests/unit/operations/flush_file.rs index 0346676682b..4a3cb659fe0 100644 --- a/libparsec/crates/platform_mountpoint/tests/unit/operations/flush_file.rs +++ b/libparsec/crates/platform_mountpoint/tests/unit/operations/flush_file.rs @@ -54,7 +54,7 @@ async fn ok( .stat_entry(&"/bar.txt".parse().unwrap()) .await .unwrap(); - p_assert_matches!(stat, EntryStat::File { size, .. } if size == expected_size); + p_assert_matches!(stat, EntryStat::File { base, .. } if base.size == expected_size); // File descriptor must be kept so that the file is not closed before we do the stat tokio::task::spawn_blocking(move || { diff --git a/libparsec/crates/platform_mountpoint/tests/unit/operations/open_file.rs b/libparsec/crates/platform_mountpoint/tests/unit/operations/open_file.rs index 390d41cd987..d166e3467f9 100644 --- a/libparsec/crates/platform_mountpoint/tests/unit/operations/open_file.rs +++ b/libparsec/crates/platform_mountpoint/tests/unit/operations/open_file.rs @@ -70,7 +70,7 @@ async fn ok_first_open( .stat_entry(&format!("/{}", name).parse().unwrap()) .await .unwrap(); - p_assert_matches!(stat, EntryStat::File { size, .. } if size == expected_size); + p_assert_matches!(stat, EntryStat::File { base, .. } if base.size == expected_size); } ); } @@ -122,7 +122,7 @@ async fn ok_already_opened( .stat_entry(&format!("/{}", name).parse().unwrap()) .await .unwrap(); - p_assert_matches!(stat, EntryStat::File { size, .. } if size == expected_size); + p_assert_matches!(stat, EntryStat::File { base, .. } if base.size == expected_size); } ); } @@ -131,6 +131,8 @@ async fn ok_already_opened( #[cfg(not(target_os = "windows"))] #[parsec_test(testbed = "minimal_client_ready")] async fn no_create_and_not_found(tmp_path: TmpPath, env: &TestbedEnv) { + use libparsec_client::workspace::FileStat; + mount_and_test!( env, &tmp_path, @@ -143,14 +145,16 @@ async fn no_create_and_not_found(tmp_path: TmpPath, env: &TestbedEnv) { if path == &"/dummy.txt".parse().unwrap() { Some(Ok(EntryStat::File { confinement_point: None, - id: VlobID::default(), parent: VlobID::default(), - created: "2000-01-01T00:00:00Z".parse().unwrap(), - updated: "2000-01-01T00:00:00Z".parse().unwrap(), - base_version: 0, - is_placeholder: false, - need_sync: false, - size: 0, + base: FileStat { + id: VlobID::default(), + created: "2000-01-01T00:00:00Z".parse().unwrap(), + updated: "2000-01-01T00:00:00Z".parse().unwrap(), + base_version: 0, + is_placeholder: false, + need_sync: false, + size: 0, + }, })) } else { // Fallback to real lookup From 9316eabec6846d7c80ed9fc705583d4e83af2826 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 20 Dec 2024 16:39:37 +0100 Subject: [PATCH 13/19] Rework mountpoint logging --- .../src/unix/filesystem.rs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index 530e023ca7b..f22b634c354 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -310,7 +310,7 @@ impl fuser::Filesystem for Filesystem { name: &std::ffi::OsStr, reply: fuser::ReplyEntry, ) { - log::debug!("[FUSE] lookup(parent: {:#x?}, name: {:?})", parent, name); + log::debug!("[FUSE] lookup(parent: {parent:#x?}, name: {name:?})"); let reply = reply_on_drop_guard!(reply, fuser::ReplyEntry); let uid = req.uid(); @@ -416,7 +416,7 @@ impl fuser::Filesystem for Filesystem { _fh: Option, reply: fuser::ReplyAttr, ) { - log::debug!("[FUSE] getattr(ino: {:#x?})", ino); + log::debug!("[FUSE] getattr(ino: {ino:#x?}, _fh: {_fh:#x?})"); let reply = reply_on_drop_guard!(reply, fuser::ReplyAttr); let uid = req.uid(); @@ -808,12 +808,7 @@ impl fuser::Filesystem for Filesystem { flags: i32, reply: fuser::ReplyCreate, ) { - log::debug!( - "[FUSE] create(parent: {:#x?}, name: {:?}, flags: {:#x?})", - parent, - name, - flags - ); + log::debug!("[FUSE] create(parent: {parent:#x?}, name: {name:?}, flags: {flags:#x?}, _mode: {_mode:o}, _umask: {_umask:o})",); let reply = reply_on_drop_guard!(reply, fuser::ReplyCreate); let uid = req.uid(); @@ -951,15 +946,8 @@ impl fuser::Filesystem for Filesystem { reply: fuser::ReplyAttr, ) { log::debug!( - "[FUSE] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ - gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", - ino, - mode, - uid, - gid, - size, - fh, - flags + "[FUSE] setattr(ino: {ino:#x?}, mode: {mode:x?}, uid: {uid:?}, \ + gid: {gid:?}, size: {size:?}, fh: {fh:?}, flags: {flags:x?})", ); let reply = reply_on_drop_guard!(reply, fuser::ReplyAttr); let uid = req.uid(); From 3dccfa0388790e792a8cfdb91cd408bf74f23494 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 20 Dec 2024 16:48:34 +0100 Subject: [PATCH 14/19] Trace openend file with their fd --- .../crates/client/src/workspace/transactions/open_file.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libparsec/crates/client/src/workspace/transactions/open_file.rs b/libparsec/crates/client/src/workspace/transactions/open_file.rs index fc59036e3bd..ba5d9686dc9 100644 --- a/libparsec/crates/client/src/workspace/transactions/open_file.rs +++ b/libparsec/crates/client/src/workspace/transactions/open_file.rs @@ -118,7 +118,7 @@ pub async fn open_file( } // Special case if the file doesn't exist but we are allowed to create it Err(ResolvePathError::EntryNotFound) if options.create || options.create_new => { - let outcome = super::create_file(ops, path).await; + let outcome = super::create_file(ops, path.clone()).await; outcome.or_else(|err| match err { // Concurrent operation has created the file in the meantime WorkspaceCreateFileError::EntryExists { entry_id } => Ok(entry_id), @@ -175,7 +175,9 @@ pub async fn open_file( } }; - open_file_by_id(ops, entry_id, options).await + open_file_by_id(ops, entry_id, options) + .await + .inspect(|(fd, _)| log::trace!("Opened file {path} wit fd {fd:?}")) } pub async fn open_file_by_id( From 93a20bddf039b8d6a24b027afccd5d3571175143 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Thu, 9 Jan 2025 16:45:43 +0100 Subject: [PATCH 15/19] Inspect return value of `lookup` and `getattr` --- .../crates/platform_mountpoint/src/unix/filesystem.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index f22b634c354..28ff79e2bab 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -332,7 +332,7 @@ impl fuser::Filesystem for Filesystem { let path = { let inodes_guard = inodes.lock().expect("mutex is poisoned"); let parent_path = inodes_guard.get_path_or_panic(parent); - parent_path.join(name) + parent_path.join(name.clone()) }; let outcome = { @@ -356,7 +356,10 @@ impl fuser::Filesystem for Filesystem { } }; - match outcome { + match outcome + .inspect(|stat| log::debug!("lookup({parent}, {name:?}) => {stat:?}")) + .inspect_err(|e| log::warn!("lookup({parent}, {name:?}) result in error: {e:?}")) + { Ok(stat) => { let mut inodes_guard = inodes.lock().expect("mutex is poisoned"); let inode = inodes_guard.insert_path(path); From 33dae77cfd5cb782b1429957e9cfcf6260c4e403 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 10 Jan 2025 12:08:13 +0100 Subject: [PATCH 16/19] Generate entry attr in map so it's displayed in the log below it --- .../platform_mountpoint/src/unix/filesystem.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index 28ff79e2bab..6387a3333c0 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -357,18 +357,15 @@ impl fuser::Filesystem for Filesystem { }; match outcome + .map(|stat| { + let mut inodes_guard = inodes.lock().expect("mutex is poisoned"); + let inode = inodes_guard.insert_path(path); + entry_stat_to_file_attr(stat, inode, uid, gid) + }) .inspect(|stat| log::debug!("lookup({parent}, {name:?}) => {stat:?}")) .inspect_err(|e| log::warn!("lookup({parent}, {name:?}) result in error: {e:?}")) { - Ok(stat) => { - let mut inodes_guard = inodes.lock().expect("mutex is poisoned"); - let inode = inodes_guard.insert_path(path); - reply.manual().entry( - &TTL, - &entry_stat_to_file_attr(stat, inode, uid, gid), - GENERATION, - ) - } + Ok(stat) => reply.manual().entry(&TTL, &stat, GENERATION), Err(err) => match err { WorkspaceStatEntryError::EntryNotFound => reply.manual().error(libc::ENOENT), WorkspaceStatEntryError::Offline => reply.manual().error(libc::EHOSTUNREACH), From d4d7184409e5c43dcc1b8e1fb599e930fb52b152 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Wed, 15 Jan 2025 09:47:53 +0100 Subject: [PATCH 17/19] Set blocksize to 512 KB And use the constant in `statfs` --- .../crates/platform_mountpoint/src/unix/filesystem.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index 6387a3333c0..97fd1a76850 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -31,7 +31,8 @@ const TTL: std::time::Duration = std::time::Duration::ZERO; /// must assign a new, previously unused generation number to the inode at the /// same time. const GENERATION: u64 = 0; -const BLOCK_SIZE: u64 = 512; +/// The default block size, set to 512 KB. +const BLOCK_SIZE: u64 = 512 * 1024; /// Default permissions for files and folders. /// Equivalent to `chmod` flags `all=,u=rwx`. const PERMISSIONS: u16 = 0o700; @@ -403,9 +404,11 @@ impl fuser::Filesystem for Filesystem { 2 * 1024u64.pow(2), // 2 MBlocks is 1 TB inode_used as u64, inode_remaining as u64, - 512 * 1024, // 512 KB, i.e the default block size - 255, // 255 bytes as maximum length for filenames - 512 * 1024, // 512 KB, i.e the default block size + BLOCK_SIZE as u32, + 255, // 255 bytes as maximum length for filenames + // Fragment size (frsize) is set to the same size of block size + // (bsize) since we do not handle elements smallar + BLOCK_SIZE as u32, ) } From f3ed3c3bf5897cc357f5410b3b9f3810876a5fef Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Wed, 15 Jan 2025 11:16:35 +0100 Subject: [PATCH 18/19] Add log to statfs to see the returned value --- .../platform_mountpoint/src/unix/filesystem.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index 97fd1a76850..05021b8b850 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -390,18 +390,25 @@ impl fuser::Filesystem for Filesystem { fn statfs(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyStatfs) { log::debug!("[FUSE] statfs(ino: {:#x?})", ino); + // 2 MBlocks is 1 TB + const BLOCK_AVAILABLE: u64 = 2 * 1024u64.pow(2); let (inode_used, inode_remaining) = { let guard = self.inodes.lock().expect("mutex is poisoned"); guard.usage() }; + // We currently do not provide a dynamic value for the used block. + // We set the value to 0 for no block used + let block_used = 0; + + log::trace!("Statfs {{ inode: Inode {{ used: {inode_used}, remaining: {inode_remaining} }}, block: {{ used: {block_used}, available: {BLOCK_AVAILABLE}, size: {BLOCK_SIZE} }} }}"); // We have currently no way of easily getting the size of workspace // Also, the total size of a workspace is not limited // For the moment let's settle on 0 MB used for 1 TB available reply.statfs( - 0, // 0 for no block used - 2 * 1024u64.pow(2), // 2 MBlocks is 1 TB - 2 * 1024u64.pow(2), // 2 MBlocks is 1 TB + block_used, + BLOCK_AVAILABLE - block_used, + BLOCK_AVAILABLE, inode_used as u64, inode_remaining as u64, BLOCK_SIZE as u32, From c0904c541dcca37d0d64ec5aa02c47f5af9ddf02 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Wed, 15 Jan 2025 11:35:58 +0100 Subject: [PATCH 19/19] [mountpoint] Add trace for returned values --- .../src/unix/filesystem.rs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs index 05021b8b850..106e9d9ca1f 100644 --- a/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs +++ b/libparsec/crates/platform_mountpoint/src/unix/filesystem.rs @@ -842,7 +842,7 @@ impl fuser::Filesystem for Filesystem { let inodes_guard = inodes.lock().expect("mutex is poisoned"); inodes_guard.get_path_or_panic(parent) }; - let path = parent_path.into_child(name); + let path = parent_path.into_child(name.clone()); let options = { let mut options = OpenOptions { @@ -925,14 +925,12 @@ impl fuser::Filesystem for Filesystem { }; } }; + let stat = file_stat_to_file_attr(stat, inode, uid, gid); - reply.manual().created( - &TTL, - &file_stat_to_file_attr(stat, inode, uid, gid), - GENERATION, - fd.0.into(), - open_flags, - ); + log::trace!("create({parent}, {name:?}) => {stat:?}"); + reply + .manual() + .created(&TTL, &stat, GENERATION, fd.0.into(), open_flags); }); } @@ -1082,9 +1080,9 @@ impl fuser::Filesystem for Filesystem { } } - reply - .manual() - .attr(&TTL, &file_stat_to_file_attr(stat, ino, uid, gid)); + let stat = file_stat_to_file_attr(stat, ino, uid, gid); + log::trace!("setattr({ino:#x?}, ..) => {stat:?}"); + reply.manual().attr(&TTL, &stat); }); return; @@ -1145,6 +1143,10 @@ impl fuser::Filesystem for Filesystem { let offset = u64::try_from(offset).expect("Offset is negative"); match ops.fd_read(fd, offset, size as u64, &mut buf).await { Ok(_) => { + log::trace!( + "read({ino}, offset: {offset}, size: {size}): Read {rsize} bytes", + rsize = buf.len() + ); reply.manual().data(&buf); } Err(err) => match err { @@ -1193,6 +1195,10 @@ impl fuser::Filesystem for Filesystem { let offset = u64::try_from(offset).expect("Offset is negative"); match ops.fd_write(fd, offset, &data).await { Ok(written) => { + log::trace!( + "write({ino}, offset: {offset}, size: {size}): Written {written} bytes", + size = data.len() + ); reply.manual().written(written as u32); } Err(err) => match err {