root/src/xz/options.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. parse_options
  2. set_delta
  3. options_delta
  4. set_bcj
  5. options_bcj
  6. lzma_attribute
  7. set_lzma
  8. options_lzma

///////////////////////////////////////////////////////////////////////////////
//
/// \file       options.c
/// \brief      Parser for filter-specific options
//
//  Author:     Lasse Collin
//
//  This file has been put into the public domain.
//  You can do whatever you want with this file.
//
///////////////////////////////////////////////////////////////////////////////

#include "private.h"


///////////////////
// Generic stuff //
///////////////////

typedef struct {
        const char *name;
        uint64_t id;
} name_id_map;


typedef struct {
        const char *name;
        const name_id_map *map;
        uint64_t min;
        uint64_t max;
} option_map;


/// Parses option=value pairs that are separated with colons, semicolons,
/// or commas: opt=val:opt=val;opt=val,opt=val
///
/// Each option is a string, that is converted to an integer using the
/// index where the option string is in the array.
///
/// Value can be
///  - a string-id map mapping a list of possible string values to integers
///    (opts[i].map != NULL, opts[i].min and opts[i].max are ignored);
///  - a number with minimum and maximum value limit
///    (opts[i].map == NULL && opts[i].min != UINT64_MAX);
///  - a string that will be parsed by the filter-specific code
///    (opts[i].map == NULL && opts[i].min == UINT64_MAX, opts[i].max ignored)
///
/// When parsing both option and value succeed, a filter-specific function
/// is called, which should update the given value to filter-specific
/// options structure.
///
/// \param      str     String containing the options from the command line
/// \param      opts    Filter-specific option map
/// \param      set     Filter-specific function to update filter_options
/// \param      filter_options  Pointer to filter-specific options structure
///
/// \return     Returns only if no errors occur.
///
static void
parse_options(const char *str, const option_map *opts,
                void (*set)(void *filter_options,
                        uint32_t key, uint64_t value, const char *valuestr),
                void *filter_options)
{
        if (str == NULL || str[0] == '\0')
                return;

        char *s = xstrdup(str);
        char *name = s;

        while (*name != '\0') {
                if (*name == ',') {
                        ++name;
                        continue;
                }

                char *split = strchr(name, ',');
                if (split != NULL)
                        *split = '\0';

                char *value = strchr(name, '=');
                if (value != NULL)
                        *value++ = '\0';

                if (value == NULL || value[0] == '\0')
                        message_fatal(_("%s: Options must be `name=value' "
                                        "pairs separated with commas"), str);

                // Look for the option name from the option map.
                size_t i = 0;
                while (true) {
                        if (opts[i].name == NULL)
                                message_fatal(_("%s: Invalid option name"),
                                                name);

                        if (strcmp(name, opts[i].name) == 0)
                                break;

                        ++i;
                }

                // Option was found from the map. See how we should handle it.
                if (opts[i].map != NULL) {
                        // value is a string which we should map
                        // to an integer.
                        size_t j;
                        for (j = 0; opts[i].map[j].name != NULL; ++j) {
                                if (strcmp(opts[i].map[j].name, value) == 0)
                                        break;
                        }

                        if (opts[i].map[j].name == NULL)
                                message_fatal(_("%s: Invalid option value"),
                                                value);

                        set(filter_options, i, opts[i].map[j].id, value);

                } else if (opts[i].min == UINT64_MAX) {
                        // value is a special string that will be
                        // parsed by set().
                        set(filter_options, i, 0, value);

                } else {
                        // value is an integer.
                        const uint64_t v = str_to_uint64(name, value,
                                        opts[i].min, opts[i].max);
                        set(filter_options, i, v, value);
                }

                // Check if it was the last option.
                if (split == NULL)
                        break;

                name = split + 1;
        }

        free(s);
        return;
}


///////////
// Delta //
///////////

enum {
        OPT_DIST,
};


static void
set_delta(void *options, uint32_t key, uint64_t value,
                const char *valuestr lzma_attribute((__unused__)))
{
        lzma_options_delta *opt = options;
        switch (key) {
        case OPT_DIST:
                opt->dist = value;
                break;
        }
}


extern lzma_options_delta *
options_delta(const char *str)
{
        static const option_map opts[] = {
                { "dist",     NULL,  LZMA_DELTA_DIST_MIN,
                                     LZMA_DELTA_DIST_MAX },
                { NULL,       NULL,  0, 0 }
        };

        lzma_options_delta *options = xmalloc(sizeof(lzma_options_delta));
        *options = (lzma_options_delta){
                // It's hard to give a useful default for this.
                .type = LZMA_DELTA_TYPE_BYTE,
                .dist = LZMA_DELTA_DIST_MIN,
        };

        parse_options(str, opts, &set_delta, options);

        return options;
}


