1 ///////////////////////////////////////////////////////////////////////////////
4 /// \brief Simple single-threaded tool to uncompress .xz or .lzma files
6 // Author: Lasse Collin
8 // This file has been put into the public domain.
9 // You can do whatever you want with this file.
11 ///////////////////////////////////////////////////////////////////////////////
22 #include "tuklib_progname.h"
23 #include "tuklib_exit.h"
32 # define TOOL_FORMAT "lzma"
34 # define TOOL_FORMAT "xz"
38 /// Error messages are suppressed if this is zero, which is the case when
39 /// --quiet has been given at least twice.
40 static int display_errors
= 2;
43 static void lzma_attribute((__format__(__printf__
, 1, 2)))
44 my_errorf(const char *fmt
, ...)
50 fprintf(stderr
, "%s: ", progname
);
51 vfprintf(stderr
, fmt
, ap
);
52 fprintf(stderr
, "\n");
60 static void lzma_attribute((__noreturn__
))
64 "Usage: %s [OPTION]... [FILE]...\n"
65 "Decompress files in the ." TOOL_FORMAT
" format to standard output.\n"
67 " -d, --decompress (ignored, only decompression is supported)\n"
68 " -k, --keep (ignored, files are never deleted)\n"
69 " -c, --stdout (ignored, output is always written to standard output)\n"
70 " -q, --quiet specify *twice* to suppress errors\n"
71 " -Q, --no-warn (ignored, the exit status 2 is never used)\n"
72 " -h, --help display this help and exit\n"
73 " -V, --version display the version number and exit\n"
75 "With no FILE, or when FILE is -, read standard input.\n"
77 "Report bugs to <" PACKAGE_BUGREPORT
"> (in English or Finnish).\n"
78 PACKAGE_NAME
" home page: <" PACKAGE_URL
">\n", progname
);
80 tuklib_exit(EXIT_SUCCESS
, EXIT_FAILURE
, display_errors
);
84 static void lzma_attribute((__noreturn__
))
87 printf(TOOL_FORMAT
"dec (" PACKAGE_NAME
") " LZMA_VERSION_STRING
"\n"
88 "liblzma %s\n", lzma_version_string());
90 tuklib_exit(EXIT_SUCCESS
, EXIT_FAILURE
, display_errors
);
94 /// Parses command line options.
96 parse_options(int argc
, char **argv
)
98 static const char short_opts
[] = "cdkM:hqQV";
99 static const struct option long_opts
[] = {
100 { "stdout", no_argument
, NULL
, 'c' },
101 { "to-stdout", no_argument
, NULL
, 'c' },
102 { "decompress", no_argument
, NULL
, 'd' },
103 { "uncompress", no_argument
, NULL
, 'd' },
104 { "keep", no_argument
, NULL
, 'k' },
105 { "quiet", no_argument
, NULL
, 'q' },
106 { "no-warn", no_argument
, NULL
, 'Q' },
107 { "help", no_argument
, NULL
, 'h' },
108 { "version", no_argument
, NULL
, 'V' },
114 while ((c
= getopt_long(argc
, argv
, short_opts
, long_opts
, NULL
))
124 if (display_errors
> 0)
145 uncompress(lzma_stream
*strm
, FILE *file
, const char *filename
)
149 // Initialize the decoder
151 ret
= lzma_alone_decoder(strm
, UINT64_MAX
);
153 ret
= lzma_stream_decoder(strm
, UINT64_MAX
, LZMA_CONCATENATED
);
156 // The only reasonable error here is LZMA_MEM_ERROR.
157 if (ret
!= LZMA_OK
) {
158 my_errorf("%s", ret
== LZMA_MEM_ERROR
? strerror(ENOMEM
)
159 : "Internal error (bug)");
163 // Input and output buffers
164 uint8_t in_buf
[BUFSIZ
];
165 uint8_t out_buf
[BUFSIZ
];
168 strm
->next_out
= out_buf
;
169 strm
->avail_out
= BUFSIZ
;
171 lzma_action action
= LZMA_RUN
;
174 if (strm
->avail_in
== 0) {
175 strm
->next_in
= in_buf
;
176 strm
->avail_in
= fread(in_buf
, 1, BUFSIZ
, file
);
179 // POSIX says that fread() sets errno if
180 // an error occurred. ferror() doesn't
182 my_errorf("%s: Error reading input file: %s",
183 filename
, strerror(errno
));
188 // When using LZMA_CONCATENATED, we need to tell
189 // liblzma when it has got all the input.
191 action
= LZMA_FINISH
;
195 ret
= lzma_code(strm
, action
);
197 // Write and check write error before checking decoder error.
198 // This way as much data as possible gets written to output
199 // even if decoder detected an error.
200 if (strm
->avail_out
== 0 || ret
!= LZMA_OK
) {
201 const size_t write_size
= BUFSIZ
- strm
->avail_out
;
203 if (fwrite(out_buf
, 1, write_size
, stdout
)
205 // Wouldn't be a surprise if writing to stderr
206 // would fail too but at least try to show an
208 my_errorf("Cannot write to standard output: "
209 "%s", strerror(errno
));
213 strm
->next_out
= out_buf
;
214 strm
->avail_out
= BUFSIZ
;
217 if (ret
!= LZMA_OK
) {
218 if (ret
== LZMA_STREAM_END
) {
220 // Check that there's no trailing garbage.
221 if (strm
->avail_in
!= 0
222 || fread(in_buf
, 1, 1, file
)
225 ret
= LZMA_DATA_ERROR
;
229 // lzma_stream_decoder() already guarantees
230 // that there's no trailing garbage.
231 assert(strm
->avail_in
== 0);
232 assert(action
== LZMA_FINISH
);
241 msg
= strerror(ENOMEM
);
244 case LZMA_FORMAT_ERROR
:
245 msg
= "File format not recognized";
248 case LZMA_OPTIONS_ERROR
:
249 // FIXME: Better message?
250 msg
= "Unsupported compression options";
253 case LZMA_DATA_ERROR
:
254 msg
= "File is corrupt";
258 msg
= "Unexpected end of input";
262 msg
= "Internal error (bug)";
266 my_errorf("%s: %s", filename
, msg
);
274 main(int argc
, char **argv
)
276 // Initialize progname which we will be used in error messages.
277 tuklib_progname_init(argv
);
279 // Parse the command line options.
280 parse_options(argc
, argv
);
282 // The same lzma_stream is used for all files that we decode. This way
283 // we don't need to reallocate memory for every file if they use same
284 // compression settings.
285 lzma_stream strm
= LZMA_STREAM_INIT
;
287 // Some systems require setting stdin and stdout to binary mode.
288 #ifdef TUKLIB_DOSLIKE
289 setmode(fileno(stdin
), O_BINARY
);
290 setmode(fileno(stdout
), O_BINARY
);
293 if (optind
== argc
) {
294 // No filenames given, decode from stdin.
295 uncompress(&strm
, stdin
, "(stdin)");
297 // Loop through the filenames given on the command line.
299 // "-" indicates stdin.
300 if (strcmp(argv
[optind
], "-") == 0) {
301 uncompress(&strm
, stdin
, "(stdin)");
303 FILE *file
= fopen(argv
[optind
], "rb");
305 my_errorf("%s: %s", argv
[optind
],
310 uncompress(&strm
, file
, argv
[optind
]);
313 } while (++optind
< argc
);
317 // Free the memory only when debugging. Freeing wastes some time,
318 // but allows detecting possible memory leaks with Valgrind.
322 tuklib_exit(EXIT_SUCCESS
, EXIT_FAILURE
, display_errors
);