xref: /freebsd/contrib/xz/src/xz/options.c (revision 128836d304d93f2d00eb14069c27089ab46c38d4)
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
parse_options(const char * str,const option_map * opts,void (* set)(void * filter_options,unsigned key,uint64_t value,const char * valuestr),void * filter_options)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
set_delta(void * options,unsigned key,uint64_t value,const char * valuestr lzma_attribute ((__unused__)))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 *
options_delta(const char * str)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
set_bcj(void * options,unsigned key,uint64_t value,const char * valuestr lzma_attribute ((__unused__)))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 *
options_bcj(const char * str)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
error_lzma_preset(const char * valuestr)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
set_lzma(void * options,unsigned key,uint64_t value,const char * valuestr)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 *
options_lzma(const char * str)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