1 /* $NetBSD: file.c,v 1.8 2017/02/10 17:53:24 christos Exp $ */ 2 3 /* 4 * Copyright (c) Ian F. Darwin 1986-1995. 5 * Software written by Ian F. Darwin and others; 6 * maintained 1995-present by Christos Zoulas and others. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice immediately at the beginning of the file, without modification, 13 * this list of conditions, and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 /* 31 * file - find type of a file or files - main program. 32 */ 33 34 #include "file.h" 35 36 #ifndef lint 37 #if 0 38 FILE_RCSID("@(#)$File: file.c,v 1.172 2016/10/24 15:21:07 christos Exp $") 39 #else 40 __RCSID("$NetBSD: file.c,v 1.8 2017/02/10 17:53:24 christos Exp $"); 41 #endif 42 #endif /* lint */ 43 44 #include "magic.h" 45 46 #include <stdlib.h> 47 #include <unistd.h> 48 #include <string.h> 49 #ifdef RESTORE_TIME 50 # if (__COHERENT__ >= 0x420) 51 # include <sys/utime.h> 52 # else 53 # ifdef USE_UTIMES 54 # include <sys/time.h> 55 # else 56 # include <utime.h> 57 # endif 58 # endif 59 #endif 60 #ifdef HAVE_UNISTD_H 61 #include <unistd.h> /* for read() */ 62 #endif 63 #ifdef HAVE_WCHAR_H 64 #include <wchar.h> 65 #endif 66 67 #if defined(HAVE_GETOPT_H) && defined(HAVE_STRUCT_OPTION) 68 #include <getopt.h> 69 #ifndef HAVE_GETOPT_LONG 70 int getopt_long(int argc, char * const *argv, const char *optstring, const struct option *longopts, int *longindex); 71 #endif 72 #else 73 #include "mygetopt.h" 74 #endif 75 76 #ifdef S_IFLNK 77 #define FILE_FLAGS "-bcEhikLlNnprsvzZ0" 78 #else 79 #define FILE_FLAGS "-bcEiklNnprsvzZ0" 80 #endif 81 82 # define USAGE \ 83 "Usage: %s [" FILE_FLAGS \ 84 "] [--apple] [--extension] [--mime-encoding] [--mime-type]\n" \ 85 " [-e testname] [-F separator] [-f namefile] [-m magicfiles] " \ 86 "file ...\n" \ 87 " %s -C [-m magicfiles]\n" \ 88 " %s [--help]\n" 89 90 private int /* Global command-line options */ 91 bflag = 0, /* brief output format */ 92 nopad = 0, /* Don't pad output */ 93 nobuffer = 0, /* Do not buffer stdout */ 94 nulsep = 0; /* Append '\0' to the separator */ 95 96 private const char *separator = ":"; /* Default field separator */ 97 private const struct option long_options[] = { 98 #define OPT_HELP 1 99 #define OPT_APPLE 2 100 #define OPT_EXTENSIONS 3 101 #define OPT_MIME_TYPE 4 102 #define OPT_MIME_ENCODING 5 103 #define OPT(shortname, longname, opt, def, doc) \ 104 {longname, opt, NULL, shortname}, 105 #define OPT_LONGONLY(longname, opt, def, doc, id) \ 106 {longname, opt, NULL, id}, 107 #include "file_opts.h" 108 #undef OPT 109 #undef OPT_LONGONLY 110 {0, 0, NULL, 0} 111 }; 112 #define OPTSTRING "bcCde:Ef:F:hiklLm:nNpP:rsvzZ0" 113 114 private const struct { 115 const char *name; 116 int value; 117 } nv[] = { 118 { "apptype", MAGIC_NO_CHECK_APPTYPE }, 119 { "ascii", MAGIC_NO_CHECK_ASCII }, 120 { "cdf", MAGIC_NO_CHECK_CDF }, 121 { "compress", MAGIC_NO_CHECK_COMPRESS }, 122 { "elf", MAGIC_NO_CHECK_ELF }, 123 { "encoding", MAGIC_NO_CHECK_ENCODING }, 124 { "soft", MAGIC_NO_CHECK_SOFT }, 125 { "tar", MAGIC_NO_CHECK_TAR }, 126 { "text", MAGIC_NO_CHECK_TEXT }, /* synonym for ascii */ 127 { "tokens", MAGIC_NO_CHECK_TOKENS }, /* OBSOLETE: ignored for backwards compatibility */ 128 }; 129 130 private struct { 131 const char *name; 132 int tag; 133 size_t value; 134 } pm[] = { 135 { "indir", MAGIC_PARAM_INDIR_MAX, 0 }, 136 { "name", MAGIC_PARAM_NAME_MAX, 0 }, 137 { "elf_phnum", MAGIC_PARAM_ELF_PHNUM_MAX, 0 }, 138 { "elf_shnum", MAGIC_PARAM_ELF_SHNUM_MAX, 0 }, 139 { "elf_notes", MAGIC_PARAM_ELF_NOTES_MAX, 0 }, 140 { "regex", MAGIC_PARAM_REGEX_MAX, 0 }, 141 { "bytes", MAGIC_PARAM_BYTES_MAX, 0 }, 142 }; 143 144 private char *progname; /* used throughout */ 145 private int posixly; 146 147 #ifdef __dead 148 __dead 149 #endif 150 private void usage(void); 151 private void docprint(const char *, int); 152 #ifdef __dead 153 __dead 154 #endif 155 private void help(void); 156 157 private int unwrap(struct magic_set *, const char *); 158 private int process(struct magic_set *ms, const char *, int); 159 private struct magic_set *load(const char *, int); 160 private void setparam(const char *); 161 private void applyparam(magic_t); 162 163 164 /* 165 * main - parse arguments and handle options 166 */ 167 int 168 main(int argc, char *argv[]) 169 { 170 int c; 171 size_t i; 172 int action = 0, didsomefiles = 0, errflg = 0; 173 int flags = 0, e = 0; 174 struct magic_set *magic = NULL; 175 int longindex; 176 const char *magicfile = NULL; /* where the magic is */ 177 178 /* makes islower etc work for other langs */ 179 #ifdef HAVE_SETLOCALE 180 (void)setlocale(LC_CTYPE, ""); 181 #endif 182 183 #ifdef __EMX__ 184 /* sh-like wildcard expansion! Shouldn't hurt at least ... */ 185 _wildcard(&argc, &argv); 186 #endif 187 188 if ((progname = strrchr(argv[0], '/')) != NULL) 189 progname++; 190 else 191 progname = argv[0]; 192 193 #ifdef S_IFLNK 194 posixly = getenv("POSIXLY_CORRECT") != NULL; 195 flags |= posixly ? MAGIC_SYMLINK : 0; 196 #endif 197 while ((c = getopt_long(argc, argv, OPTSTRING, long_options, 198 &longindex)) != -1) 199 switch (c) { 200 case OPT_HELP: 201 help(); 202 break; 203 case OPT_APPLE: 204 flags |= MAGIC_APPLE; 205 break; 206 case OPT_EXTENSIONS: 207 flags |= MAGIC_EXTENSION; 208 break; 209 case OPT_MIME_TYPE: 210 flags |= MAGIC_MIME_TYPE; 211 break; 212 case OPT_MIME_ENCODING: 213 flags |= MAGIC_MIME_ENCODING; 214 break; 215 case '0': 216 nulsep++; 217 break; 218 case 'b': 219 bflag++; 220 break; 221 case 'c': 222 action = FILE_CHECK; 223 break; 224 case 'C': 225 action = FILE_COMPILE; 226 break; 227 case 'd': 228 flags |= MAGIC_DEBUG|MAGIC_CHECK; 229 break; 230 case 'E': 231 flags |= MAGIC_ERROR; 232 break; 233 case 'e': 234 for (i = 0; i < sizeof(nv) / sizeof(nv[0]); i++) 235 if (strcmp(nv[i].name, optarg) == 0) 236 break; 237 238 if (i == sizeof(nv) / sizeof(nv[0])) 239 errflg++; 240 else 241 flags |= nv[i].value; 242 break; 243 244 case 'f': 245 if(action) 246 usage(); 247 if (magic == NULL) 248 if ((magic = load(magicfile, flags)) == NULL) 249 return 1; 250 applyparam(magic); 251 e |= unwrap(magic, optarg); 252 ++didsomefiles; 253 break; 254 case 'F': 255 separator = optarg; 256 break; 257 case 'i': 258 flags |= MAGIC_MIME; 259 break; 260 case 'k': 261 flags |= MAGIC_CONTINUE; 262 break; 263 case 'l': 264 action = FILE_LIST; 265 break; 266 case 'm': 267 magicfile = optarg; 268 break; 269 case 'n': 270 ++nobuffer; 271 break; 272 case 'N': 273 ++nopad; 274 break; 275 #if defined(HAVE_UTIME) || defined(HAVE_UTIMES) 276 case 'p': 277 flags |= MAGIC_PRESERVE_ATIME; 278 break; 279 #endif 280 case 'P': 281 setparam(optarg); 282 break; 283 case 'r': 284 flags |= MAGIC_RAW; 285 break; 286 case 's': 287 flags |= MAGIC_DEVICES; 288 break; 289 case 'v': 290 if (magicfile == NULL) 291 magicfile = magic_getpath(magicfile, action); 292 (void)fprintf(stdout, "%s-%s\n", progname, VERSION); 293 (void)fprintf(stdout, "magic file from %s\n", 294 magicfile); 295 return 0; 296 case 'z': 297 flags |= MAGIC_COMPRESS; 298 break; 299 300 case 'Z': 301 flags |= MAGIC_COMPRESS|MAGIC_COMPRESS_TRANSP; 302 break; 303 #ifdef S_IFLNK 304 case 'L': 305 flags |= MAGIC_SYMLINK; 306 break; 307 case 'h': 308 flags &= ~MAGIC_SYMLINK; 309 break; 310 #endif 311 case '?': 312 default: 313 errflg++; 314 break; 315 } 316 317 if (errflg) { 318 usage(); 319 } 320 if (e) 321 return e; 322 323 if (MAGIC_VERSION != magic_version()) 324 (void)fprintf(stderr, "%s: compiled magic version [%d] " 325 "does not match with shared library magic version [%d]\n", 326 progname, MAGIC_VERSION, magic_version()); 327 328 switch(action) { 329 case FILE_CHECK: 330 case FILE_COMPILE: 331 case FILE_LIST: 332 /* 333 * Don't try to check/compile ~/.magic unless we explicitly 334 * ask for it. 335 */ 336 magic = magic_open(flags|MAGIC_CHECK); 337 if (magic == NULL) { 338 (void)fprintf(stderr, "%s: %s\n", progname, 339 strerror(errno)); 340 return 1; 341 } 342 343 344 switch(action) { 345 case FILE_CHECK: 346 c = magic_check(magic, magicfile); 347 break; 348 case FILE_COMPILE: 349 c = magic_compile(magic, magicfile); 350 break; 351 case FILE_LIST: 352 c = magic_list(magic, magicfile); 353 break; 354 default: 355 abort(); 356 } 357 if (c == -1) { 358 (void)fprintf(stderr, "%s: %s\n", progname, 359 magic_error(magic)); 360 e = 1; 361 goto out; 362 } 363 goto out; 364 default: 365 if (magic == NULL) 366 if ((magic = load(magicfile, flags)) == NULL) 367 return 1; 368 applyparam(magic); 369 } 370 371 if (optind == argc) { 372 if (!didsomefiles) 373 usage(); 374 } 375 else { 376 size_t j, wid, nw; 377 for (wid = 0, j = (size_t)optind; j < (size_t)argc; j++) { 378 nw = file_mbswidth(argv[j]); 379 if (nw > wid) 380 wid = nw; 381 } 382 /* 383 * If bflag is only set twice, set it depending on 384 * number of files [this is undocumented, and subject to change] 385 */ 386 if (bflag == 2) { 387 bflag = optind >= argc - 1; 388 } 389 for (; optind < argc; optind++) 390 e |= process(magic, argv[optind], wid); 391 } 392 393 out: 394 if (magic) 395 magic_close(magic); 396 return e; 397 } 398 399 private void 400 applyparam(magic_t magic) 401 { 402 size_t i; 403 404 for (i = 0; i < __arraycount(pm); i++) { 405 if (pm[i].value == 0) 406 continue; 407 if (magic_setparam(magic, pm[i].tag, &pm[i].value) == -1) { 408 (void)fprintf(stderr, "%s: Can't set %s %s\n", progname, 409 pm[i].name, strerror(errno)); 410 exit(1); 411 } 412 } 413 } 414 415 private void 416 setparam(const char *p) 417 { 418 size_t i; 419 char *s; 420 421 if ((s = strchr(p, '=')) == NULL) 422 goto badparm; 423 424 for (i = 0; i < __arraycount(pm); i++) { 425 if (strncmp(p, pm[i].name, s - p) != 0) 426 continue; 427 pm[i].value = atoi(s + 1); 428 return; 429 } 430 badparm: 431 (void)fprintf(stderr, "%s: Unknown param %s\n", progname, p); 432 exit(1); 433 } 434 435 private struct magic_set * 436 /*ARGSUSED*/ 437 load(const char *magicfile, int flags) 438 { 439 struct magic_set *magic = magic_open(flags); 440 const char *e; 441 442 if (magic == NULL) { 443 (void)fprintf(stderr, "%s: %s\n", progname, strerror(errno)); 444 return NULL; 445 } 446 if (magic_load(magic, magicfile) == -1) { 447 (void)fprintf(stderr, "%s: %s\n", 448 progname, magic_error(magic)); 449 magic_close(magic); 450 return NULL; 451 } 452 if ((e = magic_error(magic)) != NULL) 453 (void)fprintf(stderr, "%s: Warning: %s\n", progname, e); 454 return magic; 455 } 456 457 /* 458 * unwrap -- read a file of filenames, do each one. 459 */ 460 private int 461 unwrap(struct magic_set *ms, const char *fn) 462 { 463 FILE *f; 464 ssize_t len; 465 char *line = NULL; 466 size_t llen = 0; 467 int wid = 0, cwid; 468 int e = 0; 469 470 if (strcmp("-", fn) == 0) { 471 f = stdin; 472 wid = 1; 473 } else { 474 if ((f = fopen(fn, "r")) == NULL) { 475 (void)fprintf(stderr, "%s: Cannot open `%s' (%s).\n", 476 progname, fn, strerror(errno)); 477 return 1; 478 } 479 480 while ((len = getline(&line, &llen, f)) > 0) { 481 if (line[len - 1] == '\n') 482 line[len - 1] = '\0'; 483 cwid = file_mbswidth(line); 484 if (cwid > wid) 485 wid = cwid; 486 } 487 488 rewind(f); 489 } 490 491 while ((len = getline(&line, &llen, f)) > 0) { 492 if (line[len - 1] == '\n') 493 line[len - 1] = '\0'; 494 e |= process(ms, line, wid); 495 if(nobuffer) 496 (void)fflush(stdout); 497 } 498 499 free(line); 500 (void)fclose(f); 501 return e; 502 } 503 504 /* 505 * Called for each input file on the command line (or in a list of files) 506 */ 507 private int 508 process(struct magic_set *ms, const char *inname, int wid) 509 { 510 const char *type, c = nulsep > 1 ? '\0' : '\n'; 511 int std_in = strcmp(inname, "-") == 0; 512 513 if (wid > 0 && !bflag) { 514 (void)printf("%s", std_in ? "/dev/stdin" : inname); 515 if (nulsep) 516 (void)putc('\0', stdout); 517 if (nulsep < 2) { 518 (void)printf("%s", separator); 519 (void)printf("%*s ", 520 (int) (nopad ? 0 : (wid - file_mbswidth(inname))), 521 ""); 522 } 523 } 524 525 type = magic_file(ms, std_in ? NULL : inname); 526 527 if (type == NULL) { 528 (void)printf("ERROR: %s%c", magic_error(ms), c); 529 return 1; 530 } else { 531 (void)printf("%s%c", type, c); 532 return 0; 533 } 534 } 535 536 protected size_t 537 file_mbswidth(const char *s) 538 { 539 #if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH) 540 size_t bytesconsumed, old_n, n, width = 0; 541 mbstate_t state; 542 wchar_t nextchar; 543 (void)memset(&state, 0, sizeof(mbstate_t)); 544 old_n = n = strlen(s); 545 546 while (n > 0) { 547 bytesconsumed = mbrtowc(&nextchar, s, n, &state); 548 if (bytesconsumed == (size_t)(-1) || 549 bytesconsumed == (size_t)(-2)) { 550 /* Something went wrong, return something reasonable */ 551 return old_n; 552 } 553 if (s[0] == '\n') { 554 /* 555 * do what strlen() would do, so that caller 556 * is always right 557 */ 558 width++; 559 } else { 560 int w = wcwidth(nextchar); 561 if (w > 0) 562 width += w; 563 } 564 565 s += bytesconsumed, n -= bytesconsumed; 566 } 567 return width; 568 #else 569 return strlen(s); 570 #endif 571 } 572 573 private void 574 usage(void) 575 { 576 (void)fprintf(stderr, USAGE, progname, progname, progname); 577 exit(1); 578 } 579 580 private void 581 defprint(int def) 582 { 583 if (!def) 584 return; 585 if (((def & 1) && posixly) || ((def & 2) && !posixly)) 586 fprintf(stdout, " (default)"); 587 fputc('\n', stdout); 588 } 589 590 private void 591 docprint(const char *opts, int def) 592 { 593 size_t i; 594 int comma; 595 char *sp, *p; 596 597 p = strstr(opts, "%o"); 598 if (p == NULL) { 599 fprintf(stdout, "%s", opts); 600 defprint(def); 601 return; 602 } 603 604 for (sp = p - 1; sp > opts && *sp == ' '; sp--) 605 continue; 606 607 fprintf(stdout, "%.*s", (int)(p - opts), opts); 608 609 comma = 0; 610 for (i = 0; i < __arraycount(nv); i++) { 611 fprintf(stdout, "%s%s", comma++ ? ", " : "", nv[i].name); 612 if (i && i % 5 == 0) { 613 fprintf(stdout, ",\n%*s", (int)(p - sp - 1), ""); 614 comma = 0; 615 } 616 } 617 618 fprintf(stdout, "%s", opts + (p - opts) + 2); 619 } 620 621 private void 622 help(void) 623 { 624 (void)fputs( 625 "Usage: file [OPTION...] [FILE...]\n" 626 "Determine type of FILEs.\n" 627 "\n", stdout); 628 #define OPT(shortname, longname, opt, def, doc) \ 629 fprintf(stdout, " -%c, --" longname, shortname), \ 630 docprint(doc, def); 631 #define OPT_LONGONLY(longname, opt, def, doc, id) \ 632 fprintf(stdout, " --" longname), \ 633 docprint(doc, def); 634 #include "file_opts.h" 635 #undef OPT 636 #undef OPT_LONGONLY 637 fprintf(stdout, "\nReport bugs to http://bugs.gw.com/\n"); 638 exit(0); 639 } 640