git: 3b66efc6e9b8 - main - nvmecontrol: add format dry-run support

From: Warner Losh <imp_at_FreeBSD.org>
Date: Mon, 19 Oct 2026 06:24:42 UTC
The branch main has been updated by imp:

URL: https://cgit.FreeBSD.org/src/commit/?id=3b66efc6e9b8999053f11a449056fad5ebc1c1ac

commit 3b66efc6e9b8999053f11a449056fad5ebc1c1ac
Author:     Warner Losh <imp@FreeBSD.org>
AuthorDate: 2026-10-19 04:43:00 +0000
Commit:     Warner Losh <imp@FreeBSD.org>
CommitDate: 2026-10-19 06:22:51 +0000

    nvmecontrol: add format dry-run support
    
    Add -n/--dry-run to resolve and print the Format NVM command without
    submitting it. This lets users verify namespace selection and format
    parameters before making destructive media changes.
    
    Reviewed by:            seuros, chuck
    Differential Revision:  https://reviews.freebsd.org/D59835
---
 sbin/nvmecontrol/format.c      | 22 +++++++++++++++++-----
 sbin/nvmecontrol/nvmecontrol.8 |  5 +++++
 2 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/sbin/nvmecontrol/format.c b/sbin/nvmecontrol/format.c
index ee0ecc34aeaa..ccf0981103c0 100644
--- a/sbin/nvmecontrol/format.c
+++ b/sbin/nvmecontrol/format.c
@@ -59,6 +59,7 @@ static struct options {
 	uint32_t	ses;
 	bool		Eflag;
 	bool		Cflag;
+	bool		dry_run;
 	const char	*dev;
 } opt = {
 	.lbaf = NONE,
@@ -69,6 +70,7 @@ static struct options {
 	.ses = SES_NONE,
 	.Eflag = false,
 	.Cflag = false,
+	.dry_run = false,
 	.dev = NULL,
 };
 
@@ -78,6 +80,8 @@ static const struct opts format_opts[] = {
 	    "Cryptographic erase"),
 	OPT("erase", 'E', arg_none, opt, Eflag,
 	    "User data erase"),
+	OPT("dry-run", 'n', arg_none, opt, dry_run,
+	    "Print the Format NVM command without submitting it"),
 	OPT("lbaf", 'f', arg_uint32, opt, lbaf,
 	    "LBA Format to apply to the media"),
 	OPT("ms", 'm', arg_uint32, opt, ms,
@@ -186,7 +190,7 @@ format(const struct cmd *f, int argc, char *argv[])
 	struct nvme_pt_command		pt;
 	char				*path;
 	const char			*target;
-	uint32_t			nsid, scan_nsid;
+	uint32_t			cdw10, nsid, scan_nsid;
 	int				lbaf, ms, pi, pil, ses, fd;
 	u_int				i;
 	uint32_t			sector_size;
@@ -220,7 +224,7 @@ format(const struct cmd *f, int argc, char *argv[])
 	else
 		ses = opt.ses;
 
-	open_dev(target, &fd, 1, 1);
+	open_dev(target, &fd, !opt.dry_run, 1);
 	get_nsid(fd, &path, &nsid);
 	if (nsid == 0) {
 		nsid = NVME_GLOBAL_NAMESPACE_TAG;
@@ -232,7 +236,7 @@ format(const struct cmd *f, int argc, char *argv[])
 		 * string to get the controller substring and namespace ID.
 		 */
 		close(fd);
-		open_dev(path, &fd, 1, 1);
+		open_dev(path, &fd, !opt.dry_run, 1);
 	}
 	free(path);
 
@@ -316,11 +320,19 @@ format(const struct cmd *f, int argc, char *argv[])
 			pil = 0;
 	}
 
+	cdw10 = (ses << 9) + (pil << 8) + (pi << 5) + (ms << 4) + lbaf;
+	if (opt.dry_run) {
+		printf("Format NVM: opc=%#x nsid=%#010x cdw10=%#010x "
+		    "(lbaf=%d mset=%d pi=%d pil=%d ses=%d)\n",
+		    NVME_OPC_FORMAT_NVM, nsid, cdw10, lbaf, ms, pi, pil, ses);
+		close(fd);
+		exit(0);
+	}
+
 	memset(&pt, 0, sizeof(pt));
 	pt.cmd.opc = NVME_OPC_FORMAT_NVM;
 	pt.cmd.nsid = htole32(nsid);
-	pt.cmd.cdw10 = htole32((ses << 9) + (pil << 8) + (pi << 5) +
-	    (ms << 4) + lbaf);
+	pt.cmd.cdw10 = htole32(cdw10);
 
 	if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
 		err(EX_IOERR, "format request failed");
diff --git a/sbin/nvmecontrol/nvmecontrol.8 b/sbin/nvmecontrol/nvmecontrol.8
index 793016652e73..19015a8c90df 100644
--- a/sbin/nvmecontrol/nvmecontrol.8
+++ b/sbin/nvmecontrol/nvmecontrol.8
@@ -153,6 +153,7 @@
 .Aq Ar device-id
 .Nm
 .Ic format
+.Op Fl n
 .Op Fl f Ar fmt
 .Op Fl s Ar sector-size
 .Op Fl m Ar mset
@@ -505,6 +506,10 @@ Use Extended Data Structure.
 Format either specified namespace, or all namespaces of specified controller,
 using specified parameters:
 .Bl -tag -width 8n
+.It Fl n , Fl Fl dry-run
+Print the resolved Format NVM command without submitting it to the controller.
+Identify commands needed to resolve and validate the format parameters are
+still performed.
 .It Fl f Ar fmt
 The index
 .Ar fmt