Update size/io_size descriptions
[fio.git] / options.c
index 29b181a020ac5a62cdfd7b6eaed3fc6db133ab17..a01b2d9b0c574621e127c1262ce965fc102e73f7 100644 (file)
--- a/options.c
+++ b/options.c
@@ -98,11 +98,11 @@ static int bssplit_ddir(struct thread_options *o, int ddir, char *str)
                        if (perc > 100)
                                perc = 100;
                        else if (!perc)
-                               perc = -1;
+                               perc = -1U;
                } else
-                       perc = -1;
+                       perc = -1U;
 
-               if (str_to_decimal(fname, &val, 1, o)) {
+               if (str_to_decimal(fname, &val, 1, o, 0, 0)) {
                        log_err("fio: bssplit conversion failed\n");
                        free(bssplit);
                        return 1;
@@ -127,26 +127,29 @@ static int bssplit_ddir(struct thread_options *o, int ddir, char *str)
        for (i = 0; i < o->bssplit_nr[ddir]; i++) {
                struct bssplit *bsp = &bssplit[i];
 
-               if (bsp->perc == (unsigned char) -1)
+               if (bsp->perc == -1U)
                        perc_missing++;
                else
                        perc += bsp->perc;
        }
 
-       if (perc > 100) {
+       if (perc > 100 && perc_missing > 1) {
                log_err("fio: bssplit percentages add to more than 100%%\n");
                free(bssplit);
                return 1;
        }
+
        /*
         * If values didn't have a percentage set, divide the remains between
         * them.
         */
        if (perc_missing) {
+               if (perc_missing == 1 && o->bssplit_nr[ddir] == 1)
+                       perc = 100;
                for (i = 0; i < o->bssplit_nr[ddir]; i++) {
                        struct bssplit *bsp = &bssplit[i];
 
-                       if (bsp->perc == (unsigned char) -1)
+                       if (bsp->perc == -1U)
                                bsp->perc = (100 - perc) / perc_missing;
                }
        }
@@ -269,7 +272,7 @@ static int ignore_error_type(struct thread_data *td, int etype, char *str)
                } else {
                        error[i] = atoi(fname);
                        if (error[i] < 0)
-                               error[i] = error[i];
+                               error[i] = -error[i];
                }
                if (!error[i]) {
                        log_err("Unknown error %s, please use number value \n",
@@ -283,7 +286,9 @@ static int ignore_error_type(struct thread_data *td, int etype, char *str)
                td->o.continue_on_error |= 1 << etype;
                td->o.ignore_error_nr[etype] = i;
                td->o.ignore_error[etype] = error;
-       }
+       } else
+               free(error);
+
        return 0;
 
 }
@@ -320,7 +325,7 @@ static int str_rw_cb(void *data, const char *str)
 {
        struct thread_data *td = data;
        struct thread_options *o = &td->o;
-       char *nr = get_opt_postfix(str);
+       char *nr;
 
        if (parse_dryrun())
                return 0;
@@ -328,6 +333,7 @@ static int str_rw_cb(void *data, const char *str)
        o->ddir_seq_nr = 1;
        o->ddir_seq_add = 0;
 
+       nr = get_opt_postfix(str);
        if (!nr)
                return 0;
 
@@ -336,7 +342,7 @@ static int str_rw_cb(void *data, const char *str)
        else {
                long long val;
 
-               if (str_to_decimal(nr, &val, 1, o)) {
+               if (str_to_decimal(nr, &val, 1, o, 0, 0)) {
                        log_err("fio: rw postfix parsing failed\n");
                        free(nr);
                        return 1;
@@ -394,6 +400,28 @@ static int str_exitall_cb(void)
 }
 
 #ifdef FIO_HAVE_CPU_AFFINITY
+int fio_cpus_split(os_cpu_mask_t *mask, unsigned int cpu_index)
+{
+       unsigned int i, index, cpus_in_mask;
+       const long max_cpu = cpus_online();
+
+       cpus_in_mask = fio_cpu_count(mask);
+       cpu_index = cpu_index % cpus_in_mask;
+
+       index = 0;
+       for (i = 0; i < max_cpu; i++) {
+               if (!fio_cpu_isset(mask, i))
+                       continue;
+
+               if (cpu_index != index)
+                       fio_cpu_clear(mask, i);
+
+               index++;
+       }
+
+       return fio_cpu_count(mask);
+}
+
 static int str_cpumask_cb(void *data, unsigned long long *val)
 {
        struct thread_data *td = data;
@@ -425,7 +453,6 @@ static int str_cpumask_cb(void *data, unsigned long long *val)
                }
        }
 
-       td->o.cpumask_set = 1;
        return 0;
 }
 
@@ -492,36 +519,24 @@ static int set_cpus_allowed(struct thread_data *td, os_cpu_mask_t *mask,
        }
 
        free(p);
-       if (!ret)
-               td->o.cpumask_set = 1;
        return ret;
 }
 
 static int str_cpus_allowed_cb(void *data, const char *input)
 {
        struct thread_data *td = data;
-       int ret;
 
        if (parse_dryrun())
                return 0;
 
-       ret = set_cpus_allowed(td, &td->o.cpumask, input);
-       if (!ret)
-               td->o.cpumask_set = 1;
-
-       return ret;
+       return set_cpus_allowed(td, &td->o.cpumask, input);
 }
 
 static int str_verify_cpus_allowed_cb(void *data, const char *input)
 {
        struct thread_data *td = data;
-       int ret;
 
-       ret = set_cpus_allowed(td, &td->o.verify_cpumask, input);
-       if (!ret)
-               td->o.verify_cpumask_set = 1;
-
-       return ret;
+       return set_cpus_allowed(td, &td->o.verify_cpumask, input);
 }
 #endif
 
@@ -529,6 +544,7 @@ static int str_verify_cpus_allowed_cb(void *data, const char *input)
 static int str_numa_cpunodes_cb(void *data, char *input)
 {
        struct thread_data *td = data;
+       struct bitmask *verify_bitmask;
 
        if (parse_dryrun())
                return 0;
@@ -538,14 +554,15 @@ static int str_numa_cpunodes_cb(void *data, char *input)
         * numa_allocate_nodemask(), so it should be freed by
         * numa_free_nodemask().
         */
-       td->o.numa_cpunodesmask = numa_parse_nodestring(input);
-       if (td->o.numa_cpunodesmask == NULL) {
+       verify_bitmask = numa_parse_nodestring(input);
+       if (verify_bitmask == NULL) {
                log_err("fio: numa_parse_nodestring failed\n");
                td_verror(td, 1, "str_numa_cpunodes_cb");
                return 1;
        }
+       numa_free_nodemask(verify_bitmask);
 
-       td->o.numa_cpumask_set = 1;
+       td->o.numa_cpunodes = strdup(input);
        return 0;
 }
 
@@ -556,6 +573,7 @@ static int str_numa_mpol_cb(void *data, char *input)
                { "default", "prefer", "bind", "interleave", "local", NULL };
        int i;
        char *nodelist;
+       struct bitmask *verify_bitmask;
 
        if (parse_dryrun())
                return 0;
@@ -635,12 +653,15 @@ static int str_numa_mpol_cb(void *data, char *input)
                break;
        case MPOL_INTERLEAVE:
        case MPOL_BIND:
-               td->o.numa_memnodesmask = numa_parse_nodestring(nodelist);
-               if (td->o.numa_memnodesmask == NULL) {
+               verify_bitmask = numa_parse_nodestring(nodelist);
+               if (verify_bitmask == NULL) {
                        log_err("fio: numa_parse_nodestring failed\n");
                        td_verror(td, 1, "str_numa_memnodes_cb");
                        return 1;
                }
+               td->o.numa_memnodes = strdup(nodelist);
+               numa_free_nodemask(verify_bitmask);
+
                break;
        case MPOL_LOCAL:
        case MPOL_DEFAULT:
@@ -648,9 +669,7 @@ static int str_numa_mpol_cb(void *data, char *input)
                break;
        }
 
-       td->o.numa_memmask_set = 1;
        return 0;
-
 out:
        return 1;
 }
@@ -703,7 +722,7 @@ static int str_random_distribution_cb(void *data, const char *str)
                return 0;
 
        nr = get_opt_postfix(str);
-       if (nr && !str_to_float(nr, &val)) {
+       if (nr && !str_to_float(nr, &val, 0)) {
                log_err("fio: random postfix parsing failed\n");
                free(nr);
                return 1;
@@ -729,11 +748,11 @@ static int str_random_distribution_cb(void *data, const char *str)
 }
 
 /*
- * Return next file in the string. Files are separated with ':'. If the ':'
+ * Return next name in the string. Files are separated with ':'. If the ':'
  * is escaped with a '\', then that ':' is part of the filename and does not
  * indicate a new file.
  */
-static char *get_next_file_name(char **ptr)
+static char *get_next_name(char **ptr)
 {
        char *str = *ptr;
        char *p, *start;
@@ -774,6 +793,43 @@ static char *get_next_file_name(char **ptr)
        return start;
 }
 
+
+static int get_max_name_idx(char *input)
+{
+       unsigned int cur_idx;
+       char *str, *p;
+
+       p = str = strdup(input);
+       for (cur_idx = 0; ; cur_idx++)
+               if (get_next_name(&str) == NULL)
+                       break;
+
+       free(p);
+       return cur_idx;
+}
+
+/*
+ * Returns the directory at the index, indexes > entires will be
+ * assigned via modulo division of the index
+ */
+int set_name_idx(char *target, char *input, int index)
+{
+       unsigned int cur_idx;
+       int len;
+       char *fname, *str, *p;
+
+       p = str = strdup(input);
+
+       index %= get_max_name_idx(input);
+       for (cur_idx = 0; cur_idx <= index; cur_idx++)
+               fname = get_next_name(&str);
+
+       len = sprintf(target, "%s/", fname);
+       free(p);
+
+       return len;
+}
+
 static int str_filename_cb(void *data, const char *input)
 {
        struct thread_data *td = data;
@@ -787,38 +843,45 @@ static int str_filename_cb(void *data, const char *input)
        if (!td->files_index)
                td->o.nr_files = 0;
 
-       while ((fname = get_next_file_name(&str)) != NULL) {
+       while ((fname = get_next_name(&str)) != NULL) {
                if (!strlen(fname))
                        break;
-               add_file(td, fname);
-               td->o.nr_files++;
+               add_file(td, fname, 0, 1);
        }
 
        free(p);
        return 0;
 }
 
-static int str_directory_cb(void *data, const char fio_unused *str)
+static int str_directory_cb(void *data, const char fio_unused *unused)
 {
        struct thread_data *td = data;
        struct stat sb;
+       char *dirname, *str, *p;
+       int ret = 0;
 
        if (parse_dryrun())
                return 0;
 
-       if (lstat(td->o.directory, &sb) < 0) {
-               int ret = errno;
+       p = str = strdup(td->o.directory);
+       while ((dirname = get_next_name(&str)) != NULL) {
+               if (lstat(dirname, &sb) < 0) {
+                       ret = errno;
 
-               log_err("fio: %s is not a directory\n", td->o.directory);
-               td_verror(td, ret, "lstat");
-               return 1;
-       }
-       if (!S_ISDIR(sb.st_mode)) {
-               log_err("fio: %s is not a directory\n", td->o.directory);
-               return 1;
+                       log_err("fio: %s is not a directory\n", dirname);
+                       td_verror(td, ret, "lstat");
+                       goto out;
+               }
+               if (!S_ISDIR(sb.st_mode)) {
+                       log_err("fio: %s is not a directory\n", dirname);
+                       ret = 1;
+                       goto out;
+               }
        }
 
-       return 0;
+out:
+       free(p);
+       return ret;
 }
 
 static int str_lockfile_cb(void *data, const char fio_unused *str)
@@ -854,6 +917,29 @@ static int pattern_cb(char *pattern, unsigned int max_size,
        uint32_t pattern_length;
        char *loc1, *loc2;
 
+       /*
+        * Check if it's a string input
+        */
+       loc1 = strchr(input, '\"');
+       if (loc1) {
+               do {
+                       loc1++;
+                       if (*loc1 == '\0' || *loc1 == '\"')
+                               break;
+
+                       pattern[i] = *loc1;
+                       i++;
+               } while (i < max_size);
+
+               if (!i)
+                       return 1;
+
+               goto fill;
+       }
+
+       /*
+        * No string, find out if it's decimal or hexidecimal
+        */
        loc1 = strstr(input, "0x");
        loc2 = strstr(input, "0X");
        if (loc1 || loc2)
@@ -890,6 +976,7 @@ static int pattern_cb(char *pattern, unsigned int max_size,
         * Fill the pattern all the way to the end. This greatly reduces
         * the number of memcpy's we have to do when verifying the IO.
         */
+fill:
        pattern_length = i;
        while (i > 1 && i * 2 <= max_size) {
                memcpy(&pattern[i], &pattern[0], i);
@@ -909,8 +996,8 @@ static int pattern_cb(char *pattern, unsigned int max_size,
 
        if (i == 1) {
                /*
-                * The code in verify_io_u_pattern assumes a single byte pattern
-                * fills the whole verify pattern buffer.
+                * The code in verify_io_u_pattern assumes a single byte
+                * pattern fills the whole verify pattern buffer.
                 */
                memset(pattern, pattern[0], max_size);
        }
@@ -927,15 +1014,38 @@ static int str_buffer_pattern_cb(void *data, const char *input)
        ret = pattern_cb(td->o.buffer_pattern, MAX_PATTERN_SIZE, input,
                                &td->o.buffer_pattern_bytes);
 
-       if (!ret) {
-               td->o.refill_buffers = 0;
+       if (!ret && td->o.buffer_pattern_bytes) {
+               if (!td->o.compress_percentage)
+                       td->o.refill_buffers = 0;
                td->o.scramble_buffers = 0;
                td->o.zero_buffers = 0;
+       } else {
+               log_err("fio: failed parsing pattern `%s`\n", input);
+               ret = 1;
        }
 
        return ret;
 }
 
+static int str_buffer_compress_cb(void *data, unsigned long long *il)
+{
+       struct thread_data *td = data;
+
+       td->flags |= TD_F_COMPRESS;
+       td->o.compress_percentage = *il;
+       return 0;
+}
+
+static int str_dedupe_cb(void *data, unsigned long long *il)
+{
+       struct thread_data *td = data;
+
+       td->flags |= TD_F_COMPRESS;
+       td->o.dedupe_percentage = *il;
+       td->o.refill_buffers = 1;
+       return 0;
+}
+
 static int str_verify_pattern_cb(void *data, const char *input)
 {
        struct thread_data *td = data;
@@ -1387,6 +1497,11 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                          { .ival = "windowsaio",
                            .help = "Windows native asynchronous IO"
                          },
+#endif
+#ifdef CONFIG_RBD
+                         { .ival = "rbd",
+                           .help = "Rados Block Device asynchronous IO"
+                         },
 #endif
                          { .ival = "mmap",
                            .help = "Memory mapped IO"
@@ -1442,6 +1557,19 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                          { .ival = "falloc",
                            .help = "fallocate() file based engine",
                          },
+#endif
+#ifdef CONFIG_GFAPI
+                         { .ival = "gfapi",
+                           .help = "Glusterfs libgfapi(sync) based engine"
+                         },
+                         { .ival = "gfapi_async",
+                           .help = "Glusterfs libgfapi(async) based engine"
+                         },
+#endif
+#ifdef CONFIG_LIBHDFS
+                         { .ival = "libhdfs",
+                           .help = "Hadoop Distributed Filesystem (HDFS) engine"
+                         },
 #endif
                          { .ival = "external",
                            .help = "Load external engine (append name)",
@@ -1506,11 +1634,22 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Size",
                .type   = FIO_OPT_STR_VAL,
                .cb     = str_size_cb,
+               .off1   = td_var_offset(size),
                .help   = "Total size of device or files",
                .interval = 1024 * 1024,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_INVALID,
        },
+       {
+               .name   = "io_size",
+               .alias  = "io_limit",
+               .lname  = "IO Size",
+               .type   = FIO_OPT_STR_VAL,
+               .off1   = td_var_offset(io_limit),
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
+       },
        {
                .name   = "fill_device",
                .lname  = "Fill device",
@@ -1534,6 +1673,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_FILE,
                .group  = FIO_OPT_G_INVALID,
        },
+       {
+               .name   = "file_append",
+               .lname  = "File append",
+               .type   = FIO_OPT_BOOL,
+               .off1   = td_var_offset(file_append),
+               .help   = "IO will start at the end of the file(s)",
+               .def    = "0",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
+       },
        {
                .name   = "offset",
                .lname  = "IO offset",
@@ -1564,7 +1713,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Number of IOs to perform",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(number_ios),
-               .help   = "Force job completion of this number of IOs",
+               .help   = "Force job completion after this number of IOs",
                .def    = "0",
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_INVALID,
@@ -1626,6 +1775,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Block size split",
                .type   = FIO_OPT_STR,
                .cb     = str_bssplit_cb,
+               .off1   = td_var_offset(bssplit),
                .help   = "Set a specific mix of block sizes",
                .parent = "rw",
                .hide   = 1,
@@ -1649,7 +1799,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Block size division is seq/random (not read/write)",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(bs_is_seq_rand),
-               .help   = "Consider any blocksize setting to be sequential,ramdom",
+               .help   = "Consider any blocksize setting to be sequential,random",
                .def    = "0",
                .parent = "blocksize",
                .category = FIO_OPT_C_IO,
@@ -1680,12 +1830,8 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
        {
                .name   = "use_os_rand",
                .lname  = "Use OS random",
-               .type   = FIO_OPT_BOOL,
-               .off1   = td_var_offset(use_os_rand),
-               .help   = "Set to use OS random generator",
-               .def    = "0",
-               .parent = "rw",
-               .hide   = 1,
+               .type   = FIO_OPT_DEPRECATED,
+               .off1   = td_var_offset(dep_use_os_rand),
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_RANDOM,
        },
@@ -1778,6 +1924,15 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_RANDOM,
        },
+       {
+               .name   = "allrandrepeat",
+               .type   = FIO_OPT_BOOL,
+               .off1   = td_var_offset(allrand_repeatable),
+               .help   = "Use repeatable random numbers for everything",
+               .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RANDOM,
+       },
        {
                .name   = "nrfiles",
                .lname  = "Number of files",
@@ -1914,18 +2069,18 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                          { .ival = "wait_before",
                            .oval = SYNC_FILE_RANGE_WAIT_BEFORE,
                            .help = "SYNC_FILE_RANGE_WAIT_BEFORE",
-                           .or   = 1,
+                           .orval  = 1,
                          },
                          { .ival = "write",
                            .oval = SYNC_FILE_RANGE_WRITE,
                            .help = "SYNC_FILE_RANGE_WRITE",
-                           .or   = 1,
+                           .orval  = 1,
                          },
                          {
                            .ival = "wait_after",
                            .oval = SYNC_FILE_RANGE_WAIT_AFTER,
                            .help = "SYNC_FILE_RANGE_WAIT_AFTER",
-                           .or   = 1,
+                           .orval  = 1,
                          },
                },
                .type   = FIO_OPT_STR_MULTI,
@@ -2006,8 +2161,11 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Start delay",
                .type   = FIO_OPT_STR_VAL_TIME,
                .off1   = td_var_offset(start_delay),
+               .off2   = td_var_offset(start_delay_high),
                .help   = "Only start job when this period has passed",
                .def    = "0",
+               .is_seconds = 1,
+               .is_time = 1,
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_RUNTIME,
        },
@@ -2019,6 +2177,8 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(timeout),
                .help   = "Stop workload when this amount of time has passed",
                .def    = "0",
+               .is_seconds = 1,
+               .is_time = 1,
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_RUNTIME,
        },
@@ -2046,6 +2206,8 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_VAL_TIME,
                .off1   = td_var_offset(ramp_time),
                .help   = "Ramp up time before measuring performance",
+               .is_seconds = 1,
+               .is_time = 1,
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_RUNTIME,
        },
@@ -2095,6 +2257,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                            .oval = MEM_MALLOC,
                            .help = "Use malloc(3) for IO buffers",
                          },
+#ifndef CONFIG_NO_SHM
                          { .ival = "shm",
                            .oval = MEM_SHM,
                            .help = "Use shared memory segments for IO buffers",
@@ -2104,6 +2267,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                            .oval = MEM_SHMHUGE,
                            .help = "Like shm, but use huge pages",
                          },
+#endif
 #endif
                          { .ival = "mmap",
                            .oval = MEM_MMAP,
@@ -2185,6 +2349,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                            .oval = VERIFY_SHA512,
                            .help = "Use sha512 checksums for verification",
                          },
+                         { .ival = "xxhash",
+                           .oval = VERIFY_XXHASH,
+                           .help = "Use xxhash checksums for verification",
+                         },
                          { .ival = "meta",
                            .oval = VERIFY_META,
                            .help = "Use io information",
@@ -2262,6 +2430,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Verify pattern",
                .type   = FIO_OPT_STR,
                .cb     = str_verify_pattern_cb,
+               .off1   = td_var_offset(verify_pattern),
                .help   = "Fill pattern for IO buffers",
                .parent = "verify",
                .hide   = 1,
@@ -2332,6 +2501,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Async verify CPUs",
                .type   = FIO_OPT_STR,
                .cb     = str_verify_cpus_allowed_cb,
+               .off1   = td_var_offset(verify_cpumask),
                .help   = "Set CPUs allowed for async verify threads",
                .parent = "verify_async",
                .hide   = 1,
@@ -2344,6 +2514,28 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(experimental_verify),
                .type   = FIO_OPT_BOOL,
                .help   = "Enable experimental verification",
+               .parent = "verify",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
+       },
+       {
+               .name   = "verify_state_load",
+               .lname  = "Load verify state",
+               .off1   = td_var_offset(verify_state),
+               .type   = FIO_OPT_BOOL,
+               .help   = "Load verify termination state",
+               .parent = "verify",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
+       },
+       {
+               .name   = "verify_state_save",
+               .lname  = "Save verify state",
+               .off1   = td_var_offset(verify_state_save),
+               .type   = FIO_OPT_BOOL,
+               .def    = "1",
+               .help   = "Save verify state on termination",
+               .parent = "verify",
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_VERIFY,
        },
@@ -2519,6 +2711,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Read/write mix read",
                .type   = FIO_OPT_INT,
                .cb     = str_rwmix_read_cb,
+               .off1   = td_var_offset(rwmix[DDIR_READ]),
                .maxval = 100,
                .help   = "Percentage of mixed workload that is reads",
                .def    = "50",
@@ -2532,6 +2725,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Read/write mix write",
                .type   = FIO_OPT_INT,
                .cb     = str_rwmix_write_cb,
+               .off1   = td_var_offset(rwmix[DDIR_WRITE]),
                .maxval = 100,
                .help   = "Percentage of mixed workload that is writes",
                .def    = "50",
@@ -2593,6 +2787,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(thinktime),
                .help   = "Idle time between IO buffers (usec)",
                .def    = "0",
+               .is_time = 1,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_THINKTIME,
        },
@@ -2603,6 +2798,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(thinktime_spin),
                .help   = "Start think time by spinning this amount (usec)",
                .def    = "0",
+               .is_time = 1,
                .parent = "thinktime",
                .hide   = 1,
                .category = FIO_OPT_C_IO,
@@ -2686,6 +2882,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(max_latency),
                .help   = "Maximum tolerated IO latency (usec)",
+               .is_time = 1,
                .category = FIO_OPT_C_IO,
                .group = FIO_OPT_G_LATPROF,
        },
@@ -2695,6 +2892,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_VAL_TIME,
                .off1   = td_var_offset(latency_target),
                .help   = "Ramp to max queue depth supporting this latency",
+               .is_time = 1,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_LATPROF,
        },
@@ -2704,6 +2902,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_VAL_TIME,
                .off1   = td_var_offset(latency_window),
                .help   = "Time to sustain latency_target",
+               .is_time = 1,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_LATPROF,
        },
@@ -2796,6 +2995,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "CPU mask",
                .type   = FIO_OPT_INT,
                .cb     = str_cpumask_cb,
+               .off1   = td_var_offset(cpumask),
                .help   = "CPU affinity mask",
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_CRED,
@@ -2805,16 +3005,39 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "CPUs allowed",
                .type   = FIO_OPT_STR,
                .cb     = str_cpus_allowed_cb,
+               .off1   = td_var_offset(cpumask),
                .help   = "Set CPUs allowed",
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_CRED,
        },
+       {
+               .name   = "cpus_allowed_policy",
+               .lname  = "CPUs allowed distribution policy",
+               .type   = FIO_OPT_STR,
+               .off1   = td_var_offset(cpus_allowed_policy),
+               .help   = "Distribution policy for cpus_allowed",
+               .parent = "cpus_allowed",
+               .prio   = 1,
+               .posval = {
+                         { .ival = "shared",
+                           .oval = FIO_CPUS_SHARED,
+                           .help = "Mask shared between threads",
+                         },
+                         { .ival = "split",
+                           .oval = FIO_CPUS_SPLIT,
+                           .help = "Mask split between threads",
+                         },
+               },
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
+       },
 #endif
 #ifdef CONFIG_LIBNUMA
        {
                .name   = "numa_cpu_nodes",
                .type   = FIO_OPT_STR,
                .cb     = str_numa_cpunodes_cb,
+               .off1   = td_var_offset(numa_cpunodes),
                .help   = "NUMA CPU nodes bind",
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_INVALID,
@@ -2823,6 +3046,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .name   = "numa_mem_policy",
                .type   = FIO_OPT_STR,
                .cb     = str_numa_mpol_cb,
+               .off1   = td_var_offset(numa_memnodes),
                .help   = "NUMA memory policy setup",
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_INVALID,
@@ -2892,6 +3116,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(use_thread),
                .help   = "Use threads instead of processes",
+#ifdef CONFIG_NO_SHM
+               .def    = "1",
+               .no_warn_def = 1,
+#endif
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_PROCESS,
        },
@@ -2932,6 +3160,38 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_LOG,
                .group  = FIO_OPT_G_INVALID,
        },
