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 int display_errors = 2; 41 42 43 lzma_attribute((__format__(__printf__, 1, 2))) 44 static void 45 my_errorf(const char *fmt, ...) 46 { 47 va_list ap; 48 va_start(ap, fmt); 49 50 if (display_errors) { 51 fprintf(stderr, "%s: ", progname); 52 vfprintf(stderr, fmt, ap); 53 fprintf(stderr, "\n"); 54 } 55 56 va_end(ap); 57 return; 58 } 59 60 61 tuklib_attr_noreturn 62 static void 63 help(void) 64 { 65 printf( 66 "Usage: %s [OPTION]... [FILE]...\n" 67 "Decompress files in the ." TOOL_FORMAT " format to standard output.\n" 68 "\n" 69 " -d, --decompress (ignored, only decompression is supported)\n" 70 " -k, --keep (ignored, files are never deleted)\n" 71 " -c, --stdout (ignored, output is always written to standard output)\n" 72 " -q, --quiet specify *twice* to suppress errors\n" 73 " -Q, --no-warn (ignored, the exit status 2 is never used)\n" 74 " -h, --help display this help and exit\n" 75 " -V, --version display the version number and exit\n" 76 "\n" 77 "With no FILE, or when FILE is -, read standard input.\n" 78 "\n" 79 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n" 80 PACKAGE_NAME " home page: <" PACKAGE_URL ">\n", progname); 81 82 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors); 83 } 84 85 86 tuklib_attr_noreturn 87 static void 88 version(void) 89 { 90 printf(TOOL_FORMAT "dec (" PACKAGE_NAME ") " LZMA_VERSION_STRING "\n" 91 "liblzma %s\n", lzma_version_string()); 92 93 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors); 94 } 95 96 97 /// Parses command line options. 98 static void 99 parse_options(int argc, char **argv) 100 { 101 static const char short_opts[] = "cdkM:hqQV"; 102 static const struct option long_opts[] = { 103 { "stdout", no_argument, NULL, 'c' }, 104 { "to-stdout", no_argument, NULL, 'c' }, 105 { "decompress", no_argument, NULL, 'd' }, 106 { "uncompress", no_argument, NULL, 'd' }, 107 { "keep", no_argument, NULL, 'k' }, 108 { "quiet", no_argument, NULL, 'q' }, 109 { "no-warn", no_argument, NULL, 'Q' }, 110 { "help", no_argument, NULL, 'h' }, 111 { "version", no_argument, NULL, 'V' }, 112 { NULL, 0, NULL, 0 } 113 }; 114 115 int c; 116 117 while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) 118 != -1) { 119 switch (c) { 120 case 'c': 121 case 'd': 122 case 'k': 123 case 'Q': 124 break; 125 126 case 'q': 127 if (display_errors > 0) 128 --display_errors; 129 130 break; 131 132 case 'h': 133 help(); 134 135 case 'V': 136 version(); 137 138 default: 139 exit(EXIT_FAILURE); 140 } 141 } 142 143 return; 144 } 145 146 147 static void 148 uncompress(lzma_stream *strm, FILE *file, const char *filename) 149 { 150 lzma_ret ret; 151 152 // Initialize the decoder 153 #ifdef LZMADEC 154 ret = lzma_alone_decoder(strm, UINT64_MAX); 155 #else 156 ret = lzma_stream_decoder(strm, UINT64_MAX, LZMA_CONCATENATED); 157 #endif 158 159 // The only reasonable error here is LZMA_MEM_ERROR. 160 if (ret != LZMA_OK) { 161 my_errorf("%s", ret == LZMA_MEM_ERROR ? strerror(ENOMEM) 162 : "Internal error (bug)"); 163 exit(EXIT_FAILURE); 164 } 165 166 // Input and output buffers 167 uint8_t in_buf[BUFSIZ]; 168 uint8_t out_buf[BUFSIZ]; 169 170 strm->avail_in = 0; 171 strm->next_out = out_buf; 172 strm->avail_out = BUFSIZ; 173 174 lzma_action action = LZMA_RUN; 175 176 while (true) { 177 if (strm->avail_in == 0) { 178 strm->next_in = in_buf; 179 strm->avail_in = fread(in_buf, 1, BUFSIZ, file); 180 181 if (ferror(file)) { 182 // POSIX says that fread() sets errno if 183 // an error occurred. ferror() doesn't 184 // touch errno. 185 my_errorf("%s: Error reading input file: %s", 186 filename, strerror(errno)); 187 exit(EXIT_FAILURE); 188 } 189 190 #ifndef LZMADEC 191 // When using LZMA_CONCATENATED, we need to tell 192 // liblzma when it has got all the input. 193 if (feof(file)) 194 action = LZMA_FINISH; 195 #endif 196 } 197 198 ret = lzma_code(strm, action); 199 200 // Write and check write error before checking decoder error. 201 // This way as much data as possible gets written to output 202 // even if decoder detected an error. 203 if (strm->avail_out == 0 || ret != LZMA_OK) { 204 const size_t write_size = BUFSIZ - strm->avail_out; 205 206 if (fwrite(out_buf, 1, write_size, stdout) 207 != write_size) { 208 // Wouldn't be a surprise if writing to stderr 209 // would fail too but at least try to show an 210 // error message. 211 my_errorf("Cannot write to standard output: " 212 "%s", strerror(errno)); 213 exit(EXIT_FAILURE); 214 } 215 216 strm->next_out = out_buf; 217 strm->avail_out = BUFSIZ; 218 } 219 220 if (ret != LZMA_OK) { 221 if (ret == LZMA_STREAM_END) { 222 #ifdef LZMADEC 223 // Check that there's no trailing garbage. 224 if (strm->avail_in != 0 225 || fread(in_buf, 1, 1, file) 226 != 0 227 || !feof(file)) 228 ret = LZMA_DATA_ERROR; 229 else 230 return; 231 #else 232 // lzma_stream_decoder() already guarantees 233 // that there's no trailing garbage. 234 assert(strm->avail_in == 0); 235 assert(action == LZMA_FINISH); 236 assert(feof(file)); 237 return; 238 #endif 239 } 240 241 const char *msg; 242 switch (ret) { 243 case LZMA_MEM_ERROR: 244 msg = strerror(ENOMEM); 245 break; 246 247 case LZMA_FORMAT_ERROR: 248 msg = "File format not recognized"; 249 break; 250 251 case LZMA_OPTIONS_ERROR: 252 // FIXME: Better message? 253 msg = "Unsupported compression options"; 254 break; 255 256 case LZMA_DATA_ERROR: 257 msg = "File is corrupt"; 258 break; 259 260 case LZMA_BUF_ERROR: 261 msg = "Unexpected end of input"; 262 break; 263 264 default: 265 msg = "Internal error (bug)"; 266 break; 267 } 268 269 my_errorf("%s: %s", filename, msg); 270 exit(EXIT_FAILURE); 271 } 272 } 273 } 274 275 276 int 277 main(int argc, char **argv) 278 { 279 // Initialize progname which we will be used in error messages. 280 tuklib_progname_init(argv); 281 282 // Parse the command line options. 283 parse_options(argc, argv); 284 285 // The same lzma_stream is used for all files that we decode. This way 286 // we don't need to reallocate memory for every file if they use same 287 // compression settings. 288 lzma_stream strm = LZMA_STREAM_INIT; 289 290 // Some systems require setting stdin and stdout to binary mode. 291 #ifdef TUKLIB_DOSLIKE 292 setmode(fileno(stdin), O_BINARY); 293 setmode(fileno(stdout), O_BINARY); 294 #endif 295 296 if (optind == argc) { 297 // No filenames given, decode from stdin. 298 uncompress(&strm, stdin, "(stdin)"); 299 } else { 300 // Loop through the filenames given on the command line. 301 do { 302 // "-" indicates stdin. 303 if (strcmp(argv[optind], "-") == 0) { 304 uncompress(&strm, stdin, "(stdin)"); 305 } else { 306 FILE *file = fopen(argv[optind], "rb"); 307 if (file == NULL) { 308 my_errorf("%s: %s", argv[optind], 309 strerror(errno)); 310 exit(EXIT_FAILURE); 311 } 312 313 uncompress(&strm, file, argv[optind]); 314 fclose(file); 315 } 316 } while (++optind < argc); 317 } 318 319 #ifndef NDEBUG 320 // Free the memory only when debugging. Freeing wastes some time, 321 // but allows detecting possible memory leaks with Valgrind. 322 lzma_end(&strm); 323 #endif 324 325 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors); 326 } 327