From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id F18CD1FF0E5 for ; Wed, 12 Aug 2026 16:40:53 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 71A8C21594; Wed, 12 Aug 2026 16:40:51 +0200 (CEST) From: Shan Shaji To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox v3 3/3] pbs-api-types: zfs: define schema for recordsize Date: Wed, 12 Aug 2026 16:40:29 +0200 Message-ID: <20260812144029.129970-4-s.shaji@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260812144029.129970-1-s.shaji@proxmox.com> References: <20260812144029.129970-1-s.shaji@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1786545632550 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.678 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust 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: U6RACGYUJR6BWQNVBPTBNBIPKHMOPEYK X-Message-ID-Hash: U6RACGYUJR6BWQNVBPTBNBIPKHMOPEYK X-MailFrom: s.shaji@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 Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: add schema and verifcation function for recordsize to check against allowed zfs constraints. Without the verification function the dataset will be created using the default 128k recordsize. Reviewed-by: Nicolas Frey Tested-by: Nicolas Frey Signed-off-by: Shan Shaji --- changes since v2: * style nit: use let-else instead of if let-else. pbs-api-types/src/zfs.rs | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/pbs-api-types/src/zfs.rs b/pbs-api-types/src/zfs.rs index 2cfbd403..b2e1baf6 100644 --- a/pbs-api-types/src/zfs.rs +++ b/pbs-api-types/src/zfs.rs @@ -1,3 +1,4 @@ +use anyhow::{Error, bail}; use serde::{Deserialize, Serialize}; #[cfg(feature = "enum-fallback")] @@ -6,6 +7,7 @@ use proxmox_schema::*; const_regex! { pub ZPOOL_NAME_REGEX = r"^[a-zA-Z][a-z0-9A-Z\-_.:]+$"; + ZFS_RECORD_SIZE_REGEX = r"^(?i)([1-9][0-9]*)([km])?$"; } pub const ZFS_ASHIFT_SCHEMA: Schema = IntegerSchema::new("Pool sector size exponent.") @@ -18,6 +20,39 @@ pub const ZPOOL_NAME_SCHEMA: Schema = StringSchema::new("ZFS Pool Name") .format(&ApiStringFormat::Pattern(&ZPOOL_NAME_REGEX)) .schema(); +fn verify_zfs_recordsize(value: &str) -> Result<(), Error> { + let Some(caps) = ZFS_RECORD_SIZE_REGEX.captures(value) else { + bail!("Invalid format. Use numbers with optional k or m suffix (e.g., 16k).") + }; + + let range_error = "Value must be a power of 2 between 512 and 16m"; + + let Ok(number) = caps[1].parse::() else { + bail!(range_error); + }; + + let multiplier = match caps.get(2).map(|v| v.as_str()) { + Some(suffix) if suffix.eq_ignore_ascii_case("k") => 1024, + Some(suffix) if suffix.eq_ignore_ascii_case("m") => 1024 * 1024, + _ => 1, + }; + + let bytes = number + .checked_mul(multiplier) + .filter(|&b| b.is_power_of_two() && (512..=16 * 1024 * 1024).contains(&b)); + + if bytes.is_none() { + bail!(range_error); + } + + Ok(()) +} + +pub const ZFS_RECORD_SIZE_SCHEMA: Schema = + StringSchema::new("ZFS Recordsize. Use numbers with optional k or m suffix (e.g., 128k).") + .format(&ApiStringFormat::VerifyFn(verify_zfs_recordsize)) + .schema(); + #[api(default: "On")] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] @@ -84,3 +119,36 @@ pub struct ZpoolListItem { /// ZFS deduplication ratio pub dedup: f64, } + +#[cfg(test)] +mod tests { + + use super::verify_zfs_recordsize; + + #[test] + fn test_zfs_record_size_verify_fn() { + let format_error = "Invalid format. Use numbers with optional k or m suffix (e.g., 16k)."; + let range_error = "Value must be a power of 2 between 512 and 16m"; + + let valid_inputs = ["512", "1024", "16m", "16384k", "16777216"]; + for input in valid_inputs { + assert!(verify_zfs_recordsize(input).is_ok()); + } + + let range_error_inputs = ["31m", "256", "31m", "32m", "4294967297"]; + for input in range_error_inputs { + assert_eq!( + verify_zfs_recordsize(input).unwrap_err().to_string(), + range_error + ); + } + + let format_error_inputs = ["0", "k12", "16g", "1.5m", "-512", "0512"]; + for input in format_error_inputs { + assert_eq!( + verify_zfs_recordsize(input).unwrap_err().to_string(), + format_error + ); + } + } +} -- 2.47.3