+       {
+               .name   = "log_offset",
+               .lname  = "Log offset of IO",
+               .type   = FIO_OPT_BOOL,
+               .off1   = td_var_offset(log_offset),
+               .help   = "Include offset of IO for each log entry",
+               .def    = "0",
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
+       },
+#ifdef CONFIG_ZLIB
+       {
+               .name   = "log_compression",
+               .lname  = "Log compression",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(log_gz),
+               .help   = "Log in compressed chunks of this size",
+               .minval = 32 * 1024 * 1024ULL,
+               .maxval = 512 * 1024 * 1024ULL,
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = "log_store_compressed",
+               .lname  = "Log store compressed",
+               .type   = FIO_OPT_BOOL,
+               .off1   = td_var_offset(log_gz_store),
+               .help   = "Store logs in a compressed format",
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
+       },
+#endif
        {
                .name   = "bwavgtime",
                .lname  = "Bandwidth average time",
@@ -3001,6 +3261,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Buffer pattern",
                .type   = FIO_OPT_STR,
                .cb     = str_buffer_pattern_cb,
+               .off1   = td_var_offset(buffer_pattern),
                .help   = "Fill pattern for IO buffers",
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_IO_BUF,
@@ -3009,9 +3270,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .name   = "buffer_compress_percentage",
                .lname  = "Buffer compression percentage",
                .type   = FIO_OPT_INT,
+               .cb     = str_buffer_compress_cb,
                .off1   = td_var_offset(compress_percentage),
                .maxval = 100,
-               .minval = 1,
+               .minval = 0,
                .help   = "How compressible the buffer is (approximately)",
                .interval = 5,
                .category = FIO_OPT_C_IO,
@@ -3029,6 +3291,19 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_IO_BUF,
        },
