[PATCH] Add full command line parameter support

You may now give full job options on the command line. Makes it easier
to script fio or for one-off runs, as you don't have to write a job file
and run that.

Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
diff --git a/HOWTO b/HOWTO
index 227bc51..3f4a667 100644
--- a/HOWTO
+++ b/HOWTO
@@ -82,6 +82,12 @@
 of those files. Internally that is the same as using the 'stonewall'
 parameter described the the parameter section.
 
+If the job file contains only one job, you may as well just give the
+parameters on the command line. The command line parameters are identical
+to the job parameters, with a few extra that control global parameters
+(see README). For example, for the job file parameter iodepth=2, the
+mirror command line option would be --iodepth 2 or --iodepth=2.
+
 fio does not need to run as root, except if the files or devices specified
 in the job section requires that. Some other options may also be restricted,
 such as memory locking, io scheduler switching, and descreasing the nice value.
@@ -136,7 +142,11 @@
 We want to use async io here, with a depth of 4 for each file. We also
 increased the buffer size used to 32KiB and define numjobs to 4 to
 fork 4 identical jobs. The result is 4 processes each randomly writing
-to their own 64MiB file.
+to their own 64MiB file. Instead of using the above job file, you could
+have given the parameters on the command line. For this case, you would
+specify:
+
+$ fio --name=random-writers --ioengine=libaio --iodepth=4 --rw=randwrite --bs=32k --direct=0 --size=64m --numjobs=4
 
 fio ships with a few example job files, you can also look there for
 inspiration.
diff --git a/README b/README
index 7e79d94..04ff1ee 100644
--- a/README
+++ b/README
@@ -43,17 +43,17 @@
 ------------
 
 $ fio
-	-t <sec> Runtime in seconds
-	-l Generate per-job latency logs
-	-w Generate per-job bandwidth logs
-	-o <file> Log output to file
-	-m Minimal (terse) output
-	-h Print help info
-	-v Print version information and exit
+        --output        Write output to file
+        --timeout       Runtime in seconds
+        --latency-log   Generate per-job latency logs
+        --bandwidth-log Generate per-job bandwidth logs
+        --minimal       Minimal (terse) output
+        --version       Print version info and exit
 
-Any parameters following the options will be assumed to be job files.
-You can add as many as you want, each job file will be regarded as a
-separate group and fio will stonewall it's execution.
+Any parameters following the options will be assumed to be job files,
+unless they match a job file parameter. You can add as many as you want,
+each job file will be regarded as a separate group and fio will stonewall
+its execution.
 
 
 Job file
