1 /* $OpenBSD: file.c,v 1.17 2007/02/19 13:02:08 tom Exp $ */ 2 /* 3 * Copyright (c) Ian F. Darwin 1986-1995. 4 * Software written by Ian F. Darwin and others; 5 * maintained 1995-present by Christos Zoulas and others. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice immediately at the beginning of the file, without modification, 12 * this list of conditions, and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 21 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 /* 30 * file - find type of a file or files - main program. 31 */ 32 33 #include "file.h" 34 #include "magic.h" 35 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <unistd.h> 39 #include <string.h> 40 #include <sys/types.h> 41 #include <sys/param.h> /* for MAXPATHLEN */ 42 #include <sys/stat.h> 43 #include <fcntl.h> /* for open() */ 44 #ifdef RESTORE_TIME 45 # if (__COHERENT__ >= 0x420) 46 # include <sys/utime.h> 47 # else 48 # ifdef USE_UTIMES 49 # include <sys/time.h> 50 # else 51 # include <utime.h> 52 # endif 53 # endif 54 #endif 55 #ifdef HAVE_UNISTD_H 56 #include <unistd.h> /* for read() */ 57 #endif 58 #ifdef HAVE_LOCALE_H 59 #include <locale.h> 60 #endif 61 #ifdef HAVE_WCHAR_H 62 #include <wchar.h> 63 #endif 64 65 #ifdef HAVE_GETOPT_H 66 #include <getopt.h> /* for long options (is this portable?)*/ 67 #else 68 #undef HAVE_GETOPT_LONG 69 #endif 70 71 #include <netinet/in.h> /* for byte swapping */ 72 73 #include "patchlevel.h" 74 75 #ifndef lint 76 FILE_RCSID("@(#)$Id: file.c,v 1.17 2007/02/19 13:02:08 tom Exp $") 77 #endif /* lint */ 78 79 80 #ifdef S_IFLNK 81 #define SYMLINKFLAG "L" 82 #else 83 #define SYMLINKFLAG "" 84 #endif 85 86 #define USAGE "Usage: %s [-bck" SYMLINKFLAG "Nnrsvz] [-F separator] [-f namefile] [-m magicfiles] file ...\n" \ 87 " %s [-m magicfiles] -C\n" 88 89 #ifndef MAXPATHLEN 90 #define MAXPATHLEN 512 91 #endif 92 93 private int /* Global command-line options */ 94 bflag = 0, /* brief output format */ 95 nopad = 0, /* Don't pad output */ 96 nobuffer = 0; /* Do not buffer stdout */ 97 98 private const char *magicfile = 0; /* where the magic is */ 99 private const char *default_magicfile = MAGIC; 100 private char *separator = ":"; /* Default field separator */ 101 102 private char *progname; /* used throughout */ 103 104 private struct magic_set *magic; 105 106 private void unwrap(char *); 107 private void usage(void); 108 #ifdef HAVE_GETOPT_LONG 109 private void help(void); 110 #endif 111 #if 0 112 private int byteconv4(int, int, int); 113 private short byteconv2(int, int, int); 114 #endif 115 116 int main(int, char *[]); 117 private void process(const char *, int); 118 private void load(const char *, int); 119 120 121 /* 122 * main - parse arguments and handle options 123 */ 124 int 125 main(int argc, char *argv[]) 126 { 127 int c; 128 int action = 0, didsomefiles = 0, errflg = 0; 129 int flags = 0; 130 char *home, *usermagic; 131 struct stat sb; 132 #define OPTSTRING "bcCdf:F:kLm:nNprsvz" 133 #ifdef HAVE_GETOPT_LONG 134 int longindex; 135 private struct option long_options[] = 136 { 137 {"version", 0, 0, 'v'}, 138 {"help", 0, 0, 0}, 139 {"brief", 0, 0, 'b'}, 140 {"checking-printout", 0, 0, 'c'}, 141 {"debug", 0, 0, 'd'}, 142 {"files-from", 1, 0, 'f'}, 143 {"separator", 1, 0, 'F'}, 144 {"keep-going", 0, 0, 'k'}, 145 #ifdef S_IFLNK 146 {"dereference", 0, 0, 'L'}, 147 #endif 148 {"magic-file", 1, 0, 'm'}, 149 #if defined(HAVE_UTIME) || defined(HAVE_UTIMES) 150 {"preserve-date", 0, 0, 'p'}, 151 #endif 152 {"uncompress", 0, 0, 'z'}, 153 {"raw", 0, 0, 'r'}, 154 {"no-buffer", 0, 0, 'n'}, 155 {"no-pad", 0, 0, 'N'}, 156 {"special-files", 0, 0, 's'}, 157 {"compile", 0, 0, 'C'}, 158 {0, 0, 0, 0}, 159 }; 160 #endif 161 162 #ifdef LC_CTYPE 163 setlocale(LC_CTYPE, ""); /* makes islower etc work for other langs */ 164 #endif 165 166 #ifdef __EMX__ 167 /* sh-like wildcard expansion! Shouldn't hurt at least ... */ 168 _wildcard(&argc, &argv); 169 #endif 170 171 if ((progname = strrchr(argv[0], '/')) != NULL) 172 progname++; 173 else 174 progname = argv[0]; 175 176 magicfile = default_magicfile; 177 if ((usermagic = getenv("MAGIC")) != NULL) 178 magicfile = usermagic; 179 else 180 if ((home = getenv("HOME")) != NULL) { 181 size_t len = strlen(home) + 8; 182 if ((usermagic = malloc(len)) != NULL) { 183 (void)strlcpy(usermagic, home, len); 184 (void)strlcat(usermagic, "/.magic", len); 185 if (stat(usermagic, &sb)<0) 186 free(usermagic); 187 else 188 magicfile = usermagic; 189 } 190 } 191 192 #ifndef HAVE_GETOPT_LONG 193 while ((c = getopt(argc, argv, OPTSTRING)) != -1) 194 #else 195 while ((c = getopt_long(argc, argv, OPTSTRING, long_options, 196 &longindex)) != -1) 197 #endif 198 switch (c) { 199 #ifdef HAVE_GETOPT_LONG 200 case 0 : 201 if (longindex == 1) 202 help(); 203 break; 204 #endif 205 case 'b': 206 ++bflag; 207 break; 208 case 'c': 209 action = FILE_CHECK; 210 break; 211 case 'C': 212 action = FILE_COMPILE; 213 break; 214 case 'd': 215 flags |= MAGIC_DEBUG|MAGIC_CHECK; 216 break; 217 case 'f': 218 if(action) 219 usage(); 220 load(magicfile, flags); 221 unwrap(optarg); 222 ++didsomefiles; 223 break; 224 case 'F': 225 separator = optarg; 226 break; 227 case 'k': 228 flags |= MAGIC_CONTINUE; 229 break; 230 case 'm': 231 magicfile = optarg; 232 break; 233 case 'n': 234 ++nobuffer; 235 break; 236 case 'N': 237 ++nopad; 238 break; 239 #if defined(HAVE_UTIME) || defined(HAVE_UTIMES) 240 case 'p': 241 flags |= MAGIC_PRESERVE_ATIME; 242 break; 243 #endif 244 case 'r': 245 flags |= MAGIC_RAW; 246 break; 247 case 's': 248 flags |= MAGIC_DEVICES; 249 break; 250 case 'v': 251 (void) fprintf(stdout, "%s-%d.%.2d\n", progname, 252 FILE_VERSION_MAJOR, patchlevel); 253 (void) fprintf(stdout, "magic file from %s\n", 254 magicfile); 255 return 1; 256 case 'z': 257 flags |= MAGIC_COMPRESS; 258 break; 259 #ifdef S_IFLNK 260 case 'L': 261 flags |= MAGIC_SYMLINK; 262 break; 263 #endif 264 case '?': 265 default: 266 errflg++; 267 break; 268 } 269 270 if (errflg) { 271 usage(); 272 } 273 274 switch(action) { 275 case FILE_CHECK: 276 case FILE_COMPILE: 277 magic = magic_open(flags|MAGIC_CHECK); 278 if (magic == NULL) { 279 (void)fprintf(stderr, "%s: %s\n", progname, 280 strerror(errno)); 281 return 1; 282 } 283 c = action == FILE_CHECK ? magic_check(magic, magicfile) : 284 magic_compile(magic, magicfile); 285 if (c == -1) { 286 (void)fprintf(stderr, "%s: %s\n", progname, 287 magic_error(magic)); 288 return -1; 289 } 290 return 0; 291 default: 292 load(magicfile, flags); 293 break; 294 } 295 296 if (optind == argc) { 297 if (!didsomefiles) { 298 usage(); 299 } 300 } 301 else { 302 int i, wid, nw; 303 for (wid = 0, i = optind; i < argc; i++) { 304 nw = file_mbswidth(argv[i]); 305 if (nw > wid) 306 wid = nw; 307 } 308 for (; optind < argc; optind++) 309 process(argv[optind], wid); 310 } 311 312 return 0; 313 } 314 315 316 private void 317 load(const char *m, int flags) 318 { 319 if (magic) 320 return; 321 magic = magic_open(flags); 322 if (magic == NULL) { 323 (void)fprintf(stderr, "%s: %s\n", progname, strerror(errno)); 324 exit(1); 325 } 326 if (magic_load(magic, magicfile) == -1) { 327 (void)fprintf(stderr, "%s: %s\n", 328 progname, magic_error(magic)); 329 exit(1); 330 } 331 } 332 333 /* 334 * unwrap -- read a file of filenames, do each one. 335 */ 336 private void 337 unwrap(char *fn) 338 { 339 char buf[MAXPATHLEN]; 340 FILE *f; 341 int wid = 0, cwid; 342 343 if (strcmp("-", fn) == 0) { 344 f = stdin; 345 wid = 1; 346 } else { 347 if ((f = fopen(fn, "r")) == NULL) { 348 (void)fprintf(stderr, "%s: Cannot open `%s' (%s).\n", 349 progname, fn, strerror(errno)); 350 exit(1); 351 } 352 353 while (fgets(buf, sizeof(buf), f) != NULL) { 354 cwid = file_mbswidth(buf) - 1; 355 if (cwid > wid) 356 wid = cwid; 357 } 358 359 rewind(f); 360 } 361 362 while (fgets(buf, sizeof(buf), f) != NULL) { 363 buf[file_mbswidth(buf)-1] = '\0'; 364 process(buf, wid); 365 if(nobuffer) 366 (void) fflush(stdout); 367 } 368 369 (void) fclose(f); 370 } 371 372 private void 373 process(const char *inname, int wid) 374 { 375 const char *type; 376 int std_in = strcmp(inname, "-") == 0; 377 378 if (wid > 0 && !bflag) 379 (void) printf("%s%s%*s ", std_in ? "/dev/stdin" : inname, 380 separator, (int) (nopad ? 0 : (wid - file_mbswidth(inname))), ""); 381 382 type = magic_file(magic, std_in ? NULL : inname); 383 if (type == NULL) 384 printf("ERROR: %s\n", magic_error(magic)); 385 else 386 printf("%s\n", type); 387 } 388 389 390 #if 0 391 /* 392 * byteconv4 393 * Input: 394 * from 4 byte quantity to convert 395 * same whether to perform byte swapping 396 * big_endian whether we are a big endian host 397 */ 398 private int 399 byteconv4(int from, int same, int big_endian) 400 { 401 if (same) 402 return from; 403 else if (big_endian) { /* lsb -> msb conversion on msb */ 404 union { 405 int i; 406 char c[4]; 407 } retval, tmpval; 408 409 tmpval.i = from; 410 retval.c[0] = tmpval.c[3]; 411 retval.c[1] = tmpval.c[2]; 412 retval.c[2] = tmpval.c[1]; 413 retval.c[3] = tmpval.c[0]; 414 415 return retval.i; 416 } 417 else 418 return ntohl(from); /* msb -> lsb conversion on lsb */ 419 } 420 421 /* 422 * byteconv2 423 * Same as byteconv4, but for shorts 424 */ 425 private short 426 byteconv2(int from, int same, int big_endian) 427 { 428 if (same) 429 return from; 430 else if (big_endian) { /* lsb -> msb conversion on msb */ 431 union { 432 short s; 433 char c[2]; 434 } retval, tmpval; 435 436 tmpval.s = (short) from; 437 retval.c[0] = tmpval.c[1]; 438 retval.c[1] = tmpval.c[0]; 439 440 return retval.s; 441 } 442 else 443 return ntohs(from); /* msb -> lsb conversion on lsb */ 444 } 445 #endif 446 447 size_t 448 file_mbswidth(const char *s) 449 { 450 #ifdef HAVE_WCHAR_H 451 size_t bytesconsumed, old_n, n, width = 0; 452 mbstate_t state; 453 wchar_t nextchar; 454 (void)memset(&state, 0, sizeof(mbstate_t)); 455 old_n = n = strlen(s); 456 457 while (n > 0) { 458 bytesconsumed = mbrtowc(&nextchar, s, n, &state); 459 if (bytesconsumed == (size_t)(-1) || 460 bytesconsumed == (size_t)(-2)) { 461 /* Something went wrong, return something reasonable */ 462 return old_n; 463 } 464 if (s[0] == '\n') { 465 /* 466 * do what strlen() would do, so that caller 467 * is always right 468 */ 469 width++; 470 } else 471 width += wcwidth(nextchar); 472 473 s += bytesconsumed, n -= bytesconsumed; 474 } 475 return width; 476 #else 477 return strlen(s); 478 #endif 479 } 480 481 private void 482 usage(void) 483 { 484 (void)fprintf(stderr, USAGE, progname, progname); 485 #ifdef HAVE_GETOPT_LONG 486 (void)fputs("Try `file --help' for more information.\n", stderr); 487 #endif 488 exit(1); 489 } 490 491 #ifdef HAVE_GETOPT_LONG 492 private void 493 help(void) 494 { 495 puts( 496 "Usage: file [OPTION]... [FILE]...\n" 497 "Determine file type of FILEs.\n" 498 "\n" 499 " -m, --magic-file LIST use LIST as a colon-separated list of magic\n" 500 " number files\n" 501 " -z, --uncompress try to look inside compressed files\n" 502 " -b, --brief do not prepend filenames to output lines\n" 503 " -c, --checking-printout print the parsed form of the magic file, use in\n" 504 " conjunction with -m to debug a new magic file\n" 505 " before installing it\n" 506 " -f, --files-from FILE read the filenames to be examined from FILE\n" 507 " -F, --separator string use string as separator instead of `:'\n" 508 " -k, --keep-going don't stop at the first match\n" 509 " -L, --dereference causes symlinks to be followed\n" 510 " -n, --no-buffer do not buffer output\n" 511 " -N, --no-pad do not pad output\n" 512 " -p, --preserve-date preserve access times on files\n" 513 " -r, --raw don't translate unprintable chars to \\ooo\n" 514 " -s, --special-files treat special (block/char devices) files as\n" 515 " ordinary ones\n" 516 " --help display this help and exit\n" 517 " --version output version information and exit\n" 518 ); 519 exit(0); 520 } 521 #endif 522