/////////
// BCJ //
/////////

enum {
        OPT_START_OFFSET,
};


static void
set_bcj(void *options, uint32_t key, uint64_t value,
                const char *valuestr lzma_attribute((__unused__)))
{
        lzma_options_bcj *opt = options;
        switch (key) {
        case OPT_START_OFFSET:
                opt->start_offset = value;
                break;
        }
}


extern lzma_options_bcj *
options_bcj(const char *str)
{
        static const option_map opts[] = {
                { "start",    NULL,  0, UINT32_MAX },
                { NULL,       NULL,  0, 0 }
        };

        lzma_options_bcj *options = xmalloc(sizeof(lzma_options_bcj));
        *options = (lzma_options_bcj){
                .start_offset = 0,
        };

        parse_options(str, opts, &set_bcj, options);

        return options;
}


//////////
// LZMA //
//////////

enum {
        OPT_PRESET,
        OPT_DICT,
        OPT_LC,
        OPT_LP,
        OPT_PB,
        OPT_MODE,
        OPT_NICE,
        OPT_MF,
        OPT_DEPTH,
};


static void lzma_attribute((__noreturn__))
error_lzma_preset(const char *valuestr)
{
        message_fatal(_("Unsupported LZMA1/LZMA2 preset: %s"), valuestr);
}


static void
set_lzma(void *options, uint32_t key, uint64_t value, const char *valuestr)
{
        lzma_options_lzma *opt = options;

        switch (key) {
        case OPT_PRESET: {
                if (valuestr[0] < '0' || valuestr[0] > '9')
                        error_lzma_preset(valuestr);

                uint32_t preset = valuestr[0] - '0';

                // Currently only "e" is supported as a modifier,
                // so keep this simple for now.
                if (valuestr[1] != '\0') {
                        if (valuestr[1] == 'e')
                                preset |= LZMA_PRESET_EXTREME;
                        else
                                error_lzma_preset(valuestr);

                        if (valuestr[2] != '\0')
                                error_lzma_preset(valuestr);
                }

                if (lzma_lzma_preset(options, preset))
                        error_lzma_preset(valuestr);

                break;
        }

        case OPT_DICT:
                opt->dict_size = value;
                break;

        case OPT_LC:
                opt->lc = value;
                break;

        case OPT_LP:
                opt->lp = value;
                break;

        case OPT_PB:
                opt->pb = value;
                break;

        case OPT_MODE:
                opt->mode = value;
                break;

        case OPT_NICE:
                opt->nice_len = value;
                break;

        case OPT_MF:
                opt->mf = value;
                break;

        case OPT_DEPTH:
                opt->depth = value;
                break;
        }
}


extern lzma_options_lzma *
options_lzma(const char *str)
{
        static const name_id_map modes[] = {
                { "fast",   LZMA_MODE_FAST },
                { "normal", LZMA_MODE_NORMAL },
                { NULL,     0 }
        };

        static const name_id_map mfs[] = {
                { "hc3", LZMA_MF_HC3 },
                { "hc4", LZMA_MF_HC4 },
                { "bt2", LZMA_MF_BT2 },
                { "bt3", LZMA_MF_BT3 },
                { "bt4", LZMA_MF_BT4 },
                { NULL,  0 }
        };

        static const option_map opts[] = {
                { "preset", NULL,   UINT64_MAX, 0 },
                { "dict",   NULL,   LZMA_DICT_SIZE_MIN,
                                (UINT32_C(1) << 30) + (UINT32_C(1) << 29) },
                { "lc",     NULL,   LZMA_LCLP_MIN, LZMA_LCLP_MAX },
                { "lp",     NULL,   LZMA_LCLP_MIN, LZMA_LCLP_MAX },
                { "pb",     NULL,   LZMA_PB_MIN, LZMA_PB_MAX },
                { "mode",   modes,  0, 0 },
                { "nice",   NULL,   2, 273 },
                { "mf",     mfs,    0, 0 },
                { "depth",  NULL,   0, UINT32_MAX },
                { NULL,     NULL,   0, 0 }
        };

        lzma_options_lzma *options = xmalloc(sizeof(lzma_options_lzma));
        if (lzma_lzma_preset(options, LZMA_PRESET_DEFAULT))
                message_bug();

        parse_options(str, opts, &set_lzma, options);

        if (options->lc + options->lp > LZMA_LCLP_MAX)
                message_fatal(_("The sum of lc and lp must not exceed 4"));

        const uint32_t nice_len_min = options->mf & 0x0F;
        if (options->nice_len < nice_len_min)
                message_fatal(_("The selected match finder requires at "
                                "least nice=%" PRIu32), nice_len_min);

        return options;
}

/* [<][>][^][v][top][bottom][index][help] */