diff mbox series

[v2] mmc-utils: implemented CMD42 locking/unlocking

Message ID 20240531182517.929498-1-linux-mmc@danman.eu
State New
Headers show
Series [v2] mmc-utils: implemented CMD42 locking/unlocking | expand

Commit Message

Daniel Kucera May 31, 2024, 6:25 p.m. UTC
From: Daniel Kucera <linux-mmc@danman.eu>

Implemented locking/unlocking using CMD42 according to Micron
Technical Note

original link https://media-www.micron.com/-/media/client/global/documents/products/technical-note/sd-cards/tnsd01_enable_sd_lock_unlock_in_linux.pdf?rev=03f03a6bc0f8435fafa93a8fc8e88988
currently available at https://github.com/danielkucera/esp32-sdcard/blob/master/tnsd01_enable_sd_lock_unlock_in_linux.pdf

Signed-off-by: Daniel Kucera <linux-mmc@danman.eu>
---
 mmc.c      |  12 ++++
 mmc.h      |  10 +++
 mmc_cmds.c | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 mmc_cmds.h |   1 +
 4 files changed, 212 insertions(+)

Comments

Avri Altman June 7, 2024, 8:01 a.m. UTC | #1
> From: Daniel Kucera <linux-mmc@danman.eu>
> 
> Implemented locking/unlocking using CMD42 according to Micron Technical Note
> 
> original link https://media-www.micron.com/-
> /media/client/global/documents/products/technical-note/sd-
> cards/tnsd01_enable_sd_lock_unlock_in_linux.pdf?rev=03f03a6bc0f8435fafa93a
> 8fc8e88988
> currently available at https://github.com/danielkucera/esp32-
> sdcard/blob/master/tnsd01_enable_sd_lock_unlock_in_linux.pdf
> 
> Signed-off-by: Daniel Kucera <linux-mmc@danman.eu>
> ---
>  mmc.c      |  12 ++++
>  mmc.h      |  10 +++
>  mmc_cmds.c | 189
> +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  mmc_cmds.h |   1 +
>  4 files changed, 212 insertions(+)
> 
> diff --git a/mmc.c b/mmc.c
> index bc8f74e..38e3e72 100644
> --- a/mmc.c
> +++ b/mmc.c
> @@ -250,6 +250,18 @@ static struct Command commands[] = {
>                 "be 1.",
>         NULL
>         },
> +       { do_lock_unlock, -3,
> +       "cmd42", "<parameter> " "<device> " "[password] " "[new_password]\n"
> +               "Usage: mmc cmd42 <s|c|l|u|e> <device> [password]
> [new_password]\n"
> +               "<password> can be up to 16 character plaintext or hex string starting
> with 0x\n"
> +               "s\tset password\n"
> +               "c\tclear password\n"
> +               "l\tlock\n"
> +               "sl\tset password and lock\n"
> +               "u\tunlock\n"
> +               "e\tforce erase\n",
> +       NULL
> +       },
>         { do_softreset, -1,
>           "softreset", "<device>\n"
>           "Issues a CMD0 softreset, e.g. for testing if hardware reset for UHS works",
> diff --git a/mmc.h b/mmc.h index 6f1bf3e..ddbb06c 100644
> --- a/mmc.h
> +++ b/mmc.h
> @@ -30,6 +30,7 @@
>  #define MMC_SEND_STATUS                13      /* ac   [31:16] RCA        R1  */
>  #define R1_SWITCH_ERROR   (1 << 7)  /* sx, c */
>  #define MMC_SWITCH_MODE_WRITE_BYTE     0x03    /* Set target to value */
> +#define MMC_SET_BLOCKLEN       16  /* ac [31:0] block len R1 */
>  #define MMC_READ_MULTIPLE_BLOCK  18   /* adtc [31:0] data addr   R1  */
>  #define MMC_SET_BLOCK_COUNT      23   /* adtc [31:0] data addr   R1  */
>  #define MMC_WRITE_BLOCK                24      /* adtc [31:0] data addr        R1  */
> @@ -46,6 +47,7 @@
>                                               [1] Discard Enable
>                                               [0] Identify Write Blocks for
>                                               Erase (or TRIM Enable)  R1b */
> +#define MMC_LOCK_UNLOCK                42  /* adtc R1b */
>  #define MMC_GEN_CMD            56   /* adtc  [31:1] stuff bits.
>                                               [0]: RD/WR1 R1 */
> 
> @@ -70,6 +72,14 @@
>  #define R1_EXCEPTION_EVENT      (1 << 6)        /* sr, a */
>  #define R1_APP_CMD              (1 << 5)        /* sr, c */
> 
> +#define MMC_CMD42_UNLOCK       0x0 /* UNLOCK */
> +#define MMC_CMD42_SET_PWD      0x1 /* SET_PWD */
> +#define MMC_CMD42_CLR_PWD      0x2 /* CLR_PWD */
> +#define MMC_CMD42_LOCK         0x4 /* LOCK */
> +#define MMC_CMD42_ERASE                0x8 /* ERASE */
> +#define MAX_PWD_LENGTH         32 /* max PWDS_LEN: old+new */
> +#define MMC_BLOCK_SIZE         512 /* data blk size for cmd42 */
Those can be privet to mmc_cmds.c

> +
>  /*
>   * EXT_CSD fields
>   */
> diff --git a/mmc_cmds.c b/mmc_cmds.c
> index 936e0c5..dbbaf8d 100644
> --- a/mmc_cmds.c
> +++ b/mmc_cmds.c
> @@ -3245,3 +3245,192 @@ dev_fd_close:
>                 exit(1);
>         return 0;
>  }
> +
> +static int hex_to_bytes(char *input, char *output, int len) {
> +       int ilen = strlen(input);
> +
> +       if (ilen % 2) {
> +               printf("Error: hex string has odd number of characters\n");
> +               exit(1);
> +       }
> +
> +       if (ilen / 2 > len) {
> +               printf("Error: hex string is too long\n");
> +               exit(1);
> +       }
> +
> +       for (int i = 0; i < (ilen / 2); i++) {
> +               if (sscanf(input + 2 * i, "%2hhx", &output[i]) != 1) {
> +                       printf("Error: failed to parse hex string\n");
> +                       exit(1);
> +               }
> +       }
> +
> +       return ilen/2;
> +}
> +
> +static int parse_password(char *pass, char *buf) {
> +       int pwd_len;
> +
> +       if (!strncmp("0x", pass, 2)) {
> +               pwd_len = hex_to_bytes(pass+2, buf, MAX_PWD_LENGTH);
> +       } else {
> +               pwd_len = strlen(pass);
> +               strncpy(buf, pass, MAX_PWD_LENGTH);
> +       }
> +
> +       if (pwd_len > MAX_PWD_LENGTH) {
> +               printf("Password too long.\n");
> +               exit(1);
> +       }
> +
> +       return pwd_len;
> +}
Can the above be eliminated by using parse_bin() instead?
Are you expecting the old & new passwords as a single concatenated sequence?
If you do - this seems wrong to me.