+       {
+               .name   = "dedupe_percentage",
+               .lname  = "Dedupe percentage",
+               .type   = FIO_OPT_INT,
+               .cb     = str_dedupe_cb,
+               .off1   = td_var_offset(dedupe_percentage),
+               .maxval = 100,
+               .minval = 0,
+               .help   = "Percentage of buffers that are dedupable",
+               .interval = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
+       },
        {
                .name   = "clat_percentiles",
                .lname  = "Completion latency percentiles",
@@ -3192,6 +3467,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .name   = "ignore_error",
                .type   = FIO_OPT_STR,
                .cb     = str_ignore_error_cb,
+               .off1   = td_var_offset(ignore_error_nr),
                .help   = "Set a specific list of errors to ignore",
                .parent = "rw",
                .category = FIO_OPT_C_GENERAL,
@@ -3525,8 +3801,10 @@ static char *bc_calc(char *str)
                return NULL;
 
        ret = fread(&buf[tmp - str], 1, 128 - (tmp - str), f);
-       if (ret <= 0)
+       if (ret <= 0) {
+               pclose(f);
                return NULL;
+       }
 
        pclose(f);
        buf[(tmp - str) + ret - 1] = '\0';
@@ -3654,6 +3932,39 @@ static char **dup_and_sub_options(char **opts, int num_opts)
        return opts_copy;
 }
 
