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