From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 6FDD51FF141 for ; Fri, 13 Feb 2026 10:41:06 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 467A83209B; Fri, 13 Feb 2026 10:41:52 +0100 (CET) From: Kefu Chai To: pve-devel@lists.proxmox.com Subject: [PATCH pve-cluster 02/14 v2] pmxcfs-rs: add pmxcfs-api-types crate Date: Fri, 13 Feb 2026 17:33:39 +0800 Message-ID: <20260213094119.2379288-3-k.chai@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260213094119.2379288-1-k.chai@proxmox.com> References: <20260213094119.2379288-1-k.chai@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1770975698208 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.127 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: X2WMM55RF7WAZTTFG62EE4HYYCZ4QQ6X X-Message-ID-Hash: X2WMM55RF7WAZTTFG62EE4HYYCZ4QQ6X X-MailFrom: k.chai@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add pmxcfs-api-types crate which provides foundational types: - PmxcfsError: Error type with errno mapping for FUSE operations - FuseMessage: Filesystem operation messages - KvStoreMessage: Status synchronization messages - ApplicationMessage: Wrapper enum for both message types - VmType: VM type enum (Qemu, Lxc) All other crates will depend on these shared type definitions. Signed-off-by: Kefu Chai --- src/pmxcfs-rs/Cargo.toml | 10 +- src/pmxcfs-rs/pmxcfs-api-types/Cargo.toml | 19 +++ src/pmxcfs-rs/pmxcfs-api-types/README.md | 88 ++++++++++++++ src/pmxcfs-rs/pmxcfs-api-types/src/error.rs | 122 ++++++++++++++++++++ src/pmxcfs-rs/pmxcfs-api-types/src/lib.rs | 67 +++++++++++ 5 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 src/pmxcfs-rs/pmxcfs-api-types/Cargo.toml create mode 100644 src/pmxcfs-rs/pmxcfs-api-types/README.md create mode 100644 src/pmxcfs-rs/pmxcfs-api-types/src/error.rs create mode 100644 src/pmxcfs-rs/pmxcfs-api-types/src/lib.rs diff --git a/src/pmxcfs-rs/Cargo.toml b/src/pmxcfs-rs/Cargo.toml index d109221fb..13407f402 100644 --- a/src/pmxcfs-rs/Cargo.toml +++ b/src/pmxcfs-rs/Cargo.toml @@ -1,6 +1,7 @@ # Workspace root for pmxcfs Rust implementation [workspace] members = [ + "pmxcfs-api-types", # Shared types and error definitions ] resolver = "2" @@ -13,7 +14,14 @@ repository = "https://git.proxmox.com/?p=pve-cluster.git" rust-version = "1.85" [workspace.dependencies] -# Dependencies will be added incrementally as crates are introduced +# Internal workspace dependencies +pmxcfs-api-types = { path = "pmxcfs-api-types" } + +# Error handling +thiserror = "1.0" + +# System integration +libc = "0.2" [workspace.lints.clippy] uninlined_format_args = "warn" diff --git a/src/pmxcfs-rs/pmxcfs-api-types/Cargo.toml b/src/pmxcfs-rs/pmxcfs-api-types/Cargo.toml new file mode 100644 index 000000000..cdce7951a --- /dev/null +++ b/src/pmxcfs-rs/pmxcfs-api-types/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pmxcfs-api-types" +description = "Shared types and error definitions for pmxcfs" + +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +# Error handling +thiserror.workspace = true + +# System integration +libc.workspace = true diff --git a/src/pmxcfs-rs/pmxcfs-api-types/README.md b/src/pmxcfs-rs/pmxcfs-api-types/README.md new file mode 100644 index 000000000..ddcd4e478 --- /dev/null +++ b/src/pmxcfs-rs/pmxcfs-api-types/README.md @@ -0,0 +1,88 @@ +# pmxcfs-api-types + +**Shared Types and Error Definitions** for pmxcfs. + +This crate provides common types and error definitions used across all pmxcfs crates. + +## Overview + +The crate contains: +- **Error types**: `PmxcfsError` with errno mapping for FUSE +- **Shared types**: `MemberInfo`, `NodeSyncInfo`, `VmType`, `VmEntry` + +## Error Types + +### PmxcfsError + +Type-safe error enum with automatic errno conversion. + +### errno Mapping + +Errors automatically convert to POSIX errno values for FUSE. + +| Error | errno | Value | Note | +|-------|-------|-------|------| +| `NotFound(_)` | `ENOENT` | 2 | File or directory not found | +| `PermissionDenied` | `EACCES` | 13 | File permission denied | +| `AlreadyExists(_)` | `EEXIST` | 17 | File already exists | +| `NotADirectory(_)` | `ENOTDIR` | 20 | Not a directory | +| `IsADirectory(_)` | `EISDIR` | 21 | Is a directory | +| `DirectoryNotEmpty(_)` | `ENOTEMPTY` | 39 | Directory not empty | +| `InvalidArgument(_)` | `EINVAL` | 22 | Invalid argument | +| `InvalidPath(_)` | `EINVAL` | 22 | Invalid path | +| `FileTooLarge` | `EFBIG` | 27 | File too large | +| `ReadOnlyFilesystem` | `EROFS` | 30 | Read-only filesystem | +| `NoQuorum` | `EACCES` | 13 | No cluster quorum | +| `Lock(_)` | `EAGAIN` | 11 | Lock unavailable, try again | +| `Timeout` | `ETIMEDOUT` | 110 | Operation timed out | +| `Io(e)` | varies | varies | OS error code or `EIO` | +| Others* | `EIO` | 5 | Internal error | + +*Others include: `Database`, `Fuse`, `Cluster`, `Corosync`, `Configuration`, `System`, `Ipc` + +## Shared Types + +### MemberInfo + +Cluster member information. + +### NodeSyncInfo + +DFSM synchronization state. + +### VmType + +VM/CT type enum (Qemu or Lxc). + +### VmEntry + +VM/CT entry for vmlist. + +## C to Rust Mapping + +### Error Handling + +**C Version (cfs-utils.h):** +- Return codes: `0` = success, negative = error +- errno-based error reporting +- Manual error checking everywhere + +**Rust Version:** +- `Result` type + +## Known Issues / TODOs + +### Missing Features +- None identified + +### Compatibility +- **errno values**: Match POSIX standards + +## References + +### C Implementation +- `src/pmxcfs/cfs-utils.h` - Utility types and error codes + +### Related Crates +- **pmxcfs-dfsm**: Uses shared types for cluster sync +- **pmxcfs-memdb**: Uses PmxcfsError for database operations diff --git a/src/pmxcfs-rs/pmxcfs-api-types/src/error.rs b/src/pmxcfs-rs/pmxcfs-api-types/src/error.rs new file mode 100644 index 000000000..dcb5d1e9e --- /dev/null +++ b/src/pmxcfs-rs/pmxcfs-api-types/src/error.rs @@ -0,0 +1,122 @@ +use thiserror::Error; + +/// Error types for pmxcfs operations +#[derive(Error, Debug)] +pub enum PmxcfsError { + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + #[error("Database error: {0}")] + Database(String), + + #[error("FUSE error: {0}")] + Fuse(String), + + #[error("Cluster error: {0}")] + Cluster(String), + + #[error("Corosync error: {0}")] + Corosync(String), + + #[error("Configuration error: {0}")] + Configuration(String), + + #[error("System error: {0}")] + System(String), + + #[error("IPC error: {0}")] + Ipc(String), + + #[error("Permission denied")] + PermissionDenied, + + #[error("Not found: {0}")] + NotFound(String), + + #[error("Already exists: {0}")] + AlreadyExists(String), + + #[error("Invalid argument: {0}")] + InvalidArgument(String), + + #[error("Not a directory: {0}")] + NotADirectory(String), + + #[error("Is a directory: {0}")] + IsADirectory(String), + + #[error("Directory not empty: {0}")] + DirectoryNotEmpty(String), + + #[error("No quorum")] + NoQuorum, + + #[error("Read-only filesystem")] + ReadOnlyFilesystem, + + #[error("File too large")] + FileTooLarge, + + #[error("Filesystem full")] + FilesystemFull, + + #[error("Lock error: {0}")] + Lock(String), + + #[error("Timeout")] + Timeout, + + #[error("Invalid path: {0}")] + InvalidPath(String), +} + +impl PmxcfsError { + /// Convert error to errno value for FUSE operations + pub fn to_errno(&self) -> i32 { + match self { + // File/directory errors + PmxcfsError::NotFound(_) => libc::ENOENT, + PmxcfsError::AlreadyExists(_) => libc::EEXIST, + PmxcfsError::NotADirectory(_) => libc::ENOTDIR, + PmxcfsError::IsADirectory(_) => libc::EISDIR, + PmxcfsError::DirectoryNotEmpty(_) => libc::ENOTEMPTY, + PmxcfsError::FileTooLarge => libc::EFBIG, + PmxcfsError::FilesystemFull => libc::ENOSPC, + PmxcfsError::ReadOnlyFilesystem => libc::EROFS, + + // Permission and access errors + // EACCES: Permission denied for file operations (standard POSIX) + // C implementation uses EACCES as default for access/quorum issues + PmxcfsError::PermissionDenied => libc::EACCES, + PmxcfsError::NoQuorum => libc::EACCES, + + // Validation errors + PmxcfsError::InvalidArgument(_) => libc::EINVAL, + PmxcfsError::InvalidPath(_) => libc::EINVAL, + + // Lock errors - use EAGAIN for temporary failures + PmxcfsError::Lock(_) => libc::EAGAIN, + + // Timeout + PmxcfsError::Timeout => libc::ETIMEDOUT, + + // I/O errors with automatic errno extraction + PmxcfsError::Io(e) => match e.raw_os_error() { + Some(errno) => errno, + None => libc::EIO, + }, + + // Fallback to EIO for internal/system errors + PmxcfsError::Database(_) | + PmxcfsError::Fuse(_) | + PmxcfsError::Cluster(_) | + PmxcfsError::Corosync(_) | + PmxcfsError::Configuration(_) | + PmxcfsError::System(_) | + PmxcfsError::Ipc(_) => libc::EIO, + } + } +} + +/// Result type for pmxcfs operations +pub type Result = std::result::Result; diff --git a/src/pmxcfs-rs/pmxcfs-api-types/src/lib.rs b/src/pmxcfs-rs/pmxcfs-api-types/src/lib.rs new file mode 100644 index 000000000..99cafbaa3 --- /dev/null +++ b/src/pmxcfs-rs/pmxcfs-api-types/src/lib.rs @@ -0,0 +1,67 @@ +mod error; + +pub use error::{PmxcfsError, Result}; + +/// Maximum size for status data (matches C implementation) +/// From status.h: #define CFS_MAX_STATUS_SIZE (32 * 1024) +pub const CFS_MAX_STATUS_SIZE: usize = 32 * 1024; + +/// VM/CT types +/// +/// Note: OpenVZ was historically supported (VMTYPE_OPENVZ = 2 in C implementation) +/// but was removed in PVE 4.0 in favor of LXC. Only QEMU and LXC are currently supported. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VmType { + Qemu, + Lxc, +} + +impl VmType { + /// Returns the directory name where config files are stored + pub fn config_dir(&self) -> &'static str { + match self { + VmType::Qemu => "qemu-server", + VmType::Lxc => "lxc", + } + } +} + +impl std::fmt::Display for VmType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VmType::Qemu => write!(f, "qemu"), + VmType::Lxc => write!(f, "lxc"), + } + } +} + +/// VM/CT entry for vmlist +#[derive(Debug, Clone)] +pub struct VmEntry { + pub vmid: u32, + pub vmtype: VmType, + pub node: String, + /// Per-VM version counter (increments when this VM's config changes) + pub version: u32, +} + +/// Information about a cluster member +/// +/// This is a shared type used by both cluster and DFSM modules +#[derive(Debug, Clone)] +pub struct MemberInfo { + pub node_id: u32, + pub pid: u32, + pub joined_at: u64, +} + +/// Node synchronization info for DFSM state sync +/// +/// Used during DFSM synchronization to track which nodes have provided state +#[derive(Debug, Clone)] +pub struct NodeSyncInfo { + pub node_id: u32, + pub pid: u32, + pub state: Option>, + pub synced: bool, +} -- 2.47.3