Home | History | Annotate | Line # | Download | only in xz
      1 // SPDX-License-Identifier: 0BSD
      2 
      3 ///////////////////////////////////////////////////////////////////////////////
      4 //
      5 /// \file       options.c
      6 /// \brief      Parser for filter-specific options
      7 //
      8 //  Author:     Lasse Collin
      9 //
     10 ///////////////////////////////////////////////////////////////////////////////
     11 
     12 #include "private.h"
     13 
     14 
     15 ///////////////////
     16 // Generic stuff //
     17 ///////////////////
     18 
     19 typedef struct {
     20 	const char *name;
     21 	uint64_t id;
     22 } name_id_map;
     23 
     24 
     25 typedef struct {
     26 	const char *name;
     27 	const name_id_map *map;
     28 	uint64_t min;
     29 	uint64_t max;
     30 } option_map;
     31 
     32 
     33 /// Parses option=value pairs that are separated with commas:
     34 /// opt=val,opt=val,opt=val
     35 ///
     36 /// Each option is a string, that is converted to an integer using the
     37 /// index where the option string is in the array.
     38 ///
     39 /// Value can be
     40 ///  - a string-id map mapping a list of possible string values to integers
     41 ///    (opts[i].map != NULL, opts[i].min and opts[i].max are ignored);
     42 ///  - a number with minimum and maximum value limit
     43 ///    (opts[i].map == NULL && opts[i].min != UINT64_MAX);
     44 ///  - a string that will be parsed by the filter-specific code
     45 ///    (opts[i].map == NULL && opts[i].min == UINT64_MAX, opts[i].max ignored)
     46 ///
     47 /// When parsing both option and value succeed, a filter-specific function
     48 /// is called, which should update the given value to filter-specific
     49 /// options structure.
     50 ///
     51 /// This returns only if no errors occur.
     52 ///
     53 /// \param      str     String containing the options from the command line
     54 /// \param      opts    Filter-specific option map
     55 /// \param      set     Filter-specific function to update filter_options
     56 /// \param      filter_options  Pointer to filter-specific options structure
     57 ///
     58 static void
     59 parse_options(const char *str, const option_map *opts,
     60 		void (*set)(void *filter_options,
     61 			unsigned key, uint64_t value, const char *valuestr),
     62 		void *filter_options)
     63 {
     64 	if (str == NULL || str[0] == '\0')
     65 		return;
     66 
     67 	char *s = xstrdup(str);
     68 	char *name = s;
     69 
     70 	while (*name != '\0') {
     71 		if (*name == ',') {
     72 			++name;
     73 			continue;
     74 		}
     75 
     76 		char *split = strchr(name, ',');
     77 		if (split != NULL)
     78 			*split = '\0';
     79 
     80 		char *value = strchr(name, '=');
     81 		if (value != NULL)
     82 			*value++ = '\0';
     83 
     84 		if (value == NULL || value[0] == '\0')
     85 			message_fatal(_("%s: %s"), tuklib_mask_nonprint(str),
     86 					_("Options must be 'name=value' "
     87 					"pairs separated with commas"));
     88 
     89 		// Look for the option name from the option map.
     90 		unsigned i = 0;
     91 		while (true) {
     92 			if (opts[i].name == NULL)
     93 				message_fatal(_("%s: Invalid option name"),
     94 						tuklib_mask_nonprint(name));
     95 
     96 			if (strcmp(name, opts[i].name) == 0)
     97 				break;
     98 
     99 			++i;
    100 		}
    101 
    102 		// Option was found from the map. See how we should handle it.
    103 		if (opts[i].map != NULL) {
    104 			// value is a string which we should map
    105 			// to an integer.
    106 			unsigned j;
    107 			for (j = 0; opts[i].map[j].name != NULL; ++j) {
    108 				if (strcmp(opts[i].map[j].name, value) == 0)
    109 					break;
    110 			}
    111 
    112 			if (opts[i].map[j].name == NULL)
    113 				message_fatal(_("%s: %s"),
    114 						tuklib_mask_nonprint(value),
    115 						_("Invalid option value"));
    116 
    117 			set(filter_options, i, opts[i].map[j].id, value);
    118 
    119 		} else if (opts[i].min == UINT64_MAX) {
    120 			// value is a special string that will be
    121 			// parsed by set().
    122 			set(filter_options, i, 0, value);
    123 
    124 		} else {
    125 			// value is an integer.
    126 			const uint64_t v = str_to_uint64(name, value,
    127 					opts[i].min, opts[i].max);
    128 			set(filter_options, i, v, value);
    129 		}
    130 
    131 		// Check if it was the last option.
    132 		if (split == NULL)
    133 			break;
    134 
    135 		name = split + 1;
    136 	}
    137 
    138 	free(s);
    139 	return;
    140 }
    141 
    142 
    143 ///////////
    144 // Delta //
    145 ///////////
    146 
    147 enum {
    148 	OPT_DIST,
    149 };
    150 
    151 
    152 static void
    153 set_delta(void *options, unsigned key, uint64_t value,
    154 		const char *valuestr lzma_attribute((__unused__)))
    155 {
    156 	lzma_options_delta *opt = options;
    157 	switch (key) {
    158 	case OPT_DIST:
    159 		opt->dist = value;
    160 		break;
    161 	}
    162 }
    163 
    164 
    165 extern lzma_options_delta *
    166 options_delta(const char *str)
    167 {
    168 	static const option_map opts[] = {
    169 		{ "dist",     NULL,  LZMA_DELTA_DIST_MIN,
    170 		                     LZMA_DELTA_DIST_MAX },
    171 		{ NULL,       NULL,  0, 0 }
    172 	};
    173 
    174 	lzma_options_delta *options = xmalloc(sizeof(lzma_options_delta));
    175 	*options = (lzma_options_delta){
    176 		// It's hard to give a useful default for this.
    177 		.type = LZMA_DELTA_TYPE_BYTE,
    178 		.dist = LZMA_DELTA_DIST_MIN,
    179 	};
    180 
    181 	parse_options(str, opts, &set_delta, options);
    182 
    183 	return options;
    184 }
    185 
    186 
    187 /////////
    188 // BCJ //
    189 /////////
    190 
    191 enum {
    192 	OPT_START_OFFSET,
    193 };
    194 
    195 
    196 static void
    197 set_bcj(void *options, unsigned key, uint64_t value,
    198 		const char *valuestr lzma_attribute((__unused__)))
    199 {
    200 	lzma_options_bcj *opt = options;
    201 	switch (key) {
    202 	case OPT_START_OFFSET:
    203 		opt->start_offset = value;
    204 		break;
    205 	}
    206 }
    207 
    208 
    209 extern lzma_options_bcj *
    210 options_bcj(const char *str)
    211 {
    212 	static const option_map opts[] = {
    213 		{ "start",    NULL,  0, UINT32_MAX },
    214 		{ NULL,       NULL,  0, 0 }
    215 	};
    216 
    217 	lzma_options_bcj *options = xmalloc(sizeof(lzma_options_bcj));
    218 	*options = (lzma_options_bcj){
    219 		.start_offset = 0,
    220 	};
    221 
    222 	parse_options(str, opts, &set_bcj, options);
    223 
    224 	return options;
    225 }
    226 
    227 
    228 //////////
    229 // LZMA //
    230 //////////
    231 
    232 enum {
    233 	OPT_PRESET,
    234 	OPT_DICT,
    235 	OPT_LC,
    236 	OPT_LP,
    237 	OPT_PB,
    238 	OPT_MODE,
    239 	OPT_NICE,
    240 	OPT_MF,
    241 	OPT_DEPTH,
    242 };
    243 
    244 
    245 tuklib_attr_noreturn
    246 static void
    247 error_lzma_preset(const char *valuestr)
    248 {
    249 	message_fatal(_("Unsupported LZMA1/LZMA2 preset: %s"),
    250 			tuklib_mask_nonprint(valuestr));
    251 }
    252 
    253 
    254 static void
    255 set_lzma(void *options, unsigned key, uint64_t value, const char *valuestr)
    256 {
    257 	lzma_options_lzma *opt = options;
    258 
    259 	switch (key) {
    260 	case OPT_PRESET: {
    261 		if (valuestr[0] < '0' || valuestr[0] > '9')
    262 			error_lzma_preset(valuestr);
    263 
    264 		uint32_t preset = (uint32_t)(valuestr[0] - '0');
    265 
    266 		// Currently only "e" is supported as a modifier,
    267 		// so keep this simple for now.
    268 		if (valuestr[1] != '\0') {
    269 			if (valuestr[1] == 'e')
    270 				preset |= LZMA_PRESET_EXTREME;
    271 			else
    272 				error_lzma_preset(valuestr);
    273 
    274 			if (valuestr[2] != '\0')
    275 				error_lzma_preset(valuestr);
    276 		}
    277 
    278 		if (lzma_lzma_preset(options, preset))
    279 			error_lzma_preset(valuestr);
    280 
    281 		break;
    282 	}
    283 
    284 	case OPT_DICT:
    285 		opt->dict_size = value;
    286 		break;
    287 
    288 	case OPT_LC:
    289 		opt->lc = value;
    290 		break;
    291 
    292 	case OPT_LP:
    293 		opt->lp = value;
    294 		break;
    295 
    296 	case OPT_PB:
    297 		opt->pb = value;
    298 		break;
    299 
    300 	case OPT_MODE:
    301 		opt->mode = value;
    302 		break;
    303 
    304 	case OPT_NICE:
    305 		opt->nice_len = value;
    306 		break;
    307 
    308 	case OPT_MF:
    309 		opt->mf = value;
    310 		break;
    311 
    312 	case OPT_DEPTH:
    313 		opt->depth = value;
    314 		break;
    315 	}
    316 }
    317 
    318 
    319 extern lzma_options_lzma *
    320 options_lzma(const char *str)
    321 {
    322 	static const name_id_map modes[] = {
    323 		{ "fast",   LZMA_MODE_FAST },
    324 		{ "normal", LZMA_MODE_NORMAL },
    325 		{ NULL,     0 }
    326 	};
    327 
    328 	static const name_id_map mfs[] = {
    329 		{ "hc3", LZMA_MF_HC3 },
    330 		{ "hc4", LZMA_MF_HC4 },
    331 		{ "bt2", LZMA_MF_BT2 },
    332 		{ "bt3", LZMA_MF_BT3 },
    333 		{ "bt4", LZMA_MF_BT4 },
    334 		{ NULL,  0 }
    335 	};
    336 
    337 	static const option_map opts[] = {
    338 		{ "preset", NULL,   UINT64_MAX, 0 },
    339 		{ "dict",   NULL,   LZMA_DICT_SIZE_MIN,
    340 				(UINT32_C(1) << 30) + (UINT32_C(1) << 29) },
    341 		{ "lc",     NULL,   LZMA_LCLP_MIN, LZMA_LCLP_MAX },
    342 		{ "lp",     NULL,   LZMA_LCLP_MIN, LZMA_LCLP_MAX },
    343 		{ "pb",     NULL,   LZMA_PB_MIN, LZMA_PB_MAX },
    344 		{ "mode",   modes,  0, 0 },
    345 		{ "nice",   NULL,   2, 273 },
    346 		{ "mf",     mfs,    0, 0 },
    347 		{ "depth",  NULL,   0, UINT32_MAX },
    348 		{ NULL,     NULL,   0, 0 }
    349 	};
    350 
    351 	lzma_options_lzma *options = xmalloc(sizeof(lzma_options_lzma));
    352 	if (lzma_lzma_preset(options, LZMA_PRESET_DEFAULT))
    353 		message_bug();
    354 
    355 	parse_options(str, opts, &set_lzma, options);
    356 
    357 	if (options->lc + options->lp > LZMA_LCLP_MAX)
    358 		message_fatal(_("The sum of lc and lp must not exceed 4"));
    359 
    360 	return options;
    361 }
    362