diff --git a/fio.h b/fio.h
index d8b0152..f8b8e38 100644
--- a/fio.h
+++ b/fio.h
@@ -164,7 +164,7 @@
  * This describes a single thread/process executing a fio job.
  */
 struct thread_data {
-	char name[32];
+	char *name;
 	char *directory;
 	char *filename;
 	char verror[80];
@@ -575,7 +575,7 @@
 
 #define FIO_IOOPS_VERSION	3
 
-extern struct ioengine_ops *load_ioengine(struct thread_data *, char *);
+extern struct ioengine_ops *load_ioengine(struct thread_data *, const char *);
 extern void close_ioengine(struct thread_data *);
 
 /*
diff --git a/init.c b/init.c
index b0c35f1..c799bf5 100644
--- a/init.c
+++ b/init.c
@@ -8,6 +8,8 @@
 #include <ctype.h>
 #include <string.h>
 #include <errno.h>
+#include <getopt.h>
+#include <assert.h>
 #include <sys/ipc.h>
 #include <sys/shm.h>
 #include <sys/types.h>
@@ -52,10 +54,10 @@
 
 #define td_var_offset(var)	((size_t) &((struct thread_data *)0)->var)
 
-static int str_rw_cb(void *, char *);
-static int str_ioengine_cb(void *, char *);
-static int str_mem_cb(void *, char *);
-static int str_verify_cb(void *, char *);
+static int str_rw_cb(void *, const char *);
+static int str_ioengine_cb(void *, const char *);
+static int str_mem_cb(void *, const char *);
+static int str_verify_cb(void *, const char *);
 static int str_lockmem_cb(void *, unsigned long *);
 static int str_prio_cb(void *, unsigned int *);
 static int str_prioclass_cb(void *, unsigned int *);
@@ -345,9 +347,53 @@
 	},
 };
 
+#define FIO_JOB_OPTS	(sizeof(options) / sizeof(struct fio_option))
+#define FIO_CMD_OPTS	(16)
+#define FIO_GETOPT_JOB	(0x89988998)
+
+/*
+ * Command line options. These will contain the above, plus a few
+ * extra that only pertain to fio itself and not jobs.
+ */
+static struct option long_options[FIO_JOB_OPTS + FIO_CMD_OPTS] = {
+	{
+		.name		= "output",
+		.has_arg	= required_argument,
+		.val		= 'o',
+	},
+	{
+		.name		= "timeout",
+		.has_arg	= required_argument,
+		.val		= 't',
+	},
+	{
+		.name		= "latency-log",
+		.has_arg	= required_argument,
+		.val		= 'l',
+	},
+	{
+		.name		= "bandwidth-log",
+		.has_arg	= required_argument,
+		.val		= 'b',
+	},
+	{
+		.name		= "minimal",
+		.has_arg	= optional_argument,
+		.val		= 'm',
+	},
+	{
+		.name		= "version",
+		.has_arg	= no_argument,
+		.val		= 'v',
+	},
+	{
+		.name		= NULL,
+	},
+};
+
 static int def_timeout = DEF_TIMEOUT;
 
-static char fio_version_string[] = "fio 1.5";
+static char fio_version_string[] = "fio 1.7";
 
 static char **ini_file;
 static int max_jobs = MAX_JOBS;
@@ -379,7 +425,6 @@
 
 	td = &threads[thread_number++];
 	*td = *parent;
-	td->name[0] = '\0';
 
 	td->thread_number = thread_number;
 	return td;
@@ -555,8 +600,8 @@
 	if (td->write_bw_log)
 		setup_log(&td->bw_log);
 
-	if (td->name[0] == '\0')
-		snprintf(td->name, sizeof(td->name)-1, "client%d", td->thread_number);
+	if (!td->name)
+		td->name = strdup(jobname);
 
 	ddir = td->ddir + (!td->sequential << 1) + (td->iomix << 2);
 
@@ -671,7 +716,7 @@
 	return 1;
 }
 
-static int str_rw_cb(void *data, char *mem)
+static int str_rw_cb(void *data, const char *mem)
 {
 	struct thread_data *td = data;
 
@@ -707,7 +752,7 @@
 	return 1;
 }
 
-static int str_verify_cb(void *data, char *mem)
+static int str_verify_cb(void *data, const char *mem)
 {
 	struct thread_data *td = data;
 
@@ -726,7 +771,7 @@
 	return 1;
 }
 
-static int str_mem_cb(void *data, char *mem)
+static int str_mem_cb(void *data, const char *mem)
 {
 	struct thread_data *td = data;
 
@@ -745,7 +790,7 @@
 	return 1;
 }
 
