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