+static void show_closest_option(const char *opt)
+{
+       int best_option, best_distance;
+       int i, distance;
+       char *name;
+
+       if (!strlen(opt))
+               return;
+
+       name = strdup(opt);
+       i = 0;
+       while (name[i] != '\0' && name[i] != '=')
+               i++;
+       name[i] = '\0';
+
+       best_option = -1;
+       best_distance = INT_MAX;
+       i = 0;
+       while (fio_options[i].name) {
+               distance = string_distance(name, fio_options[i].name);
+               if (distance < best_distance) {
+                       best_distance = distance;
+                       best_option = i;
+               }
+               i++;
+       }
+
+       if (best_option != -1)
+               log_err("Did you mean %s?\n", fio_options[best_option].name);
+
+       free(name);
+}
+
 int fio_options_parse(struct thread_data *td, char **opts, int num_opts,
                        int dump_cmdline)
 {
@@ -3668,6 +3979,9 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts,
                int newret = parse_option(opts_copy[i], opts[i], fio_options,
                                                &o, td, dump_cmdline);
 
+               if (!newret && o)
+                       fio_option_mark_set(&td->o, o);
+
                if (opts_copy[i]) {
                        if (newret && !o) {
                                unknown++;
@@ -3689,6 +4003,7 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts,
                for (i = 0; i < num_opts; i++) {
                        struct fio_option *o = NULL;
                        int newret = 1;
+
                        if (!opts_copy[i])
                                continue;
 
@@ -3698,9 +4013,10 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts,
                                                      td->eo, dump_cmdline);
 
                        ret |= newret;
-                       if (!o)
+                       if (!o) {
                                log_err("Bad option <%s>\n", opts[i]);
-
+                               show_closest_option(opts[i]);
+                       }
                        free(opts_copy[i]);
                        opts_copy[i] = NULL;
                }
@@ -3712,7 +4028,18 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts,
 
 int fio_cmd_option_parse(struct thread_data *td, const char *opt, char *val)
 {
-       return parse_cmd_option(opt, val, fio_options, td);
+       int ret;
+
+       ret = parse_cmd_option(opt, val, fio_options, td);
+       if (!ret) {
+               struct fio_option *o;
+
+               o = find_option(fio_options, opt);
+               if (o)
+                       fio_option_mark_set(&td->o, o);
+       }
+
+       return ret;
 }
 
 int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt,
@@ -3723,6 +4050,7 @@ int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt,
 
 void fio_fill_default_options(struct thread_data *td)
 {
+       td->o.magic = OPT_MAGIC;
        fill_default_options(td, fio_options);
 }
 
@@ -3740,7 +4068,7 @@ void options_mem_dupe(void *data, struct fio_option *options)
                if (o->type != FIO_OPT_STR_STORE)
                        continue;
 
-               ptr = td_var(data, o->off1);
+               ptr = td_var(data, o, o->off1);
                if (*ptr)
                        *ptr = strdup(*ptr);
        }
@@ -3767,7 +4095,16 @@ unsigned int fio_get_kb_base(void *data)
        struct thread_options *o = data;
        unsigned int kb_base = 0;
 
-       if (o)
+       /*
+        * This is a hack... For private options, *data is not holding
+        * a pointer to the thread_options, but to private data. This means
+        * we can't safely dereference it, but magic is first so mem wise
+        * it is valid. But this also means that if the job first sets
+        * kb_base and expects that to be honored by private options,
+        * it will be disappointed. We will return the global default
+        * for this.
+        */
+       if (o && o->magic == OPT_MAGIC)
                kb_base = o->kb_base;
        if (!kb_base)
                kb_base = 1024;
@@ -3786,7 +4123,13 @@ int add_option(struct fio_option *o)
                __o++;
        }
 
