From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <pve-devel-bounces@lists.proxmox.com>
Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9])
	by lore.proxmox.com (Postfix) with ESMTPS id 5CE241FF164
	for <inbox@lore.proxmox.com>; Fri,  9 May 2025 14:11:24 +0200 (CEST)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
	by firstgate.proxmox.com (Proxmox) with ESMTP id 716D53C6ED;
	Fri,  9 May 2025 14:11:37 +0200 (CEST)
From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Date: Fri,  9 May 2025 14:09:14 +0200
Message-ID: <20250509121007.1430080-3-c.heiss@proxmox.com>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <20250509121007.1430080-1-c.heiss@proxmox.com>
References: <20250509121007.1430080-1-c.heiss@proxmox.com>
MIME-Version: 1.0
X-SPAM-LEVEL: Spam detection results:  0
 AWL 0.030 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
Subject: [pve-devel] [PATCH installer 2/5] chroot: replace clap with
 pico-args for command argument parsing
X-BeenThere: pve-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox VE development discussion <pve-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pve-devel/>
List-Post: <mailto:pve-devel@lists.proxmox.com>
List-Help: <mailto:pve-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=subscribe>
Reply-To: Proxmox VE development discussion <pve-devel@lists.proxmox.com>
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: pve-devel-bounces@lists.proxmox.com
Sender: "pve-devel" <pve-devel-bounces@lists.proxmox.com>

Drops clap for pico-args, as the former is pretty heavy-weight and
completely overhauls its API with ~every major release.

As for the resulting binary, there is a ~18% decrease in binary size,
from 2.9M to 2.4MiB (after stripping).

   text    data     bss     dec     hex filename
2743595  274576     424 3018595  2e0f63 proxmox-chroot-clap
2208631  261224     408 2470263  25b177 proxmox-chroot-pico

Further, the dependency tree goes from 40 to 29 dependencies, as well as
the package compile time on my machine (on hot caches), roughly measured
with

`cargo clean && time cargo build --package proxmox-chroot --release`

from ~20s to ~16s, so a nice little change w.r.t. to that aspect too.

No functional changes intended.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
 proxmox-chroot/Cargo.toml  |   6 +-
 proxmox-chroot/src/main.rs | 159 +++++++++++++++++++++++++++----------
 2 files changed, 120 insertions(+), 45 deletions(-)

diff --git a/proxmox-chroot/Cargo.toml b/proxmox-chroot/Cargo.toml
index 7a1390a..c043938 100644
--- a/proxmox-chroot/Cargo.toml
+++ b/proxmox-chroot/Cargo.toml
@@ -9,8 +9,6 @@ homepage = "https://www.proxmox.com"
 
 [dependencies]
 anyhow.workspace = true
-proxmox-installer-common.workspace = true
-serde_json.workspace = true
 regex.workspace = true
-
-clap = { version = "4.0", features = ["derive"] }
+proxmox-installer-common = { workspace = true, features = [ "cli" ] }
+serde_json.workspace = true
diff --git a/proxmox-chroot/src/main.rs b/proxmox-chroot/src/main.rs
index ef339ed..037f079 100644
--- a/proxmox-chroot/src/main.rs
+++ b/proxmox-chroot/src/main.rs
@@ -1,13 +1,20 @@
+//! Tool to help prepare a successfully installed target environment for
+//! chroot'ing.
+//!
+//! Can also be used in the rescue environment for mounting an existing system.
+
+#![forbid(unsafe_code)]
+
 use std::{
-    fs, io,
+    env, fs, io,
     path::{self, Path, PathBuf},
-    process::Command,
+    process::{self, Command},
+    str::FromStr,
 };
 
 use anyhow::{Result, bail};
-use clap::{Args, Parser, Subcommand, ValueEnum};
 use proxmox_installer_common::{
-    RUNTIME_DIR,
+    RUNTIME_DIR, cli,
     options::FsType,
     setup::{InstallConfig, SetupInfo},
 };
@@ -18,46 +25,87 @@ static BINDMOUNTS: [&str; 4] = ["dev", "proc", "run", "sys"];
 const TARGET_DIR: &str = "/target";
 const ZPOOL_NAME: &str = "rpool";
 
