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