static error_t root_parse()

in cli/bpfcov.c [203:392]


static error_t root_parse(int key, char *arg, struct argp_state *state)
{
    struct root_args *args = state->input;

    char str[2];
    log_debu(args, "parsing <root> %s = '%s'\n", argp_key(key, str), arg ? arg : "(null)");

    switch (key)
    {

    // Initialization
    case ARGP_KEY_INIT:
        args->bpffs = "/sys/fs/bpf";
        // args->verbosity = 0; // It needs to be set before the parsing starts
        args->command = NULL;
        args->program = calloc(PATH_MAX, sizeof(char *));
        args->output = NULL;
        args->unpin = false;
        break;

    case ROOT_BPFFS_OPT_KEY:
        if (strlen(arg) > 0)
        {
            strip_trailing_char(arg, '/');
            args->bpffs = arg;
            break;
        }
        argp_error(state, "option '--%s' requires a %s", ROOT_BPFFS_OPT_LONG, ROOT_BPFFS_OPT_ARG);
        break;

    case ROOT_VERBOSITY_OPT_KEY:
        if (arg)
        {
            errno = 0;
            char *end;
            long num = strtol(arg, &end, 10);
            if (end == arg)
            {
                argp_error(state, "option '--%s' requires a numeric %s", ROOT_VERBOSITY_OPT_LONG, ROOT_VERBOSITY_OPT_ARG);
            }
            if (num < 0 || num > 3)
            {
                argp_error(state, "option '--%s' requires a %s value in [0,3]", ROOT_VERBOSITY_OPT_LONG, ROOT_VERBOSITY_OPT_ARG);
            }

            args->verbosity = (int)num;
        }
        else
        {
            args->verbosity++;
        }
        break;

    case ARGP_KEY_ARG:
        assert(arg);

        /**/ if (strncmp(arg, "run", 3) == 0)
        {
            args->command = &run;
            run_cmd(state);
        }
        else if (strncmp(arg, "gen", 3) == 0)
        {
            args->command = &gen;
            gen_cmd(state);
        }
        else if (strncmp(arg, "out", 3) == 0)
        {
            args->command = &out;
            out_cmd(state);
        }
        else
        {
            args->program[state->arg_num] = arg;
        }

        break;

    // Args validation
    case ARGP_KEY_END:
        if (state->arg_num == 0)
        {
            argp_state_help(state, state->err_stream, ARGP_HELP_STD_HELP);
        }
        if (args->command != &out && args->program[0] == NULL)
        {
            // This should never happen
            argp_error(state, "unexpected missing <program>");
        }
        break;

    // Final validations, checks, and settings
    case ARGP_KEY_FINI:
        bool is_run = args->command == &run;

        // When the subcommand is <out>
        // - do not validate BPF FS
        // - do not generate pinning paths
        // - do not clean up (<run>) or check (<gen>) pinned maps
        if (args->command == &out)
        {
            break;
        }

        // Check the BPF filesystem
        if (!is_bpffs(args->bpffs))
        {
            argp_error(state, "the BPF filesystem is not mounted at %s", args->bpffs);
        }

        // Create the coverage directory in the BPF filesystem
        char cov_root[PATH_MAX];
        int cov_root_len = snprintf(cov_root, PATH_MAX, "%s/%s", args->bpffs, "cov");
        if (cov_root_len >= PATH_MAX)
        {
            argp_error(state, "coverage root path too long");
        }
        if (is_run && mkdir(cov_root, 0700) && errno != EEXIST)
        {
            argp_error(state, "could not create '%s'", cov_root);
        }
        args->cov_root = cov_root;

        // Obtain the program name and create a directory in the BPF filesystem for it
        char *prog_name = basename(args->program[0]);
        char prog_root[PATH_MAX];
        int prog_root_len = snprintf(prog_root, PATH_MAX, "%s/%s", cov_root, prog_name);
        if (prog_root_len >= PATH_MAX)
        {
            argp_error(state, "program root path too long");
        }
        char *prog_root_sane = strdup(prog_root);
        replace_with(prog_root_sane, '.', '_'); // Sanitize because BPF FS doesn't accept dots
        if (is_run && mkdir(prog_root_sane, 0700) && errno != EEXIST)
        {
            argp_error(state, "could not create '%s'", prog_root_sane);
        }
        args->prog_root = prog_root_sane;
        log_info(args, "root directory for map pins at '%s'\n", prog_root_sane);

        // Create pinning path for the counters map
        char pin_profc[PATH_MAX];
        int pin_profc_len = snprintf(pin_profc, PATH_MAX, "%s/%s", prog_root_sane, "profc");
        if (pin_profc_len >= PATH_MAX)
        {
            argp_error(state, "counters pinning path too long");
        }
        args->pin[0] = pin_profc;

        // Create pinning path for the data map
        char pin_profd[PATH_MAX];
        int pin_profd_len = snprintf(pin_profd, PATH_MAX, "%s/%s", prog_root_sane, "profd");
        if (pin_profd_len >= PATH_MAX)
        {
            argp_error(state, "data pinning path too long");
        }
        args->pin[1] = pin_profd;

        // Create pinning path for the names map
        char pin_profn[PATH_MAX];
        int pin_profn_len = snprintf(pin_profn, PATH_MAX, "%s/%s", prog_root_sane, "profn");
        if (pin_profn_len >= PATH_MAX)
        {
            argp_error(state, "names pinning path too long");
        }
        args->pin[2] = pin_profn;

        // Create pinning path for the coverage mapping header
        char pin_covmap[PATH_MAX];
        int pin_covmap_len = snprintf(pin_covmap, PATH_MAX, "%s/%s", prog_root_sane, "covmap");
        if (pin_covmap_len >= PATH_MAX)
        {
            argp_error(state, "coverage mapping header path too long");
        }
        args->pin[3] = pin_covmap;

        // Check whether the map pinning paths already exist:
        // - unpin them in case they do exist and the current subcommand is `run`
        // - error out in case the do not exist and the current subcommand is `gen`
        handle_map_pins(args, state, is_run);

        break;

    default:
        log_debu(args, "parsing <root> UNKNOWN = '%s'\n", arg ? arg : "(null)");
        return ARGP_ERR_UNKNOWN;
    }

    return 0;
}