> +
> +int do_lock_unlock(int nargs, char **argv) {
> +       int fd, ret = 0;
> +       char *device;
> +       __u8 data_block[MMC_BLOCK_SIZE] = {};
> +       __u8 data_block_onebyte[1] = {0};
Why is this needed?
Why not use data_block for erase as well?

> +       int block_size = 0;
> +       struct mmc_ioc_multi_cmd *mioc;
> +       struct mmc_ioc_cmd *idata;
> +       int cmd42_para;
> +       char pwd[MAX_PWD_LENGTH*2+1];
Please run checkpatch before re-submitting

Thanks,
Avri

P.S.
But wait - need to wait for your kernel fix to get accepted before re-submitting -
you need to add a reference to it in your commit log.

Thanks,
Avri
> +       int pwd_len = 0, new_pwd_len;
> +       int min_args, max_args;
> +       __u32 r1_response;
> +
> +       min_args = 4;
> +       max_args = 4;
> +
> +       printf("Function: ");
> +       if (!strcmp("s", argv[1])) {
> +               cmd42_para = MMC_CMD42_SET_PWD;
> +               printf("Set password\n");
> +               max_args = 5;
> +       } else if (!strcmp("c", argv[1])) {
> +               cmd42_para = MMC_CMD42_CLR_PWD;
> +               printf("Clear password\n");
> +       } else if (!strcmp("l", argv[1])) {
> +               cmd42_para = MMC_CMD42_LOCK;
> +               printf("Lock the card\n");
> +       } else if (!strcmp("sl", argv[1])) {
> +               cmd42_para = MMC_CMD42_SET_PWD | MMC_CMD42_LOCK;
> +               printf("Set password and lock the card\n");
> +               max_args = 5;
> +       } else if (!strcmp("u", argv[1])) {
> +               cmd42_para = MMC_CMD42_UNLOCK;
> +               printf("Unlock the card\n");
> +       } else if (!strcmp("e", argv[1])) { #ifdef
> +DANGEROUS_COMMANDS_ENABLED
> +               cmd42_para = MMC_CMD42_ERASE;
> +               printf("Force erase (Warning: all card data will be erased together with
> PWD!)\n");
> +               min_args = 3;
> +               max_args = 3;
> +#else
> +               printf("Erase is disabled unless compiled with
> DANGEROUS_COMMANDS_ENABLED\n");
> +               exit(1);
> +#endif
> +       } else {
> +               printf("Invalid parameter:\n" "s\tset password\n"
> +                       "c\tclear password\n" "l\tlock\n"
> +                       "sl\tset password and lock\n" "u\tunlock\n"
> +                       "e\tforce erase\n");
> +               exit(1);
> +       }
> +
> +       if ((nargs < min_args) || (nargs > max_args)) {
> +               fprintf(stderr, "Usage: mmc cmd42 <s|c|l|u|e> <device> [password]
> [new_password]\n");
> +               exit(1);
> +       }
> +
> +       if (nargs > 3) {
> +               pwd_len = parse_password(argv[3], pwd);
> +               printf("Using password '%s', length %d\n", argv[3], pwd_len);
> +       }
> +
> +       if (nargs == 5) {
> +               new_pwd_len = parse_password(argv[4], pwd+pwd_len);
> +               printf("New password '%s', length %d\n", argv[4],
> + new_pwd_len);
> +
> +               pwd_len += new_pwd_len;
> +       }
> +
> +       device = argv[2];
> +
> +       fd = open(device, O_RDWR);
> +       if (fd < 0) {
> +               perror("open");
> +               exit(1);
> +       }
> +
> +       if (cmd42_para == MMC_CMD42_ERASE)
> +               block_size = 2;
> +       else
> +               block_size = MMC_BLOCK_SIZE;
> +
> +       printf("Set data block length = %d byte(s).\n", block_size);
> +
> +       mioc = (struct mmc_ioc_multi_cmd *)
> +               calloc(1, sizeof(struct mmc_ioc_multi_cmd) +
> +                       2 * sizeof(struct mmc_ioc_cmd));
> +       if (!mioc)
> +               return -ENOMEM;
> +
> +       mioc->num_of_cmds = 2;
> +
> +       idata = &mioc->cmds[0];
> +       set_single_cmd(idata, MMC_SET_BLOCKLEN, 0, 0, block_size);
> +
> +       if (cmd42_para == MMC_CMD42_ERASE) {
> +               data_block_onebyte[0] = cmd42_para;
> +       } else {
> +               data_block[0] = cmd42_para;
> +               data_block[1] = pwd_len;
> +               memcpy((char *)(data_block+2), pwd, pwd_len);
> +       }
> +
> +       idata = &mioc->cmds[1];
> +
> +       idata->write_flag = 1;
> +       idata->opcode = MMC_LOCK_UNLOCK;
> +       idata->arg = 0;
> +       idata->flags = MMC_RSP_R1 | MMC_CMD_AC | MMC_CMD_ADTC;
> +       idata->blksz = block_size;
> +       idata->blocks = 1;
> +
> +       if (cmd42_para == MMC_CMD42_ERASE)
> +               mmc_ioc_cmd_set_data((*idata), data_block_onebyte);
> +       else
> +               mmc_ioc_cmd_set_data((*idata), data_block);
> +
> +       ret = ioctl(fd, MMC_IOC_MULTI_CMD, mioc);
> +
> +       printf("Multi cmd response: %d\n", ret);
> +
> +       printf("Set block length response: 0x%08x\n",
> +               mioc->cmds[0].response[0]);
> +
> +       r1_response = mioc->cmds[1].response[0];
> +       printf("cmd42 response: 0x%08x\n", r1_response);
> +
> +       if (r1_response & R1_ERROR) {
> +               printf("cmd42 error! Error code: 0x%08x\n",
> +                       r1_response & R1_ERROR);
> +               ret = -1;
> +       }
> +
> +       if (r1_response & R1_LOCK_UNLOCK_FAILED) {
> +               printf("Card lock/unlock fail! Error code: 0x%08x\n",
> +               r1_response & R1_LOCK_UNLOCK_FAILED);
> +               ret = -1;
> +       }
> +
> +       close(fd);
> +       return ret;
> +}
> +
> diff --git a/mmc_cmds.h b/mmc_cmds.h
> index 5f2bef1..96da608 100644
> --- a/mmc_cmds.h
> +++ b/mmc_cmds.h
> @@ -50,3 +50,4 @@ int do_general_cmd_read(int nargs, char **argv);  int
> do_softreset(int nargs, char **argv);  int do_preidle(int nargs, char **argv);  int
> do_alt_boot_op(int nargs, char **argv);
> +int do_lock_unlock(int nargs, char **argv);
> --
> 2.34.1
Avri Altman June 15, 2024, 6:59 p.m. UTC | #2
> Can the above be eliminated by using parse_bin() instead?
I guess not - because parse_bin() expects a known sequence which isn't the case here.

Thanks,
Avri
Daniel Kucera June 16, 2024, 6:25 p.m. UTC | #3
On 2024-06-15 20:59, Avri Altman wrote:
>> Can the above be eliminated by using parse_bin() instead?
> I guess not - because parse_bin() expects a known sequence which isn't
> the case here.

Yes, I was analyzing it but it doesn't fit the use case very well.

Btw, can you advise how to make the kernel patch merged? Shall I ping 
someone?

> 
> Thanks,
> Avri

Thanks,
D.
Avri Altman June 17, 2024, 5:20 a.m. UTC | #4
> Btw, can you advise how to make the kernel patch merged? Shall I ping someone?
It needs to get people attention, and comments.
Personally, I find it interesting, and even would like to test it.
Will try to get to it sometime soon.

Thanks,
Avri

> 
> >
> > Thanks,
> > Avri
> 
> Thanks,
> D.
diff mbox series

Patch

diff --git a/mmc.c b/mmc.c
index bc8f74e..38e3e72 100644
--- a/mmc.c
+++ b/mmc.c
@@ -250,6 +250,18 @@  static struct Command commands[] = {
 		"be 1.",
 	NULL
 	},
+	{ do_lock_unlock, -3,
+	"cmd42", "<parameter> " "<device> " "[password] " "[new_password]\n"
+		"Usage: mmc cmd42 <s|c|l|u|e> <device> [password] [new_password]\n"
+		"<password> can be up to 16 character plaintext or hex string starting with 0x\n"
+		"s\tset password\n"
+		"c\tclear password\n"
+		"l\tlock\n"
+		"sl\tset password and lock\n"
+		"u\tunlock\n"
+		"e\tforce erase\n",
+	NULL
+	},
 	{ do_softreset, -1,
 	  "softreset", "<device>\n"
 	  "Issues a CMD0 softreset, e.g. for testing if hardware reset for UHS works",
diff --git a/mmc.h b/mmc.h
index 6f1bf3e..ddbb06c 100644
--- a/mmc.h
+++ b/mmc.h
@@ -30,6 +30,7 @@ 
 #define MMC_SEND_STATUS		13	/* ac   [31:16] RCA        R1  */
 #define R1_SWITCH_ERROR   (1 << 7)  /* sx, c */
 #define MMC_SWITCH_MODE_WRITE_BYTE	0x03	/* Set target to value */
+#define MMC_SET_BLOCKLEN	16  /* ac [31:0] block len R1 */
 #define MMC_READ_MULTIPLE_BLOCK  18   /* adtc [31:0] data addr   R1  */
 #define MMC_SET_BLOCK_COUNT      23   /* adtc [31:0] data addr   R1  */
 #define MMC_WRITE_BLOCK		24	/* adtc [31:0] data addr	R1  */
@@ -46,6 +47,7 @@ 
 					      [1] Discard Enable
 					      [0] Identify Write Blocks for
 					      Erase (or TRIM Enable)  R1b */
+#define MMC_LOCK_UNLOCK		42  /* adtc R1b */
 #define MMC_GEN_CMD		56   /* adtc  [31:1] stuff bits.
 					      [0]: RD/WR1 R1 */
 
@@ -70,6 +72,14 @@ 
 #define R1_EXCEPTION_EVENT      (1 << 6)        /* sr, a */
 #define R1_APP_CMD              (1 << 5)        /* sr, c */
 
+#define MMC_CMD42_UNLOCK	0x0 /* UNLOCK */
+#define MMC_CMD42_SET_PWD	0x1 /* SET_PWD */
+#define MMC_CMD42_CLR_PWD	0x2 /* CLR_PWD */
+#define MMC_CMD42_LOCK		0x4 /* LOCK */
+#define MMC_CMD42_ERASE		0x8 /* ERASE */
+#define MAX_PWD_LENGTH		32 /* max PWDS_LEN: old+new */
+#define MMC_BLOCK_SIZE		512 /* data blk size for cmd42 */
+
 /*
  * EXT_CSD fields
  */
diff --git a/mmc_cmds.c b/mmc_cmds.c
index 936e0c5..dbbaf8d 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -3245,3 +3245,192 @@  dev_fd_close:
 		exit(1);
 	return 0;
 }
+
+static int hex_to_bytes(char *input, char *output, int len)
+{
+	int ilen = strlen(input);
+
+	if (ilen % 2) {
+		printf("Error: hex string has odd number of characters\n");
+		exit(1);
+	}
+
+	if (ilen / 2 > len) {
+		printf("Error: hex string is too long\n");
+		exit(1);
+	}
+
+	for (int i = 0; i < (ilen / 2); i++) {
+		if (sscanf(input + 2 * i, "%2hhx", &output[i]) != 1) {
+			printf("Error: failed to parse hex string\n");
+			exit(1);
+		}
+	}
+
+	return ilen/2;
+}
+
+static int parse_password(char *pass, char *buf)
+{
+	int pwd_len;
+
+	if (!strncmp("0x", pass, 2)) {
+		pwd_len = hex_to_bytes(pass+2, buf, MAX_PWD_LENGTH);
+	} else {
+		pwd_len = strlen(pass);
+		strncpy(buf, pass, MAX_PWD_LENGTH);
+	}
+
+	if (pwd_len > MAX_PWD_LENGTH) {
+		printf("Password too long.\n");
+		exit(1);
+	}
+
+	return pwd_len;
+}
+
+int do_lock_unlock(int nargs, char **argv)
+{
+	int fd, ret = 0;
+	char *device;
+	__u8 data_block[MMC_BLOCK_SIZE] = {};
+	__u8 data_block_onebyte[1] = {0};
+	int block_size = 0;
+	struct mmc_ioc_multi_cmd *mioc;
+	struct mmc_ioc_cmd *idata;
+	int cmd42_para;
+	char pwd[MAX_PWD_LENGTH*2+1];
+	int pwd_len = 0, new_pwd_len;
+	int min_args, max_args;
+	__u32 r1_response;
+
+	min_args = 4;
+	max_args = 4;
+
+	printf("Function: ");
+	if (!strcmp("s", argv[1])) {
+		cmd42_para = MMC_CMD42_SET_PWD;
+		printf("Set password\n");
+		max_args = 5;
+	} else if (!strcmp("c", argv[1])) {
+		cmd42_para = MMC_CMD42_CLR_PWD;
+		printf("Clear password\n");
+	} else if (!strcmp("l", argv[1])) {
+		cmd42_para = MMC_CMD42_LOCK;
+		printf("Lock the card\n");
+	} else if (!strcmp("sl", argv[1])) {
+		cmd42_para = MMC_CMD42_SET_PWD | MMC_CMD42_LOCK;
+		printf("Set password and lock the card\n");
+		max_args = 5;
+	} else if (!strcmp("u", argv[1])) {
+		cmd42_para = MMC_CMD42_UNLOCK;
+		printf("Unlock the card\n");
+	} else if (!strcmp("e", argv[1])) {
+#ifdef DANGEROUS_COMMANDS_ENABLED
+		cmd42_para = MMC_CMD42_ERASE;
+		printf("Force erase (Warning: all card data will be erased together with PWD!)\n");
+		min_args = 3;
+		max_args = 3;
+#else
+		printf("Erase is disabled unless compiled with DANGEROUS_COMMANDS_ENABLED\n");
+		exit(1);
+#endif
+	} else {
+		printf("Invalid parameter:\n" "s\tset password\n"
+			"c\tclear password\n" "l\tlock\n"
+			"sl\tset password and lock\n" "u\tunlock\n"
+			"e\tforce erase\n");
+		exit(1);
+	}
+
+	if ((nargs < min_args) || (nargs > max_args)) {
+		fprintf(stderr, "Usage: mmc cmd42 <s|c|l|u|e> <device> [password] [new_password]\n");
+		exit(1);
+	}
+
+	if (nargs > 3) {
+		pwd_len = parse_password(argv[3], pwd);
+		printf("Using password '%s', length %d\n", argv[3], pwd_len);
+	}
+
+	if (nargs == 5) {
+		new_pwd_len = parse_password(argv[4], pwd+pwd_len);
+		printf("New password '%s', length %d\n", argv[4], new_pwd_len);
+
+		pwd_len += new_pwd_len;
+	}
+
+	device = argv[2];
+
+	fd = open(device, O_RDWR);
+	if (fd < 0) {
+		perror("open");
+		exit(1);
+	}
+
+	if (cmd42_para == MMC_CMD42_ERASE)
+		block_size = 2;
+	else
+		block_size = MMC_BLOCK_SIZE;
+
+	printf("Set data block length = %d byte(s).\n", block_size);
+
+	mioc = (struct mmc_ioc_multi_cmd *)
+		calloc(1, sizeof(struct mmc_ioc_multi_cmd) +
+			2 * sizeof(struct mmc_ioc_cmd));
+	if (!mioc)
+		return -ENOMEM;
+
+	mioc->num_of_cmds = 2;
+
+	idata = &mioc->cmds[0];
+	set_single_cmd(idata, MMC_SET_BLOCKLEN, 0, 0, block_size);
+
+	if (cmd42_para == MMC_CMD42_ERASE) {
+		data_block_onebyte[0] = cmd42_para;
+	} else {
+		data_block[0] = cmd42_para;
+		data_block[1] = pwd_len;
+		memcpy((char *)(data_block+2), pwd, pwd_len);
+	}
+
+	idata = &mioc->cmds[1];
+
+	idata->write_flag = 1;
+	idata->opcode = MMC_LOCK_UNLOCK;
+	idata->arg = 0;
+	idata->flags = MMC_RSP_R1 | MMC_CMD_AC | MMC_CMD_ADTC;
+	idata->blksz = block_size;
+	idata->blocks = 1;
+
+	if (cmd42_para == MMC_CMD42_ERASE)
+		mmc_ioc_cmd_set_data((*idata), data_block_onebyte);
+	else
+		mmc_ioc_cmd_set_data((*idata), data_block);
+
+	ret = ioctl(fd, MMC_IOC_MULTI_CMD, mioc);
+
+	printf("Multi cmd response: %d\n", ret);
+
+	printf("Set block length response: 0x%08x\n",
+		mioc->cmds[0].response[0]);
+
+	r1_response = mioc->cmds[1].response[0];
+	printf("cmd42 response: 0x%08x\n", r1_response);
+
+	if (r1_response & R1_ERROR) {
+		printf("cmd42 error! Error code: 0x%08x\n",
+			r1_response & R1_ERROR);
+		ret = -1;
+	}
+
+	if (r1_response & R1_LOCK_UNLOCK_FAILED) {
+		printf("Card lock/unlock fail! Error code: 0x%08x\n",
+		r1_response & R1_LOCK_UNLOCK_FAILED);
+		ret = -1;
+	}
+
+	close(fd);
+	return ret;
+}
+
diff --git a/mmc_cmds.h b/mmc_cmds.h
index 5f2bef1..96da608 100644
--- a/mmc_cmds.h
+++ b/mmc_cmds.h
@@ -50,3 +50,4 @@  int do_general_cmd_read(int nargs, char **argv);
 int do_softreset(int nargs, char **argv);
 int do_preidle(int nargs, char **argv);
 int do_alt_boot_op(int nargs, char **argv);
+int do_lock_unlock(int nargs, char **argv);