2.5 Option Callbacks

Option callbacks are useful when you have more advanced option parsing needs. Each option argument type has a different callback. There are also callbacks for options which don’t take arguments: callback_none for non-negatable options, and callback_negatable for negatable options (see Negatable Options). All callback names mentioned are members of the MU_OPT structure.

For a callback that is called when a positional argument is seen, use mu_opt_context_set_arg_callback (see Ordered Option Parsing).

The callback names and prototypes for each argument type are listed below (although MU_OPT_NONE is not a type, and should be passed in the has_arg field of the MU_OPT structure, not the arg_type field):

MU_OPT_NONE

If the negatable field is zero (see Option Structure):

int (*callback_none) (void *data, char *err)

Otherwise, if negatable is nonzero:

int (*callback_negatable) (int value, void *data, char *err)

MU_OPT_BOOL

int (*callback_bool) (int has_arg, int arg, void *data, char *err)

MU_OPT_INT

int (*callback_int) (int has_arg, long arg, void *data, char *err)

MU_OPT_FLOAT

int (*callback_float) (int has_arg, double arg, void *data, char *err)

MU_OPT_STRING

int (*callback_string) (int has_arg, const char *arg, void *data, char *err)

MU_OPT_FILE

int (*callback_file) (int has_arg, const char *filename, FILE *file, void *data, char *err)

MU_OPT_DIRECTORY

int (*callback_directory) (int has_arg, const char *dirname, DIR *directory, void *data, char *err)

MU_OPT_ENUM

int (*callback_enum) (int has_arg, int arg, void *data, char *err)

MU_OPT_SUBOPT

int (*callback_subopt) (int has_arg,   void *data, char *err)

A callback will be called as soon as an option is found, so callbacks are guaranteed to be called in the same order as the options appear on the command line. This means that if an option takes suboptions as arguments, the callback for the main option will be called before the callbacks for the suboptions (see Parsing Suboptions). The has_arg parameter will be passed as 1 if the option has an argument, or 0 if the option doesn’t have an argument (except for callback_none which doesn’t have a has_arg parameter).

If the option has an argument, arg will be set to that argument, except in the case of suboptions (see Parsing Suboptions). In the case of callaback_file and callback_directory, filename or dirname will be set to the name of the file or directory respectively.4 For callback_negatable, value will be zero if the option was negated, or nonzero if it wasn’t (see Negatable Options).

If you need to provide extra information to a callback, provide it in the cb_data field of the MU_OPT structure. This will then be passed as the data parameter to a callback. Note: a callback should not free this parameter even if it is dynamically allocated. In the case that cb_data is dynamically allocated and/or contains dynamically allocated data, you should also set the cb_data_destructor field to a function which will free all dynamically allocated data in cb_data.

When you call mu_opt_context_free (see Parsing Options and Environment) or mu_subopt_context_free (see Parsing Suboptions), each cb_data_destructor field is called with the corresponding cb_data in order to free that data. If an error occurs while freeing callback data (for example, an error closing a file), cb_data_destructor should return nonzero. Otherwise, cb_data_destructor should return zero.

For callback_file and callback_directory, the file or directory argument will be closed after the callback returns if you leave the arg field of the MU_OPT structure as NULL (see Option Structure). So you must not close the file or directory argument in the callback.

You also must not use the cb_data field to get the opened file/directory. For example, the following code is wrong:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <mu/options.h>
#include <mu/safe.h>            /* For mu_opt_context_x{new,free} */

int file_callback(int has_arg, const char *filename,
                  FILE *file, void *data, char *err) {
  /* Make sure the file is named "foo". */
  if (strcmp(filename, "foo")) {
    snprintf(err, MU_OPT_ERR_MAX, "file is not named \"foo\"");
    return 1;
  }

  /* It is named "foo"; return `file' in `*data'.
     This is WRONG! Do not do this! */
  *(FILE **)data = file;

  return 0;
}

int main(int argc, char **argv) {
  FILE *file = NULL;
  char buf[256];
  size_t size;
  int ret;
  const MU_OPT options[] = {
    {
     .short_opt     = "f",
     .long_opt      = "file",
     .has_arg       = MU_OPT_REQUIRED,
     .arg_type      = MU_OPT_FILE,
     .file_mode     = "r",
     .callback_file = file_callback,
     .cb_data       = &file
    },
    { 0 }
  };
  MU_OPT_CONTEXT *context;

  /* Parse the options. */
  context = mu_opt_context_xnew(argc, argv, options, MU_OPT_PERMUTE);
  ret = mu_parse_opts(context);
  mu_opt_context_xfree(context);
  if (MU_OPT_ERR(ret))
    return 1;

  if (!file) {
    /* We weren't passed the `-f' option. */
    return 0;
  }

  /* Read the file. This invokes UNDEFINED BEHAVIOR because
     `file' was already closed by `mu_parse_opts'! */
  size = fread(buf, sizeof(*buf), sizeof(buf), file);
  if (ferror(file)) {
    fprintf(stderr, "%s: cannot read foo: %s\n",
            argv[0], strerror(errno));
    return 1;
  }
  fclose(file);

  /* Print the contents of the file to standard output. */
  fwrite(buf, sizeof(*buf), size, stdout);

  return 0;
}

When this program is run, it invokes undefined behavior. The correct way to do this is to not use the cb_data field, and instead set the arg field to &file. This way, mu_parse_opts will not close the file or directory after the callback returns.

If a callback needs to indicate an error (if its argument is in the wrong format, for example), it should return nonzero. Otherwise, on success, it should return 0. If a callback returns nonzero, you must write an error string to err which will then be used by mu_parse_opts to print an error message. You must not write more than MU_OPT_ERR_MAX characters to err (including the terminating null byte). However, if you write exactly MU_OPT_ERR_MAX bytes to err, you need not terminate err with a null byte.

Below is an example of how to use callbacks. Of course, this trivial example would be better expressed using enumerated argument parsing (see Parsing Enumerated Arguments to Options).

#include <stdio.h>
#include <string.h>
#include <mu/options.h>
#include <mu/safe.h>            /* For mu_opt_context_x{new,free} */

enum selection { FOO, BAR, BAZ };

/* Parse a selection. `has_arg' will always be true because the option
   takes a required argument. */
int parse_selection(int has_arg, const char *arg,
                    void *data, char *err) {
  enum selection sel;

  if (!strcmp(arg, "foo"))
    sel = FOO;
  else if (!strcmp(arg, "bar"))
    sel = BAR;
  else if (!strcmp(arg, "baz"))
    sel = BAZ;
  else {
    /* `err' will be used by `mu_parse_opts' to print an error
       message. */
    snprintf(err, MU_OPT_ERR_MAX, "invalid selection: %s", arg);
    /* Indicate to `mu_parse_opts' that an error occured by returning
       a nonzero value. */
    return 1;
  }

  /* Store the selection in `*data'. */
  *(enum selection *)data = sel;

  /* Success! */
  return 0;
}

int main(int argc, char **argv) {
  enum selection sel;
  int found_sel;
  int ret;
  const MU_OPT options[] = {
    {
     .short_opt       = "s",
     .long_opt        = "selection",
     .has_arg         = MU_OPT_REQUIRED,
     .arg_type        = MU_OPT_STRING,
     .found_arg       = &found_sel,
     .callback_string = parse_selection,
     .cb_data         = &sel
    },
    { 0 }
  };
  MU_OPT_CONTEXT *context;

  /* Parse the options. */
  context = mu_opt_context_xnew(argc, argv, options, MU_OPT_PERMUTE);
  ret = mu_parse_opts(context);
  mu_opt_context_xfree(context);
  if (MU_OPT_ERR(ret))
    return 1; /* `mu_parse_opts' will print an error message for us */

  if (found_sel) {
    /* Print the selection. */
    fputs("You selected: ", stdout);
    switch (sel) {
    case FOO:
      puts("FOO");
      break;
    case BAR:
      puts("BAR");
      break;
    case BAZ:
      puts("BAZ");
      break;
    default:
      puts("an unknown value!"); /* This should not happen */
    }
  }
  else
    puts("You didn't select anything.");

  return 0;
}

Here is what the output of the example program looks like:

$ ./option-callback
-| You didn't select anything.
$ ./option-callback -s foo
-| You selected: FOO
$ ./option-callback --selection=bar
-| You selected: BAR
$ ./option-callback -s qux
error→ ./option-callback: invalid selection: qux
$ ./option-callback -s
error→ ./option-callback: '-s': option requires argument

Footnotes

(4)

However, for callback_file, filename might be ‘<stdin>’ or ‘<stdout>’ when file is standard input or standard output respectively. See Option Argument Types.