1 // SPDX-License-Identifier: 0BSD 2 3 /////////////////////////////////////////////////////////////////////////////// 4 // 5 /// \file lzmainfo.c 6 /// \brief lzmainfo tool for compatibility with LZMA Utils 7 // 8 // Author: Lasse Collin 9 // 10 /////////////////////////////////////////////////////////////////////////////// 11 12 #include "sysdefs.h" 13 #include <stdio.h> 14 #include <errno.h> 15 16 #include "lzma.h" 17 #include "getopt.h" 18 #include "tuklib_gettext.h" 19 #include "tuklib_progname.h" 20 #include "tuklib_mbstr_nonprint.h" 21 #include "tuklib_mbstr_wrap.h" 22 #include "tuklib_exit.h" 23 24 #ifdef TUKLIB_DOSLIKE 25 # include <fcntl.h> 26 # include <io.h> 27 #endif 28 29 30 tuklib_attr_noreturn 31 static void 32 help(void) 33 { 34 // A few languages use so long strings that we need automatic 35 // wrapping. A few strings are the same as in xz/message.c and 36 // should be kept in sync. 37 static const struct tuklib_wrap_opt wrap0 = { 0, 0, 0, 0, 79 }; 38 int e = 0; 39 40 printf(_("Usage: %s [--help] [--version] [FILE]...\n"), progname); 41 42 e |= tuklib_wraps(stdout, &wrap0, 43 W_("Show information stored in the .lzma file header.")); 44 e |= tuklib_wraps(stdout, &wrap0, 45 W_("With no FILE, or when FILE is -, read standard input.")); 46 47 putchar('\n'); 48 49 e |= tuklib_wrapf(stdout, &wrap0, 50 W_("Report bugs to <%s> (in English or Finnish)."), 51 PACKAGE_BUGREPORT); 52 53 e |= tuklib_wrapf(stdout, &wrap0, 54 W_("%s home page: <%s>"), PACKAGE_NAME, PACKAGE_URL); 55 56 if (e != 0) { 57 // Avoid new translatable strings by printing the message 58 // in pieces. 59 fprintf(stderr, _("%s: "), progname); 60 fprintf(stderr, _("Error printing the help text " 61 "(error code %d)"), e); 62 fprintf(stderr, "\n"); 63 } 64 65 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, true); 66 } 67 68 69 tuklib_attr_noreturn 70 static void 71 version(void) 72 { 73 puts("lzmainfo (" PACKAGE_NAME ") " LZMA_VERSION_STRING); 74 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, true); 75 } 76 77 78 /// Parse command line options. 79 static void 80 parse_args(int argc, char **argv) 81 { 82 enum { 83 OPT_HELP, 84 OPT_VERSION, 85 }; 86 87 static const struct option long_opts[] = { 88 { "help", no_argument, NULL, OPT_HELP }, 89 { "version", no_argument, NULL, OPT_VERSION }, 90 { NULL, 0, NULL, 0 } 91 }; 92 93 int c; 94 while ((c = getopt_long(argc, argv, "", long_opts, NULL)) != -1) { 95 switch (c) { 96 case OPT_HELP: 97 help(); 98 99 case OPT_VERSION: 100 version(); 101 102 default: 103 exit(EXIT_FAILURE); 104 } 105 } 106 107 return; 108 } 109 110 111 /// Primitive base-2 logarithm for integers 112 static uint32_t 113 my_log2(uint32_t n) 114 { 115 uint32_t e; 116 for (e = 0; n > 1; ++e, n /= 2) ; 117 return e; 118 } 119 120 121 /// Parse the .lzma header and display information about it. 122 static bool 123 lzmainfo(const char *name, FILE *f) 124 { 125 uint8_t buf[13]; 126 const size_t size = fread(buf, 1, sizeof(buf), f); 127 if (size != 13) { 128 fprintf(stderr, "%s: %s: %s\n", progname, 129 tuklib_mask_nonprint(name), 130 ferror(f) ? strerror(errno) 131 : _("File is too small to be a .lzma file")); 132 return true; 133 } 134 135 lzma_filter filter = { .id = LZMA_FILTER_LZMA1 }; 136 137 // Parse the first five bytes. 138 switch (lzma_properties_decode(&filter, NULL, buf, 5)) { 139 case LZMA_OK: 140 break; 141 142 case LZMA_OPTIONS_ERROR: 143 fprintf(stderr, "%s: %s: %s\n", progname, 144 tuklib_mask_nonprint(name), 145 _("Not a .lzma file")); 146 return true; 147 148 case LZMA_MEM_ERROR: 149 fprintf(stderr, "%s: %s\n", progname, strerror(ENOMEM)); 150 exit(EXIT_FAILURE); 151 152 default: 153 fprintf(stderr, "%s: %s\n", progname, 154 _("Internal error (bug)")); 155 exit(EXIT_FAILURE); 156 } 157 158 // Uncompressed size 159 uint64_t uncompressed_size = 0; 160 for (size_t i = 0; i < 8; ++i) 161 uncompressed_size |= (uint64_t)(buf[5 + i]) << (i * 8); 162 163 // Display the results. We don't want to translate these and also 164 // will use MB instead of MiB, because someone could be parsing 165 // this output and we don't want to break that when people move 166 // from LZMA Utils to XZ Utils. 167 if (f != stdin) 168 printf("%s\n", tuklib_mask_nonprint(name)); 169 170 printf("Uncompressed size: "); 171 if (uncompressed_size == UINT64_MAX) 172 printf("Unknown"); 173 else 174 printf("%" PRIu64 " MB (%" PRIu64 " bytes)", 175 (uncompressed_size / 1024 + 512) / 1024, 176 uncompressed_size); 177 178 lzma_options_lzma *opt = filter.options; 179 180 printf("\nDictionary size: " 181 "%" PRIu32 " MB (2^%" PRIu32 " bytes)\n" 182 "Literal context bits (lc): %" PRIu32 "\n" 183 "Literal pos bits (lp): %" PRIu32 "\n" 184 "Number of pos bits (pb): %" PRIu32 "\n", 185 (opt->dict_size / 1024 + 512) / 1024, 186 my_log2(opt->dict_size), opt->lc, opt->lp, opt->pb); 187 188 free(opt); 189 190 return false; 191 } 192 193 194 extern int 195 main(int argc, char **argv) 196 { 197 tuklib_progname_init(argv); 198 tuklib_gettext_init(PACKAGE, LOCALEDIR); 199 200 parse_args(argc, argv); 201 202 #ifdef TUKLIB_DOSLIKE 203 setmode(fileno(stdin), O_BINARY); 204 #endif 205 206 int ret = EXIT_SUCCESS; 207 208 // We print empty lines around the output only when reading from 209 // files specified on the command line. This is due to how 210 // LZMA Utils did it. 211 if (optind == argc) { 212 if (lzmainfo("(stdin)", stdin)) 213 ret = EXIT_FAILURE; 214 } else { 215 printf("\n"); 216 217 do { 218 if (strcmp(argv[optind], "-") == 0) { 219 if (lzmainfo("(stdin)", stdin)) 220 ret = EXIT_FAILURE; 221 } else { 222 FILE *f = fopen(argv[optind], "r"); 223 if (f == NULL) { 224 ret = EXIT_FAILURE; 225 fprintf(stderr, "%s: %s: %s\n", 226 progname, 227 tuklib_mask_nonprint( 228 argv[optind]), 229 strerror(errno)); 230 continue; 231 } 232 233 if (lzmainfo(argv[optind], f)) 234 ret = EXIT_FAILURE; 235 236 printf("\n"); 237 fclose(f); 238 } 239 } while (++optind < argc); 240 } 241 242 tuklib_exit(ret, EXIT_FAILURE, true); 243 } 244