]> icculus.org git repositories - icculus/xz.git/blob - src/lzma/args.c
Update the code to mostly match the new simpler file format
[icculus/xz.git] / src / lzma / args.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       args.c
4 /// \brief      Argument parsing
5 ///
6 /// \note       Filter-specific options parsing is in options.c.
7 //
8 //  Copyright (C) 2007 Lasse Collin
9 //
10 //  This program is free software; you can redistribute it and/or
11 //  modify it under the terms of the GNU Lesser General Public
12 //  License as published by the Free Software Foundation; either
13 //  version 2.1 of the License, or (at your option) any later version.
14 //
15 //  This program is distributed in the hope that it will be useful,
16 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 //  Lesser General Public License for more details.
19 //
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "private.h"
23
24 #include "getopt.h"
25 #include <ctype.h>
26
27
28 enum tool_mode opt_mode = MODE_COMPRESS;
29 enum header_type opt_header = HEADER_AUTO;
30
31 char *opt_suffix = NULL;
32
33 char *opt_files_name = NULL;
34 char opt_files_split = '\0';
35 FILE *opt_files_file = NULL;
36
37 bool opt_stdout = false;
38 bool opt_force = false;
39 bool opt_keep_original = false;
40 bool opt_preserve_name = false;
41
42 lzma_check_type opt_check = LZMA_CHECK_CRC64;
43 lzma_options_filter opt_filters[8];
44
45 // We don't modify or free() this, but we need to assign it in some
46 // non-const pointers.
47 const char *stdin_filename = "(stdin)";
48
49 static size_t preset_number = 7 - 1;
50 static bool preset_default = true;
51 static size_t filter_count = 0;
52
53
54 enum {
55         OPT_SUBBLOCK = INT_MIN,
56         OPT_X86,
57         OPT_POWERPC,
58         OPT_IA64,
59         OPT_ARM,
60         OPT_ARMTHUMB,
61         OPT_SPARC,
62         OPT_DELTA,
63         OPT_LZMA,
64
65         OPT_FILES,
66         OPT_FILES0,
67 };
68
69
70 static const char short_opts[] = "cC:dfF:hlLkM:qrS:tT:vVz123456789";
71
72
73 static const struct option long_opts[] = {
74         // gzip-like options
75         { "fast",               no_argument,       NULL,  '1' },
76         { "best",               no_argument,       NULL,  '9' },
77         { "memory",             required_argument, NULL,  'M' },
78         { "name",               no_argument,       NULL,  'N' },
79         { "suffix",             required_argument, NULL,  'S' },
80         { "threads",            required_argument, NULL,  'T' },
81         { "version",            no_argument,       NULL,  'V' },
82         { "stdout",             no_argument,       NULL,  'c' },
83         { "to-stdout",          no_argument,       NULL,  'c' },
84         { "decompress",         no_argument,       NULL,  'd' },
85         { "uncompress",         no_argument,       NULL,  'd' },
86         { "force",              no_argument,       NULL,  'f' },
87         { "help",               no_argument,       NULL,  'h' },
88         { "list",               no_argument,       NULL,  'l' },
89         { "info",               no_argument,       NULL,  'l' },
90         { "keep",               no_argument,       NULL,  'k' },
91         { "no-name",            no_argument,       NULL,  'n' },
92         { "quiet",              no_argument,       NULL,  'q' },
93 //      { "recursive",          no_argument,       NULL,  'r' }, // TODO
94         { "test",               no_argument,       NULL,  't' },
95         { "verbose",            no_argument,       NULL,  'v' },
96         { "compress",           no_argument,       NULL,  'z' },
97
98         // Filters
99         { "subblock",           optional_argument, NULL,   OPT_SUBBLOCK },
100         { "x86",                no_argument,       NULL,   OPT_X86 },
101         { "bcj",                no_argument,       NULL,   OPT_X86 },
102         { "powerpc",            no_argument,       NULL,   OPT_POWERPC },
103         { "ppc",                no_argument,       NULL,   OPT_POWERPC },
104         { "ia64",               no_argument,       NULL,   OPT_IA64 },
105         { "itanium",            no_argument,       NULL,   OPT_IA64 },
106         { "arm",                no_argument,       NULL,   OPT_ARM },
107         { "armthumb",           no_argument,       NULL,   OPT_ARMTHUMB },
108         { "sparc",              no_argument,       NULL,   OPT_SPARC },
109         { "delta",              optional_argument, NULL,   OPT_DELTA },
110         { "lzma",               optional_argument, NULL,   OPT_LZMA },
111
112         // Other
113         { "format",             required_argument, NULL,   'F' },
114         { "check",              required_argument, NULL,   'C' },
115         { "files",              optional_argument, NULL,   OPT_FILES },
116         { "files0",             optional_argument, NULL,   OPT_FILES0 },
117
118         { NULL,                 0,                 NULL,   0 }
119 };
120
121
122 static void
123 add_filter(lzma_vli id, const char *opt_str)
124 {
125         if (filter_count == 7) {
126                 errmsg(V_ERROR, _("Maximum number of filters is seven"));
127                 my_exit(ERROR);
128         }
129
130         opt_filters[filter_count].id = id;
131
132         switch (id) {
133         case LZMA_FILTER_SUBBLOCK:
134                 opt_filters[filter_count].options
135                                 = parse_options_subblock(opt_str);
136                 break;
137
138         case LZMA_FILTER_DELTA:
139                 opt_filters[filter_count].options
140                                 = parse_options_delta(opt_str);
141                 break;
142
143         case LZMA_FILTER_LZMA:
144                 opt_filters[filter_count].options
145                                 = parse_options_lzma(opt_str);
146                 break;
147
148         default:
149                 assert(opt_str == NULL);
150                 opt_filters[filter_count].options = NULL;
151                 break;
152         }
153
154         ++filter_count;
155         preset_default = false;
156         return;
157 }
158
159
160 static void
161 parse_real(int argc, char **argv)
162 {
163         int c;
164
165         while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
166                         != -1) {
167                 switch (c) {
168                 // gzip-like options
169
170                 case '1': case '2': case '3': case '4':
171                 case '5': case '6': case '7': case '8': case '9':
172                         preset_number = c - '1';
173                         preset_default = false;
174                         break;
175
176                 // --memory
177                 case 'M':
178                         opt_memory = str_to_uint64("memory", optarg,
179                                         1, SIZE_MAX);
180                         break;
181
182                 case 'N':
183                         opt_preserve_name = true;
184                         break;
185
186                 // --suffix
187                 case 'S':
188                         // Empty suffix and suffixes having a slash are
189                         // rejected. Such suffixes would break things later.
190                         if (optarg[0] == '\0' || strchr(optarg, '/') != NULL) {
191                                 errmsg(V_ERROR, _("%s: Invalid filename "
192                                                 "suffix"), optarg);
193                                 my_exit(ERROR);
194                         }
195
196                         free(opt_suffix);
197                         opt_suffix = xstrdup(optarg);
198                         break;
199
200                 case 'T':
201                         opt_threads = str_to_uint64("threads", optarg,
202                                         1, SIZE_MAX);
203                         break;
204
205                 // --version
206                 case 'V':
207                         // This doesn't return.
208                         show_version();
209
210                 // --stdout
211                 case 'c':
212                         opt_stdout = true;
213                         break;
214
215                 // --decompress
216                 case 'd':
217                         opt_mode = MODE_DECOMPRESS;
218                         break;
219
220                 // --force
221                 case 'f':
222                         opt_force = true;
223                         break;
224
225                 // --help
226                 case 'h':
227                         // This doesn't return.
228                         show_help();
229
230                 // --list
231                 case 'l':
232                         opt_mode = MODE_LIST;
233                         break;
234
235                 // --keep
236                 case 'k':
237                         opt_keep_original = true;
238                         break;
239
240                 case 'n':
241                         opt_preserve_name = false;
242                         break;
243
244                 // --quiet
245                 case 'q':
246                         if (verbosity > V_SILENT)
247                                 --verbosity;
248
249                         break;
250
251                 case 't':
252                         opt_mode = MODE_TEST;
253                         break;
254
255                 // --verbose
256                 case 'v':
257                         if (verbosity < V_DEBUG)
258                                 ++verbosity;
259
260                         break;
261
262                 case 'z':
263                         opt_mode = MODE_COMPRESS;
264                         break;
265
266                 // Filter setup
267
268                 case OPT_SUBBLOCK:
269                         add_filter(LZMA_FILTER_SUBBLOCK, optarg);
270                         break;
271
272                 case OPT_X86:
273                         add_filter(LZMA_FILTER_X86, NULL);
274                         break;
275
276                 case OPT_POWERPC:
277                         add_filter(LZMA_FILTER_POWERPC, NULL);
278                         break;
279
280                 case OPT_IA64:
281                         add_filter(LZMA_FILTER_IA64, NULL);
282                         break;
283
284                 case OPT_ARM:
285                         add_filter(LZMA_FILTER_ARM, NULL);
286                         break;
287
288                 case OPT_ARMTHUMB:
289                         add_filter(LZMA_FILTER_ARMTHUMB, NULL);
290                         break;
291
292                 case OPT_SPARC:
293                         add_filter(LZMA_FILTER_SPARC, NULL);
294                         break;
295
296                 case OPT_DELTA:
297                         add_filter(LZMA_FILTER_DELTA, optarg);
298                         break;
299
300                 case OPT_LZMA:
301                         add_filter(LZMA_FILTER_LZMA, optarg);
302                         break;
303
304                 // Other
305
306                 // --format
307                 case 'F': {
308                         static const char *types[] = {
309                                 "auto",
310                                 "native",
311                                 "alone",
312 //                              "gzip",
313                                 NULL
314                         };
315
316                         opt_header = 0;
317                         while (strcmp(types[opt_header], optarg) != 0) {
318                                 if (types[++opt_header] == NULL) {
319                                         errmsg(V_ERROR, _("%s: Unknown file "
320                                                         "format type"),
321                                                         optarg);
322                                         my_exit(ERROR);
323                                 }
324                         }
325
326                         break;
327                 }
328
329                 // --check
330                 case 'C': {
331                         static const struct {
332                                 const char *str;
333                                 unsigned int value;
334                         } types[] = {
335                                 { "none",   LZMA_CHECK_NONE },
336                                 { "crc32",  LZMA_CHECK_CRC32 },
337                                 { "crc64",  LZMA_CHECK_CRC64 },
338                                 { "sha256", LZMA_CHECK_SHA256 },
339                                 { NULL,     0 }
340                         };
341
342                         size_t i = 0;
343                         while (strcmp(types[i].str, optarg) != 0) {
344                                 if (types[++i].str == NULL) {
345                                         errmsg(V_ERROR, _("%s: Unknown "
346                                                         "integrity check "
347                                                         "type"), optarg);
348                                         my_exit(ERROR);
349                                 }
350                         }
351
352                         opt_check = types[i].value;
353                         break;
354                 }
355
356                 case OPT_FILES:
357                         opt_files_split = '\n';
358
359                 // Fall through
360
361                 case OPT_FILES0:
362                         if (opt_files_name != NULL) {
363                                 errmsg(V_ERROR, _("Only one file can be "
364                                                 "specified with `--files'"
365                                                 "or `--files0'."));
366                                 my_exit(ERROR);
367                         }
368
369                         if (optarg == NULL) {
370                                 opt_files_name = (char *)stdin_filename;
371                                 opt_files_file = stdin;
372                         } else {
373                                 opt_files_name = optarg;
374                                 opt_files_file = fopen(optarg,
375                                                 c == OPT_FILES ? "r" : "rb");
376                                 if (opt_files_file == NULL) {
377                                         errmsg(V_ERROR, "%s: %s", optarg,
378                                                         strerror(errno));
379                                         my_exit(ERROR);
380                                 }
381                         }
382
383                         break;
384
385                 default:
386                         show_try_help();
387                         my_exit(ERROR);
388                 }
389         }
390
391         return;
392 }
393
394
395 static void
396 parse_environment(void)
397 {
398         char *env = getenv("LZMA_OPT");
399         if (env == NULL)
400                 return;
401
402         env = xstrdup(env);
403
404         // Calculate the number of arguments in env.
405         unsigned int argc = 1;
406         bool prev_was_space = true;
407         for (size_t i = 0; env[i] != '\0'; ++i) {
408                 if (isspace(env[i])) {
409                         prev_was_space = true;
410                 } else if (prev_was_space) {
411                         prev_was_space = false;
412                         if (++argc > (unsigned int)(INT_MAX)) {
413                                 errmsg(V_ERROR, _("The environment variable "
414                                                 "LZMA_OPT contains too many "
415                                                 "arguments"));
416                                 my_exit(ERROR);
417                         }
418                 }
419         }
420
421         char **argv = xmalloc((argc + 1) * sizeof(char*));
422         argv[0] = argv0;
423         argv[argc] = NULL;
424
425         argc = 1;
426         prev_was_space = true;
427         for (size_t i = 0; env[i] != '\0'; ++i) {
428                 if (isspace(env[i])) {
429                         prev_was_space = true;
430                 } else if (prev_was_space) {
431                         prev_was_space = false;
432                         argv[argc++] = env + i;
433                 }
434         }
435
436         parse_real((int)(argc), argv);
437
438         free(env);
439
440         return;
441 }
442
443
444 static void
445 set_compression_settings(void)
446 {
447         if (filter_count == 0) {
448                 opt_filters[0].id = LZMA_FILTER_LZMA;
449                 opt_filters[0].options = (lzma_options_lzma *)(
450                                 lzma_preset_lzma + preset_number);
451                 filter_count = 1;
452         }
453
454         // Terminate the filter options array.
455         opt_filters[filter_count].id = LZMA_VLI_VALUE_UNKNOWN;
456
457         // If we are using the LZMA_Alone format, allow exactly one filter
458         // which has to be LZMA.
459         if (opt_header == HEADER_ALONE && (filter_count != 1
460                         || opt_filters[0].id != LZMA_FILTER_LZMA)) {
461                 errmsg(V_ERROR, _("With --format=alone only the LZMA filter "
462                                 "is supported"));
463                 my_exit(ERROR);
464         }
465
466         const uint32_t memory_limit = opt_memory / (1024 * 1024) + 1;
467         uint32_t memory_usage = lzma_memory_usage(opt_filters, true);
468
469         // Don't go over the memory limits when the default
470         // setting is used.
471         if (preset_default) {
472                 while (memory_usage > memory_limit) {
473                         if (preset_number == 0) {
474                                 errmsg(V_ERROR, _("Memory usage limit is too "
475                                                 "small for any internal "
476                                                 "filter preset"));
477                                 my_exit(ERROR);
478                         }
479
480                         --preset_number;
481                         opt_filters[0].options = (lzma_options_lzma *)(
482                                         lzma_preset_lzma
483                                         + preset_number);
484                         memory_usage = lzma_memory_usage(opt_filters,
485                                         true);
486                 }
487         } else {
488                 if (memory_usage > memory_limit) {
489                         errmsg(V_ERROR, _("Memory usage limit is too small "
490                                         "for the given filter setup"));
491                         my_exit(ERROR);
492                 }
493         }
494
495         // Limit the number of worked threads so that memory usage
496         // limit isn't exceeded.
497         // FIXME: Probably should use bytes instead of mebibytes for
498         // memory_usage and memory_limit.
499         if (memory_usage == 0)
500                 memory_usage = 1;
501
502         size_t thread_limit = memory_limit / memory_usage;
503         if (thread_limit == 0)
504                 thread_limit = 1;
505
506         if (opt_threads > thread_limit)
507                 opt_threads = thread_limit;
508
509         return;
510 }
511
512
513 extern char **
514 parse_args(int argc, char **argv)
515 {
516         // Check how we were called.
517         {
518                 const char *name = str_filename(argv[0]);
519                 if (name != NULL) {
520                         if (strstr(name, "cat") != NULL) {
521                                 opt_mode = MODE_DECOMPRESS;
522                                 opt_stdout = true;
523                         } else if (strstr(name, "un") != NULL) {
524                                 opt_mode = MODE_DECOMPRESS;
525                         }
526                 }
527         }
528
529         // First the flags from environment
530         parse_environment();
531
532         // Then from the command line
533         optind = 1;
534         parse_real(argc, argv);
535
536         // Never remove the source file when the destination is not on disk.
537         // In test mode the data is written nowhere, but setting opt_stdout
538         // will make the rest of the code behave well.
539         if (opt_stdout || opt_mode == MODE_TEST) {
540                 opt_keep_original = true;
541                 opt_stdout = true;
542         }
543
544         if (opt_mode == MODE_COMPRESS)
545                 set_compression_settings();
546
547         // If no filenames are given, use stdin.
548         if (argv[optind] == NULL && opt_files_name == NULL) {
549                 // We don't modify or free() the "-" constant.
550                 static char *argv_stdin[2] = { (char *)"-", NULL };
551                 return argv_stdin;
552         }
553
554         return argv + optind;
555 }