+       if (opt_index + 1 == FIO_MAX_OPTS) {
+               log_err("fio: FIO_MAX_OPTS is too small\n");
+               return 1;
+       }
+
        memcpy(&fio_options[opt_index], o, sizeof(*o));
+       fio_options[opt_index + 1].name = NULL;
        return 0;
 }
 
@@ -3858,3 +4201,36 @@ struct fio_option *fio_option_find(const char *name)
        return find_option(fio_options, name);
 }
 
+int __fio_option_is_set(struct thread_options *o, unsigned int off1)
+{
+       unsigned int opt_off, index, offset;
+       struct fio_option *opt = NULL;
+       int i;
+
+       for (i = 0; fio_options[i].name; i++) {
+               if (off1 == fio_options[i].off1) {
+                       opt = &fio_options[i];
+                       break;
+               }
+       }
+
+       if (!opt) {
+               log_err("fio: no option found at offset %u\n", off1);
+               return 0;
+       }
+
+       opt_off = opt - &fio_options[0];
+       index = opt_off / (8 * sizeof(uint64_t));
+       offset = opt_off & ((8 * sizeof(uint64_t)) - 1);
+       return (o->set_options[index] & (1UL << offset)) != 0;
+}
+
+void fio_option_mark_set(struct thread_options *o, struct fio_option *opt)
+{
+       unsigned int opt_off, index, offset;
+
+       opt_off = opt - &fio_options[0];
+       index = opt_off / (8 * sizeof(uint64_t));
+       offset = opt_off & ((8 * sizeof(uint64_t)) - 1);
+       o->set_options[index] |= 1UL << offset;
+}