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