-/// Helper tool to prepare everything to `chroot` into an installation
-#[derive(Parser, Debug)]
-#[command(author, version, about, long_about = None)]
-struct Cli {
-    #[command(subcommand)]
-    command: Commands,
-}
-
-#[derive(Subcommand, Debug)]
-enum Commands {
-    Prepare(CommandPrepare),
-    Cleanup(CommandCleanup),
-}
-
-/// Mount the root file system and bind mounts in preparation to chroot into the installation
-#[derive(Args, Debug)]
-struct CommandPrepare {
+/// Arguments for the `prepare` command.
+struct CommandPrepareArgs {
     /// Filesystem used for the installation. Will try to automatically detect it after a
     /// successful installation.
-    #[arg(short, long, value_enum)]
     filesystem: Option<Filesystems>,
 
-    /// Numerical ID of `rpool` ZFS pool to import. Needed if multiple pools of name `rpool` are present.
-    #[arg(long)]
+    /// Numerical ID of the `rpool` ZFS pool to import. Needed if multiple pools of name `rpool`
+    /// are present.
     rpool_id: Option<u64>,
 
     /// UUID of the BTRFS file system to mount. Needed if multiple BTRFS file systems are present.
-    #[arg(long)]
     btrfs_uuid: Option<String>,
 }
 
-/// Unmount everything. Use once done with chroot.
-#[derive(Args, Debug)]
-struct CommandCleanup {
+impl cli::Subcommand for CommandPrepareArgs {
+    fn parse(args: &mut cli::Arguments) -> Result<Self> {
+        Ok(Self {
+            filesystem: args.opt_value_from_str(["-f", "--filesystem"])?,
+            rpool_id: args.opt_value_from_fn("--rpool-id", str::parse)?,
+            btrfs_uuid: args.opt_value_from_str("--btrfs-uuid")?,
+        })
+    }
+
+    fn print_usage() {
+        eprintln!(
+            r#"Mount the root file system and bind mounts in preparation to chroot into the installation.
+
+USAGE:
+  {} prepare <OPTIONS>
+
+OPTIONS:
+  -f, --filesystem <FILESYSTEM>  Filesystem used for the installation. Will try to automatically detect it after a successful installation. [possible values: zfs, ext4, xfs, btrfs]
+      --rpool-id <RPOOL_ID>      Numerical ID of `rpool` ZFS pool to import. Needed if multiple pools of name `rpool` are present.
+      --btrfs-uuid <BTRFS_UUID>  UUID of the BTRFS file system to mount. Needed if multiple BTRFS file systems are present.
+  -h, --help                       Print this help
+  -V, --version                    Print version
+"#,
+            env!("CARGO_PKG_NAME")
+        );
+    }
+
+    fn run(&self) -> Result<()> {
+        prepare(self)
+    }
+}
+
+/// Arguments for the `cleanup` command.
+struct CommandCleanupArgs {
     /// Filesystem used for the installation. Will try to automatically detect it by default.
-    #[arg(short, long, value_enum)]
     filesystem: Option<Filesystems>,
 }
 
-#[derive(Copy, Clone, Debug, ValueEnum)]
+impl cli::Subcommand for CommandCleanupArgs {
+    fn parse(args: &mut cli::Arguments) -> Result<Self> {
+        Ok(Self {
+            filesystem: args.opt_value_from_str(["-f", "--filesystem"])?,
+        })
+    }
+
+    fn print_usage() {
+        eprintln!(
+            r#"Unmount everything. Use once done with the chroot.
+
+USAGE:
+  {} cleanup <OPTIONS>
+
+OPTIONS:
+  -f, --filesystem <FILESYSTEM>    Filesystem used for the installation. Will try to automatically detect it after a successful installation. [possible values: zfs, ext4, xfs, btrfs]
+  -h, --help                       Print this help
+  -V, --version                    Print version
+"#,
+            env!("CARGO_PKG_NAME")
+        );
+    }
+
+    fn run(&self) -> Result<()> {
+        cleanup(self)
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
 enum Filesystems {
     Zfs,
     Ext4,
@@ -76,19 +124,48 @@ impl From<FsType> for Filesystems {
     }
 }
 
-fn main() {
-    let args = Cli::parse();
-    let res = match &args.command {
-        Commands::Prepare(args) => prepare(args),
-        Commands::Cleanup(args) => cleanup(args),
-    };
-    if let Err(err) = res {
-        eprintln!("{err:#}");
-        std::process::exit(1);
+impl FromStr for Filesystems {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self> {
+        match s {
+            "ext4" => Ok(Filesystems::Ext4),
+            "xfs" => Ok(Filesystems::Xfs),
+            _ if s.starts_with("zfs") => Ok(Filesystems::Zfs),
+            _ if s.starts_with("btrfs") => Ok(Filesystems::Btrfs),
+            _ => bail!("unknown filesystem"),
+        }
     }
 }
 
-fn prepare(args: &CommandPrepare) -> Result<()> {
+fn main() -> process::ExitCode {
+    cli::run(cli::AppInfo {
+        global_help: &format!(
+            r#"Helper tool to set up a `chroot` into an existing installation.
+
+USAGE:
+  {} <COMMAND> <OPTIONS>
+
+COMMANDS:
+  prepare            Mount the root file system and bind mounts in preparation to chroot into the installation.
+  cleanup            Unmount everything. Use once done with the chroot.
+
+GLOBAL OPTIONS:
+  -h, --help         Print help
+  -V, --version      Print version information
+"#,
+            env!("CARGO_PKG_NAME")
+        ),
+        on_command: |s, args| match s {
+            Some("prepare") => cli::handle_command::<CommandPrepareArgs>(args),
+            Some("cleanup") => cli::handle_command::<CommandCleanupArgs>(args),
+            Some(s) => bail!("unknown subcommand '{s}'"),
+            None => bail!("subcommand required"),
+        },
+    })
+}
+
+fn prepare(args: &CommandPrepareArgs) -> Result<()> {
     let fs = get_fs(args.filesystem)?;
 
     fs::create_dir_all(TARGET_DIR)?;
@@ -108,7 +185,7 @@ fn prepare(args: &CommandPrepare) -> Result<()> {
     Ok(())
 }
 
-fn cleanup(args: &CommandCleanup) -> Result<()> {
+fn cleanup(args: &CommandCleanupArgs) -> Result<()> {
     let fs = get_fs(args.filesystem)?;
 
     if let Err(e) = bind_umount() {
-- 
2.49.0



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel