1 /////////////////////////////////////////////////////////////////////////////// 2 // 3 /// \file xzdec.c 4 /// \brief Simple single-threaded tool to uncompress .xz or .lzma files 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 "sysdefs.h" 14 #include "lzma.h" 15 16 #include <stdarg.h> 17 #include <errno.h> 18 #include <stdio.h> 19 #include <unistd.h> 20 21 #include "getopt.h" 22 #include "tuklib_progname.h" 23 #include "tuklib_exit.h" 24 25 #ifdef TUKLIB_DOSLIKE 26 # include <fcntl.h> 27 # include <io.h> 28 #endif 29 30 31 #ifdef LZMADEC 32 # define TOOL_FORMAT "lzma" 33 #else 34 # define TOOL_FORMAT "xz" 35 #endif 36 37 38 /// Error messages are suppressed if this is zero, which is the case when 39 /// --quiet has been given at least twice. 40 static unsigned int display_errors = 2; 41 42 43 static void lzma_attribute((__format__(__printf__, 1, 2))) 44 my_errorf(const char *fmt, ...) 45 { 46 va_list ap; 47 va_start(ap, fmt); 48 49 if (display_errors) { 50 fprintf(stderr, "%s: ", progname); 51 vfprintf(stderr, fmt, ap); 52 fprintf(stderr, "\n"); 53 } 54 55 va_end(ap); 56 return; 57 } 58 59 60 static void lzma_attribute((__noreturn__)) 61 help(void) 62 { 63 printf( 64 "Usage: %s [OPTION]... [FILE]...\n" 65 "Decompress files in the ." TOOL_FORMAT " format to standard output.\n" 66 "\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" 74 "\n" 75 "With no FILE, or when FILE is -, read standard input.\n" 76 "\n" 77 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n" 78 PACKAGE_NAME " home page: <" PACKAGE_URL ">\n", progname); 79 80 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors); 81 } 82 83 84 static void lzma_attribute((__noreturn__)) 85 version(void) 86 { 87 printf(TOOL_FORMAT "dec (" PACKAGE_NAME ") " LZMA_VERSION_STRING "\n" 88 "liblzma %s\n", lzma_version_string()); 89 90 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors); 91 } 92 93 94 /// Parses command line options. 95 static void 96 parse_options(int argc, char **argv) 97 { 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' }, 109 { NULL, 0, NULL, 0 } 110 }; 111 112 int c; 113 114 while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) 115 != -1) { 116 switch (c) { 117 case 'c': 118 case 'd': 119 case 'k': 120 case 'Q': 121 break; 122 123 case 'q': 124 if (display_errors > 0) 125 --display_errors; 126 127 break; 128 129 case 'h': 130 help(); 131 132 case 'V': 133 version(); 134 135 default: 136 exit(EXIT_FAILURE); 137 } 138 } 139 140 return; 141 } 142 143 144 static void 145 uncompress(lzma_stream *strm, FILE *file, const char *filename) 146 { 147 lzma_ret ret; 148 149 // Initialize the decoder 150 #ifdef LZMADEC 151 ret = lzma_alone_decoder(strm, UINT64_MAX); 152 #else 153 ret = lzma_stream_decoder(strm, UINT64_MAX, LZMA_CONCATENATED); 154 #endif 155 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)"); 160 exit(EXIT_FAILURE); 161 } 162 163 // Input and output buffers 164 uint8_t in_buf[BUFSIZ]; 165 uint8_t out_buf[BUFSIZ]; 166 167 strm->avail_in = 0; 168 strm->next_out = out_buf; 169 strm->avail_out = BUFSIZ; 170 171 lzma_action action = LZMA_RUN; 172 173 while (true) { 174 if (strm->avail_in == 0) { 175 strm->next_in = in_buf; 176 strm->avail_in = fread(in_buf, 1, BUFSIZ, file); 177 178 if (ferror(file)) { 179 // POSIX says that fread() sets errno if 180 // an error occurred. ferror() doesn't 181 // touch errno. 182 my_errorf("%s: Error reading input file: %s", 183 filename, strerror(errno)); 184 exit(EXIT_FAILURE); 185 } 186 187 #ifndef LZMADEC 188 // When using LZMA_CONCATENATED, we need to tell 189 // liblzma when it has got all the input. 190 if (feof(file)) 191 action = LZMA_FINISH; 192 #endif 193 } 194 195 ret = lzma_code(strm, action); 196 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; 202 203 if (fwrite(out_buf, 1, write_size, stdout) 204 != write_size) { 205 // Wouldn't be a surprise if writing to stderr 206 // would fail too but at least try to show an 207 // error message. 208 my_errorf("Cannot write to standard output: " 209 "%s", strerror(errno)); 210 exit(EXIT_FAILURE); 211 } 212 213 strm->next_out = out_buf; 214 strm->avail_out = BUFSIZ; 215 } 216 217 if (ret != LZMA_OK) { 218 if (ret == LZMA_STREAM_END) { 219 #ifdef LZMADEC 220 // Check that there's no trailing garbage. 221 if (strm->avail_in != 0 222 || fread(in_buf, 1, 1, file) 223 != 0 224 || !feof(file)) 225 ret = LZMA_DATA_ERROR; 226 else 227 return; 228 #else 229 // lzma_stream_decoder() already guarantees 230 // that there's no trailing garbage. 231 assert(strm->avail_in == 0); 232 assert(action == LZMA_FINISH); 233 assert(feof(file)); 234 return; 235 #endif 236 } 237 238 const char *msg; 239 switch (ret) { 240 case LZMA_MEM_ERROR: 241 msg = strerror(ENOMEM); 242 break; 243 244 case LZMA_FORMAT_ERROR: 245 msg = "File format not recognized"; 246 break; 247 248 case LZMA_OPTIONS_ERROR: 249 // FIXME: Better message? 250 msg = "Unsupported compression options"; 251 break; 252 253 case LZMA_DATA_ERROR: 254 msg = "File is corrupt"; 255 break; 256 257 case LZMA_BUF_ERROR: 258 msg = "Unexpected end of input"; 259 break; 260 261 default: 262 msg = "Internal error (bug)"; 263 break; 264 } 265 266 my_errorf("%s: %s", filename, msg); 267 exit(EXIT_FAILURE); 268 } 269 } 270 } 271 272 273 int 274 main(int argc, char **argv) 275 { 276 // Initialize progname which we will be used in error messages. 277 tuklib_progname_init(argv); 278 279 // Parse the command line options. 280 parse_options(argc, argv); 281 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; 286 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); 291 #endif 292 293 if (optind == argc) { 294 // No filenames given, decode from stdin. 295 uncompress(&strm, stdin, "(stdin)"); 296 } else { 297 // Loop through the filenames given on the command line. 298 do { 299 // "-" indicates stdin. 300 if (strcmp(argv[optind], "-") == 0) { 301 uncompress(&strm, stdin, "(stdin)"); 302 } else { 303 FILE *file = fopen(argv[optind], "rb"); 304 if (file == NULL) { 305 my_errorf("%s: %s", argv[optind], 306 strerror(errno)); 307 exit(EXIT_FAILURE); 308 } 309 310 uncompress(&strm, file, argv[optind]); 311 fclose(file); 312 } 313 } while (++optind < argc); 314 } 315 316 #ifndef NDEBUG 317 // Free the memory only when debugging. Freeing wastes some time, 318 // but allows detecting possible memory leaks with Valgrind. 319 lzma_end(&strm); 320 #endif 321 322 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors); 323 } 324