1 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 // vim: expandtab:ts=8:sw=4:softtabstop=4:
3 ///////////////////////////////////////////////////////////////////////////////
4 //
5 /// \file       options.c
6 /// \brief      Parser for filter-specific options
7 //
8 //  Author:     Lasse Collin
9 //
10 //  This file has been put into the public domain.
11 //  You can do whatever you want with this file.
12 //
13 ///////////////////////////////////////////////////////////////////////////////
14 
15 #include "private.h"
16 
17 
18 ///////////////////
19 // Generic stuff //
20 ///////////////////
21 
22 typedef struct {
23 	const char *name;
24 	uint64_t id;
25 } name_id_map;
26 
27 
28 typedef struct {
29 	const char *name;
30 	const name_id_map *map;
31 	uint64_t min;
32 	uint64_t max;
33 } option_map;
34 
35 
36 /// Parses option=value pairs that are separated with colons, semicolons,
37 /// or commas: opt=val:opt=val;opt=val,opt=val
38 ///
39 /// Each option is a string, that is converted to an integer using the
40 /// index where the option string is in the array.
41 ///
42 /// Value can be
43 ///  - a string-id map mapping a list of possible string values to integers
44 ///    (opts[i].map != NULL, opts[i].min and opts[i].max are ignored);
45 ///  - a number with minimum and maximum value limit
46 ///    (opts[i].map == NULL && opts[i].min != UINT64_MAX);
47 ///  - a string that will be parsed by the filter-specific code
48 ///    (opts[i].map == NULL && opts[i].min == UINT64_MAX, opts[i].max ignored)
49 ///
50 /// When parsing both option and value succeed, a filter-specific function
51 /// is called, which should update the given value to filter-specific
52 /// options structure.
53 ///
54 /// \param      str     String containing the options from the command line
55 /// \param      opts    Filter-specific option map
56 /// \param      set     Filter-specific function to update filter_options
57 /// \param      filter_options  Pointer to filter-specific options structure
58 ///
59 /// \return     Returns only if no errors occur.
60 ///
61 static void
62 parse_options(const char *str, const option_map *opts,
63 		void (*set)(void *filter_options,
64 			uint32_t key, uint64_t value, const char *valuestr),
65 		void *filter_options)
66 {
67 	if (str == NULL || str[0] == '\0')
68 		return;
69 
70 	char *s = xstrdup(str);
71 	char *name = s;
72 
73 	while (true) {
74 		if (*name == ',') {
75 			if (*++name == '\0')
76 				break;
77 
78 			continue;
79 		}
80 
81 		char *split = strchr(name, ',');
82 		if (split != NULL)
83 			*split = '\0';
84 
85 		char *value = strchr(name, '=');
86 		if (value != NULL)
87 			*value++ = '\0';
88 
89 		if (value == NULL || value[0] == '\0')
90 			message_fatal(_("%s: Options must be `name=value' "
91 					"pairs separated with commas"), str);
92 
93 		// Look for the option name from the option map.
94 		bool found = false;
95 		for (size_t i = 0; opts[i].name != NULL; ++i) {
96 			if (strcmp(name, opts[i].name) != 0)
97 				continue;
98 
99 			if (opts[i].map != NULL) {
100 				// value is a string which we should map
101 				// to an integer.
102 				size_t j;
103 				for (j = 0; opts[i].map[j].name != NULL; ++j) {
104 					if (strcmp(opts[i].map[j].name, value)
105 							== 0)
106 						break;
107 				}
108 
109 				if (opts[i].map[j].name == NULL)
110 					message_fatal(_("%s: Invalid option "
111 							"value"), value);
112 
113 				set(filter_options, i, opts[i].map[j].id,
114 						value);
115 
116 			} else if (opts[i].min == UINT64_MAX) {
117 				// value is a special string that will be
118 				// parsed by set().
119 				set(filter_options, i, 0, value);
120 
121 			} else {
122 				// value is an integer.
123 				const uint64_t v = str_to_uint64(name, value,
124 						opts[i].min, opts[i].max);
125 				set(filter_options, i, v, value);
126 			}
127 
128 			found = true;
129 			break;
130 		}
131 
132 		if (!found)
133 			message_fatal(_("%s: Invalid option name"), name);
134 
135 		if (split == NULL)
136 			break;
137 
138 		name = split + 1;
139 	}
140 
141 	free(s);
142 	return;
143 }
144 
145 
146 //////////////
147 // Subblock //
148 //////////////
149 
150 enum {
151 	OPT_SIZE,
152 	OPT_RLE,
153 	OPT_ALIGN,
154 };
155 
156 
157 static void
158 set_subblock(void *options, uint32_t key, uint64_t value,
159 		const char *valuestr lzma_attribute((unused)))
160 {
161 	lzma_options_subblock *opt = options;
162 
163 	switch (key) {
164 	case OPT_SIZE:
165 		opt->subblock_data_size = value;
166 		break;
167 
168 	case OPT_RLE:
169 		opt->rle = value;
170 		break;
171 
172 	case OPT_ALIGN:
173 		opt->alignment = value;
174 		break;
175 	}
176 }
177 
178 
179 extern lzma_options_subblock *
180 options_subblock(const char *str)
181 {
182 	static const option_map opts[] = {
183 		{ "size", NULL,   LZMA_SUBBLOCK_DATA_SIZE_MIN,
184 		                  LZMA_SUBBLOCK_DATA_SIZE_MAX },
185 		{ "rle",  NULL,   LZMA_SUBBLOCK_RLE_OFF,
186 		                  LZMA_SUBBLOCK_RLE_MAX },
187 		{ "align",NULL,   LZMA_SUBBLOCK_ALIGNMENT_MIN,
188 		                  LZMA_SUBBLOCK_ALIGNMENT_MAX },
189 		{ NULL,   NULL,   0, 0 }
190 	};
191 
192 	lzma_options_subblock *options
193 			= xmalloc(sizeof(lzma_options_subblock));
194 	*options = (lzma_options_subblock){
195 		.allow_subfilters = false,
196 		.alignment = LZMA_SUBBLOCK_ALIGNMENT_DEFAULT,
197 		.subblock_data_size = LZMA_SUBBLOCK_DATA_SIZE_DEFAULT,
198 		.rle = LZMA_SUBBLOCK_RLE_OFF,
199 	};
200 
201 	parse_options(str, opts, &set_subblock, options);
202 
203 	return options;
204 }
205 
206 
207 ///////////
208 // Delta //
209 ///////////
210 
211 enum {
212 	OPT_DIST,
213 };
214 
215 
216 static void
217 set_delta(void *options, uint32_t key, uint64_t value,
218 		const char *valuestr lzma_attribute((unused)))
219 {
220 	lzma_options_delta *opt = options;
221 	switch (key) {
222 	case OPT_DIST:
223 		opt->dist = value;
224 		break;
225 	}
226 }
227 
228 
229 extern lzma_options_delta *
230 options_delta(const char *str)
231 {
232 	static const option_map opts[] = {
233 		{ "dist",     NULL,  LZMA_DELTA_DIST_MIN,
234 		                     LZMA_DELTA_DIST_MAX },
235 		{ NULL,       NULL,  0, 0 }
236 	};
237 
238 	lzma_options_delta *options = xmalloc(sizeof(lzma_options_delta));
239 	*options = (lzma_options_delta){
240 		// It's hard to give a useful default for this.
241 		.type = LZMA_DELTA_TYPE_BYTE,
242 		.dist = LZMA_DELTA_DIST_MIN,
243 	};
244 
245 	parse_options(str, opts, &set_delta, options);
246 
247 	return options;
248 }
249 
250 
251 /////////
252 // BCJ //
253 /////////
254 
255 enum {
256 	OPT_START_OFFSET,
257 };
258 
259 
260 static void
261 set_bcj(void *options, uint32_t key, uint64_t value,
262 		const char *valuestr lzma_attribute((unused)))
263 {
264 	lzma_options_bcj *opt = options;
265 	switch (key) {
266 	case OPT_START_OFFSET:
267 		opt->start_offset = value;
268 		break;
269 	}
270 }
271 
272 
273 extern lzma_options_bcj *
274 options_bcj(const char *str)
275 {
276 	static const option_map opts[] = {
277 		{ "start",    NULL,  0, UINT32_MAX },
278 		{ NULL,       NULL,  0, 0 }
279 	};
280 
281 	lzma_options_bcj *options = xmalloc(sizeof(lzma_options_bcj));
282 	*options = (lzma_options_bcj){
283 		.start_offset = 0,
284 	};
285 
286 	parse_options(str, opts, &set_bcj, options);
287 
288 	return options;
289 }
290 
291 
292 //////////
293 // LZMA //
294 //////////
295 
296 enum {
297 	OPT_PRESET,
298 	OPT_DICT,
299 	OPT_LC,
300 	OPT_LP,
301 	OPT_PB,
302 	OPT_MODE,
303 	OPT_NICE,
304 	OPT_MF,
305 	OPT_DEPTH,
306 };
307 
308 
309 static void lzma_attribute((noreturn))
310 error_lzma_preset(const char *valuestr)
311 {
312 	message_fatal(_("Unsupported LZMA1/LZMA2 preset: %s"), valuestr);
313 }
314 
315 
316 static void
317 set_lzma(void *options, uint32_t key, uint64_t value, const char *valuestr)
318 {
319 	lzma_options_lzma *opt = options;
320 
321 	switch (key) {
322 	case OPT_PRESET: {
323 		if (valuestr[0] < '0' || valuestr[0] > '9')
324 			error_lzma_preset(valuestr);
325 
326 		uint32_t preset = valuestr[0] - '0';
327 
328 		// Currently only "e" is supported as a modifier,
329 		// so keep this simple for now.
330 		if (valuestr[1] != '\0') {
331 			if (valuestr[1] == 'e')
332 				preset |= LZMA_PRESET_EXTREME;
333 			else
334 				error_lzma_preset(valuestr);
335 
336 			if (valuestr[2] != '\0')
337 				error_lzma_preset(valuestr);
338 		}
339 
340 		if (lzma_lzma_preset(options, preset))
341 			error_lzma_preset(valuestr);
342 
343 		break;
344 	}
345 
346 	case OPT_DICT:
347 		opt->dict_size = value;
348 		break;
349 
350 	case OPT_LC:
351 		opt->lc = value;
352 		break;
353 
354 	case OPT_LP:
355 		opt->lp = value;
356 		break;
357 
358 	case OPT_PB:
359 		opt->pb = value;
360 		break;
361 
362 	case OPT_MODE:
363 		opt->mode = value;
364 		break;
365 
366 	case OPT_NICE:
367 		opt->nice_len = value;
368 		break;
369 
370 	case OPT_MF:
371 		opt->mf = value;
372 		break;
373 
374 	case OPT_DEPTH:
375 		opt->depth = value;
376 		break;
377 	}
378 }
379 
380 
381 extern lzma_options_lzma *
382 options_lzma(const char *str)
383 {
384 	static const name_id_map modes[] = {
385 		{ "fast",   LZMA_MODE_FAST },
386 		{ "normal", LZMA_MODE_NORMAL },
387 		{ NULL,     0 }
388 	};
389 
390 	static const name_id_map mfs[] = {
391 		{ "hc3", LZMA_MF_HC3 },
392 		{ "hc4", LZMA_MF_HC4 },
393 		{ "bt2", LZMA_MF_BT2 },
394 		{ "bt3", LZMA_MF_BT3 },
395 		{ "bt4", LZMA_MF_BT4 },
396 		{ NULL,  0 }
397 	};
398 
399 	static const option_map opts[] = {
400 		{ "preset", NULL,   UINT64_MAX, 0 },
401 		{ "dict",   NULL,   LZMA_DICT_SIZE_MIN,
402 				(UINT32_C(1) << 30) + (UINT32_C(1) << 29) },
403 		{ "lc",     NULL,   LZMA_LCLP_MIN, LZMA_LCLP_MAX },
404 		{ "lp",     NULL,   LZMA_LCLP_MIN, LZMA_LCLP_MAX },
405 		{ "pb",     NULL,   LZMA_PB_MIN, LZMA_PB_MAX },
406 		{ "mode",   modes,  0, 0 },
407 		{ "nice",   NULL,   2, 273 },
408 		{ "mf",     mfs,    0, 0 },
409 		{ "depth",  NULL,   0, UINT32_MAX },
410 		{ NULL,     NULL,   0, 0 }
411 	};
412 
413 	lzma_options_lzma *options = xmalloc(sizeof(lzma_options_lzma));
414 	*options = (lzma_options_lzma){
415 		.dict_size = LZMA_DICT_SIZE_DEFAULT,
416 		.preset_dict =  NULL,
417 		.preset_dict_size = 0,
418 		.lc = LZMA_LC_DEFAULT,
419 		.lp = LZMA_LP_DEFAULT,
420 		.pb = LZMA_PB_DEFAULT,
421 		.persistent = false,
422 		.mode = LZMA_MODE_NORMAL,
423 		.nice_len = 64,
424 		.mf = LZMA_MF_BT4,
425 		.depth = 0,
426 	};
427 
428 	parse_options(str, opts, &set_lzma, options);
429 
430 	if (options->lc + options->lp > LZMA_LCLP_MAX)
431 		message_fatal(_("The sum of lc and lp must be at "
432 				"maximum of 4"));
433 
434 	const uint32_t nice_len_min = options->mf & 0x0F;
435 	if (options->nice_len < nice_len_min)
436 		message_fatal(_("The selected match finder requires at "
437 				"least nice=%" PRIu32), nice_len_min);
438 
439 	return options;
440 }
441