-static int str_ioengine_cb(void *data, char *str)
+static int str_ioengine_cb(void *data, const char *str)
 {
 	struct thread_data *td = data;
 
@@ -929,64 +974,83 @@
 static void usage(void)
 {
 	printf("%s\n", fio_version_string);
-	printf("\t-o Write output to file\n");
-	printf("\t-t Runtime in seconds\n");
-	printf("\t-l Generate per-job latency logs\n");
-	printf("\t-w Generate per-job bandwidth logs\n");
-	printf("\t-m Minimal (terse) output\n");
-	printf("\t-v Print version info and exit\n");
+	printf("\t--output\tWrite output to file\n");
+	printf("\t--timeout\tRuntime in seconds\n");
+	printf("\t--latency-log\tGenerate per-job latency logs\n");
+	printf("\t--bandwidth-log\tGenerate per-job bandwidth logs\n");
+	printf("\t--minimal\tMinimal (terse) output\n");
+	printf("\t--version\tPrint version info and exit\n");
 }
 
 static int parse_cmd_line(int argc, char *argv[])
 {
-	int c, idx = 1, ini_idx = 0;
+	struct thread_data *td = NULL;
+	int c, ini_idx = 0, lidx;
 
-	while ((c = getopt(argc, argv, "t:o:lwvhm")) != EOF) {
+	while ((c = getopt_long(argc, argv, "", long_options, &lidx)) != -1) {
 		switch (c) {
-			case 't':
-				def_timeout = atoi(optarg);
-				idx = optind;
-				break;
-			case 'l':
-				write_lat_log = 1;
-				idx = optind;
-				break;
-			case 'w':
-				write_bw_log = 1;
-				idx = optind;
-				break;
-			case 'o':
-				f_out = fopen(optarg, "w+");
-				if (!f_out) {
-					perror("fopen output");
-					exit(1);
-				}
-				f_err = f_out;
-				idx = optind;
-				break;
-			case 'm':
-				terse_output = 1;
-				idx = optind;
-				break;
-			case 'h':
-				usage();
-				exit(0);
-			case 'v':
-				printf("%s\n", fio_version_string);
-				exit(0);
+		case 't':
+			def_timeout = atoi(optarg);
+			break;
+		case 'l':
+			write_lat_log = 1;
+			break;
+		case 'w':
+			write_bw_log = 1;
+			break;
+		case 'o':
+			f_out = fopen(optarg, "w+");
+			if (!f_out) {
+				perror("fopen output");
+				exit(1);
+			}
+			f_err = f_out;
+			break;
+		case 'm':
+			terse_output = 1;
+			break;
+		case 'h':
+			usage();
+			exit(0);
+		case 'v':
+			printf("%s\n", fio_version_string);
+			exit(0);
+		case FIO_GETOPT_JOB: {
+			const char *opt = long_options[lidx].name;
+			char *val = optarg;
+
+			if (!td) {
+				td = get_new_job(0, &def_thread);
+				if (!td)
+					return 0;
+			}
+			if (parse_cmd_option(opt, val, options, td))
+				printf("foo\n");
+			break;
+		}
+		default:
+			printf("optarg <<%s>>\n", argv[optind]);
+			break;
 		}
 	}
 
-	while (idx < argc) {
-		ini_idx++;
-		ini_file = realloc(ini_file, ini_idx * sizeof(char *));
-		ini_file[ini_idx - 1] = strdup(argv[idx]);
-		idx++;
+	if (td) {
+		const char *name = td->name;
+		int ret;
+
+		if (!name)
+			name = "fio";
+
+		ret = add_job(td, name, 0);
+		if (ret)
+			put_job(td);
 	}
 
-	if (!f_out) {
-		f_out = stdout;
-		f_err = stderr;
+	while (optind < argc) {
+		ini_idx++;
+		ini_file = realloc(ini_file, ini_idx * sizeof(char *));
+		ini_file[ini_idx - 1] = strdup(argv[optind]);
+		optind++;
 	}
 
 	return ini_idx;
@@ -1041,21 +1105,49 @@
 	return 0;
 }
 
+/*
+ * Copy the fio options into the long options map, so we mirror
+ * job and cmd line options.
+ */
+static void dupe_job_options(void)
+{
+	struct fio_option *o;
+	unsigned int i;
+
+	i = 0;
+	while (long_options[i].name)
+		i++;
+
+	o = &options[0];
+	while (o->name) {
+		long_options[i].name = o->name;
+		long_options[i].val = FIO_GETOPT_JOB;
+		if (o->type == FIO_OPT_STR_SET)
+			long_options[i].has_arg = no_argument;
+		else
+			long_options[i].has_arg = required_argument;
+
+		i++;
+		o++;
+		assert(i < FIO_JOB_OPTS + FIO_CMD_OPTS);
+	}
+}
+
 int parse_options(int argc, char *argv[])
 {
 	int job_files, i;
 
+	f_out = stdout;
+	f_err = stderr;
+
+	dupe_job_options();
+
 	if (setup_thread_area())
 		return 1;
 	if (fill_def_thread())
 		return 1;
 
 	job_files = parse_cmd_line(argc, argv);
-	if (!job_files) {
-		log_err("Need job file(s)\n");
-		usage();
-		return 1;
-	}
 
 	for (i = 0; i < job_files; i++) {
 		if (fill_def_thread())
@@ -1066,5 +1158,12 @@
 	}
 
 	free(ini_file);
+
+	if (!thread_number) {
+		log_err("No jobs defined(s)\n");
+		usage();
+		return 1;
+	}
+
 	return 0;
 }
diff --git a/ioengines.c b/ioengines.c
index 115e6f2..6a5073c 100644
--- a/ioengines.c
+++ b/ioengines.c
@@ -42,7 +42,7 @@
 	return 0;
 }
 
-struct ioengine_ops *load_ioengine(struct thread_data *td, char *name)
+struct ioengine_ops *load_ioengine(struct thread_data *td, const char *name)
 {
 	char engine[16], engine_lib[256];
 	struct ioengine_ops *ops, *ret;
diff --git a/parse.c b/parse.c
index 197a46e..48703b6 100644
--- a/parse.c
+++ b/parse.c
@@ -48,9 +48,8 @@
 /*
  * convert string into decimal value, noting any size suffix
  */
-static int str_to_decimal(char *p, unsigned long long *val, int kilo)
+static int str_to_decimal(const char *str, unsigned long long *val, int kilo)
 {
-	char *str = p;
 	int len;
 
 	len = strlen(str);
@@ -66,12 +65,12 @@
 	return 0;
 }
 
-static int check_str_bytes(char *p, unsigned long long *val)
+static int check_str_bytes(const char *p, unsigned long long *val)
 {
 	return str_to_decimal(p, val, 1);
 }
 
-static int check_str_time(char *p, unsigned long long *val)
+static int check_str_time(const char *p, unsigned long long *val)
 {
 	return str_to_decimal(p, val, 0);
 }
@@ -109,7 +108,7 @@
 	return 1;
 }
 
-static int check_int(char *p, unsigned int *val)
+static int check_int(const char *p, unsigned int *val)
 {
 	if (sscanf(p, "%u", val) == 1)
 		return 0;
@@ -132,16 +131,14 @@
 	return NULL;
 }
 
-static int handle_option(struct fio_option *o, char *ptr, void *data)
+static int handle_option(struct fio_option *o, const char *ptr, void *data)
 {
 	unsigned int il, *ilp;
 	unsigned long long ull, *ullp;
 	unsigned long ul1, ul2, *ulp1, *ulp2;
-	char *tmpbuf, **cp;
+	char **cp;
 	int ret = 0, is_time = 0;
 
-	tmpbuf = malloc(4096);
-
 	switch (o->type) {
 	case FIO_OPT_STR: {
 		fio_opt_str_fn *fn = o->cb;
@@ -239,13 +236,26 @@
 		ret = 1;
 	}
 
-	free(tmpbuf);
 	return ret;
 }
 
+int parse_cmd_option(const char *opt, const char *val,
+		     struct fio_option *options, void *data)
+{
+	struct fio_option *o;
+
+	o = find_option(options, opt);
+	if (!o) {
+		fprintf(stderr, "Bad option %s\n", opt);
+		return 1;
+	}
+
+	return handle_option(o, val, data);
+}
+
 int parse_option(const char *opt, struct fio_option *options, void *data)
 {
-	struct fio_option *o = find_option(options, opt);
+	struct fio_option *o;
 	char *pre, *post;
 	char tmp[64];
 
diff --git a/parse.h b/parse.h
index c36bdc0..be3d24a 100644
--- a/parse.h
+++ b/parse.h
@@ -29,6 +29,7 @@
 typedef int (str_cb_fn)(void *, char *);
 
 extern int parse_option(const char *, struct fio_option *, void *);
+extern int parse_cmd_option(const char *t, const char *l, struct fio_option *, void *);
 
 extern void strip_blank_front(char **);
 extern void strip_blank_end(char *);
@@ -36,7 +37,7 @@
 /*
  * Handlers for the options
  */
-typedef int (fio_opt_str_fn)(void *, char *);
+typedef int (fio_opt_str_fn)(void *, const char *);
 typedef int (fio_opt_str_val_fn)(void *, unsigned long long *);
 typedef int (fio_opt_int_fn)(void *, unsigned int *);
 typedef int (fio_opt_str_set_fn)(void *);