30e04fa60ac4be2503b7bd09e250f169f4df62a8
[dragonfly.git] / contrib / file-4 / src / file.c
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 #include "magic.h"
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <string.h>
39 #include <sys/types.h>
40 #include <sys/param.h>  /* for MAXPATHLEN */
41 #include <sys/stat.h>
42 #ifdef RESTORE_TIME
43 # if (__COHERENT__ >= 0x420)
44 #  include <sys/utime.h>
45 # else
46 #  ifdef USE_UTIMES
47 #   include <sys/time.h>
48 #  else
49 #   include <utime.h>
50 #  endif
51 # endif
52 #endif
53 #ifdef HAVE_UNISTD_H
54 #include <unistd.h>     /* for read() */
55 #endif
56 #ifdef HAVE_LOCALE_H
57 #include <locale.h>
58 #endif
59 #ifdef HAVE_WCHAR_H
60 #include <wchar.h>
61 #endif
62
63 #ifdef HAVE_GETOPT_H
64 #include <getopt.h>     /* for long options (is this portable?)*/
65 #else
66 #undef HAVE_GETOPT_LONG
67 #endif
68
69 #include <netinet/in.h>         /* for byte swapping */
70
71 #include "patchlevel.h"
72
73 #ifndef lint
74 FILE_RCSID("@(#)$Id: file.c,v 1.102 2006/06/02 00:07:58 ian Exp $")
75 #endif  /* lint */
76
77
78 #ifdef S_IFLNK
79 #define SYMLINKFLAG "Lh"
80 #else
81 #define SYMLINKFLAG ""
82 #endif
83
84 # define USAGE  "Usage: %s [-bcik" SYMLINKFLAG "nNrsvz] [-f namefile] [-F separator] [-m magicfiles] file...\n       %s -C -m magicfiles\n"
85
86 #ifndef MAXPATHLEN
87 #define MAXPATHLEN      512
88 #endif
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
95 private const char *magicfile = 0;      /* where the magic is   */
96 private const char *default_magicfile = MAGIC;
97 private const char *separator = ":";    /* Default field separator      */
98
99 private char *progname;         /* used throughout              */
100
101 private struct magic_set *magic;
102
103 private void unwrap(char *);
104 private void usage(void);
105 #ifdef HAVE_GETOPT_LONG
106 private void help(void);
107 #endif
108 #if 0
109 private int byteconv4(int, int, int);
110 private short byteconv2(int, int, int);
111 #endif
112
113 int main(int, char *[]);
114 private void process(const char *, int);
115 private void load(const char *, int);
116
117
118 /*
119  * main - parse arguments and handle options
120  */
121 int
122 main(int argc, char *argv[])
123 {
124         int c;
125         int action = 0, didsomefiles = 0, errflg = 0;
126         int flags = 0;
127         char *home, *usermagic;
128         struct stat sb;
129         static const char hmagic[] = "/.magic";
130 #define OPTSTRING       "bcCdf:F:hikLm:nNprsvz"
131 #ifdef HAVE_GETOPT_LONG
132         int longindex;
133         static const struct option long_options[] =
134         {
135                 {"version", 0, 0, 'v'},
136                 {"help", 0, 0, 0},
137                 {"brief", 0, 0, 'b'},
138                 {"checking-printout", 0, 0, 'c'},
139                 {"debug", 0, 0, 'd'},
140                 {"files-from", 1, 0, 'f'},
141                 {"separator", 1, 0, 'F'},
142                 {"mime", 0, 0, 'i'},
143                 {"keep-going", 0, 0, 'k'},
144 #ifdef S_IFLNK
145                 {"dereference", 0, 0, 'L'},
146                 {"no-dereference", 0, 0, 'h'},
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         /* makes islower etc work for other langs */
164         (void)setlocale(LC_CTYPE, "");
165 #endif
166
167 #ifdef __EMX__
168         /* sh-like wildcard expansion! Shouldn't hurt at least ... */
169         _wildcard(&argc, &argv);
170 #endif
171
172         if ((progname = strrchr(argv[0], '/')) != NULL)
173                 progname++;
174         else
175                 progname = argv[0];
176
177         magicfile = default_magicfile;
178         if ((usermagic = getenv("MAGIC")) != NULL)
179                 magicfile = usermagic;
180         else
181                 if ((home = getenv("HOME")) != NULL) {
182                         if ((usermagic = malloc(strlen(home)
183                             + sizeof(hmagic))) != NULL) {
184                                 (void)strcpy(usermagic, home);
185                                 (void)strcat(usermagic, hmagic);
186                                 if (stat(usermagic, &sb)<0) 
187                                         free(usermagic);
188                                 else
189                                         magicfile = usermagic;
190                         }
191                 }
192
193 #ifdef S_IFLNK
194         flags |= getenv("POSIXLY_CORRECT") ? MAGIC_SYMLINK : 0;
195 #endif
196 #ifndef HAVE_GETOPT_LONG
197         while ((c = getopt(argc, argv, OPTSTRING)) != -1)
198 #else
199         while ((c = getopt_long(argc, argv, OPTSTRING, long_options,
200             &longindex)) != -1)
201 #endif
202                 switch (c) {
203 #ifdef HAVE_GETOPT_LONG
204                 case 0 :
205                         if (longindex == 1)
206                                 help();
207                         break;
208 #endif
209                 case 'b':
210                         ++bflag;
211                         break;
212                 case 'c':
213                         action = FILE_CHECK;
214                         break;
215                 case 'C':
216                         action = FILE_COMPILE;
217                         break;
218                 case 'd':
219                         flags |= MAGIC_DEBUG|MAGIC_CHECK;
220                         break;
221                 case 'f':
222                         if(action)
223                                 usage();
224                         load(magicfile, flags);
225                         unwrap(optarg);
226                         ++didsomefiles;
227                         break;
228                 case 'F':
229                         separator = optarg;
230                         break;
231                 case 'i':
232                         flags |= MAGIC_MIME;
233                         break;
234                 case 'k':
235                         flags |= MAGIC_CONTINUE;
236                         break;
237                 case 'm':
238                         magicfile = optarg;
239                         break;
240                 case 'n':
241                         ++nobuffer;
242                         break;
243                 case 'N':
244                         ++nopad;
245                         break;
246 #if defined(HAVE_UTIME) || defined(HAVE_UTIMES)
247                 case 'p':
248                         flags |= MAGIC_PRESERVE_ATIME;
249                         break;
250 #endif
251                 case 'r':
252                         flags |= MAGIC_RAW;
253                         break;
254                 case 's':
255                         flags |= MAGIC_DEVICES;
256                         break;
257                 case 'v':
258                         (void)fprintf(stdout, "%s-%d.%.2d\n", progname,
259                                        FILE_VERSION_MAJOR, patchlevel);
260                         (void)fprintf(stdout, "magic file from %s\n",
261                                        magicfile);
262                         return 1;
263                 case 'z':
264                         flags |= MAGIC_COMPRESS;
265                         break;
266 #ifdef S_IFLNK
267                 case 'L':
268                         flags |= MAGIC_SYMLINK;
269                         break;
270                 case 'h':
271                         flags &= ~MAGIC_SYMLINK;
272                         break;
273 #endif
274                 case '?':
275                 default:
276                         errflg++;
277                         break;
278                 }
279
280         if (errflg) {
281                 usage();
282         }
283
284         switch(action) {
285         case FILE_CHECK:
286         case FILE_COMPILE:
287                 magic = magic_open(flags|MAGIC_CHECK);
288                 if (magic == NULL) {
289                         (void)fprintf(stderr, "%s: %s\n", progname,
290                             strerror(errno));
291                         return 1;
292                 }
293                 c = action == FILE_CHECK ? magic_check(magic, magicfile) :
294                     magic_compile(magic, magicfile);
295                 if (c == -1) {
296                         (void)fprintf(stderr, "%s: %s\n", progname,
297                             magic_error(magic));
298                         return -1;
299                 }
300                 return 0;
301         default:
302                 load(magicfile, flags);
303                 break;
304         }
305
306         if (optind == argc) {
307                 if (!didsomefiles) {
308                         usage();
309                 }
310         }
311         else {
312                 int i, wid, nw;
313                 for (wid = 0, i = optind; i < argc; i++) {
314                         nw = file_mbswidth(argv[i]);
315                         if (nw > wid)
316                                 wid = nw;
317                 }
318                 for (; optind < argc; optind++)
319                         process(argv[optind], wid);
320         }
321
322         magic_close(magic);
323         return 0;
324 }
325
326
327 private void
328 /*ARGSUSED*/
329 load(const char *m, int flags)
330 {
331         if (magic)
332                 return;
333         magic = magic_open(flags);
334         if (magic == NULL) {
335                 (void)fprintf(stderr, "%s: %s\n", progname, strerror(errno));
336                 exit(1);
337         }
338         if (magic_load(magic, magicfile) == -1) {
339                 (void)fprintf(stderr, "%s: %s\n",
340                     progname, magic_error(magic));
341                 exit(1);
342         }
343 }
344
345 /*
346  * unwrap -- read a file of filenames, do each one.
347  */
348 private void
349 unwrap(char *fn)
350 {
351         char buf[MAXPATHLEN];
352         FILE *f;
353         int wid = 0, cwid;
354         size_t len;
355
356         if (strcmp("-", fn) == 0) {
357                 f = stdin;
358                 wid = 1;
359         } else {
360                 if ((f = fopen(fn, "r")) == NULL) {
361                         (void)fprintf(stderr, "%s: Cannot open `%s' (%s).\n",
362                             progname, fn, strerror(errno));
363                         exit(1);
364                 }
365
366                 while (fgets(buf, MAXPATHLEN, f) != NULL) {
367                         len = strlen(buf);
368                         if (len > 0 && buf[len - 1] == '\n')
369                                 buf[len - 1] = '\0';
370                         cwid = file_mbswidth(buf);
371                         if (cwid > wid)
372                                 wid = cwid;
373                 }
374
375                 rewind(f);
376         }
377
378         while (fgets(buf, MAXPATHLEN, f) != NULL) {
379                 len = strlen(buf);
380                 if (len > 0 && buf[len - 1] == '\n')
381                         buf[len - 1] = '\0';
382                 process(buf, wid);
383                 if(nobuffer)
384                         (void)fflush(stdout);
385         }
386
387         (void)fclose(f);
388 }
389
390 /*
391  * Called for each input file on the command line (or in a list of files)
392  */
393 private void
394 process(const char *inname, int wid)
395 {
396         const char *type;
397         int std_in = strcmp(inname, "-") == 0;
398
399         if (wid > 0 && !bflag)
400                 (void)printf("%s%s%*s ", std_in ? "/dev/stdin" : inname,
401                     separator, (int) (nopad ? 0 : (wid - file_mbswidth(inname))), "");
402
403         type = magic_file(magic, std_in ? NULL : inname);
404         if (type == NULL)
405                 (void)printf("ERROR: %s\n", magic_error(magic));
406         else
407                 (void)printf("%s\n", type);
408 }
409
410
411 #if 0
412 /*
413  * byteconv4
414  * Input:
415  *      from            4 byte quantity to convert
416  *      same            whether to perform byte swapping
417  *      big_endian      whether we are a big endian host
418  */
419 private int
420 byteconv4(int from, int same, int big_endian)
421 {
422         if (same)
423                 return from;
424         else if (big_endian) {          /* lsb -> msb conversion on msb */
425                 union {
426                         int i;
427                         char c[4];
428                 } retval, tmpval;
429
430                 tmpval.i = from;
431                 retval.c[0] = tmpval.c[3];
432                 retval.c[1] = tmpval.c[2];
433                 retval.c[2] = tmpval.c[1];
434                 retval.c[3] = tmpval.c[0];
435
436                 return retval.i;
437         }
438         else
439                 return ntohl(from);     /* msb -> lsb conversion on lsb */
440 }
441
442 /*
443  * byteconv2
444  * Same as byteconv4, but for shorts
445  */
446 private short
447 byteconv2(int from, int same, int big_endian)
448 {
449         if (same)
450                 return from;
451         else if (big_endian) {          /* lsb -> msb conversion on msb */
452                 union {
453                         short s;
454                         char c[2];
455                 } retval, tmpval;
456
457                 tmpval.s = (short) from;
458                 retval.c[0] = tmpval.c[1];
459                 retval.c[1] = tmpval.c[0];
460
461                 return retval.s;
462         }
463         else
464                 return ntohs(from);     /* msb -> lsb conversion on lsb */
465 }
466 #endif
467
468 size_t
469 file_mbswidth(const char *s)
470 {
471 #if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH)
472         size_t bytesconsumed, old_n, n, width = 0;
473         mbstate_t state;
474         wchar_t nextchar;
475         (void)memset(&state, 0, sizeof(mbstate_t));
476         old_n = n = strlen(s);
477
478         while (n > 0) {
479                 bytesconsumed = mbrtowc(&nextchar, s, n, &state);
480                 if (bytesconsumed == (size_t)(-1) ||
481                     bytesconsumed == (size_t)(-2)) {
482                         /* Something went wrong, return something reasonable */
483                         return old_n;
484                 }
485                 if (s[0] == '\n') {
486                         /*
487                          * do what strlen() would do, so that caller
488                          * is always right
489                          */
490                         width++;
491                 } else
492                         width += wcwidth(nextchar);
493
494                 s += bytesconsumed, n -= bytesconsumed;
495         }
496         return width;
497 #else
498         return strlen(s);
499 #endif
500 }
501
502 private void
503 usage(void)
504 {
505         (void)fprintf(stderr, USAGE, progname, progname);
506 #ifdef HAVE_GETOPT_LONG
507         (void)fputs("Try `file --help' for more information.\n", stderr);
508 #endif
509         exit(1);
510 }
511
512 #ifdef HAVE_GETOPT_LONG
513 private void
514 help(void)
515 {
516         (void)puts(
517 "Usage: file [OPTION]... [FILE]...\n"
518 "Determine file type of FILEs.\n"
519 "\n"
520 "  -m, --magic-file LIST      use LIST as a colon-separated list of magic\n"
521 "                               number files\n"
522 "  -z, --uncompress           try to look inside compressed files\n"
523 "  -b, --brief                do not prepend filenames to output lines\n"
524 "  -c, --checking-printout    print the parsed form of the magic file, use in\n"
525 "                               conjunction with -m to debug a new magic file\n"
526 "                               before installing it\n"
527 "  -f, --files-from FILE      read the filenames to be examined from FILE\n"
528 "  -F, --separator string     use string as separator instead of `:'\n"
529 "  -i, --mime                 output mime type strings\n"
530 "  -k, --keep-going           don't stop at the first match\n"
531 "  -L, --dereference          causes symlinks to be followed\n"
532 "  -n, --no-buffer            do not buffer output\n"
533 "  -N, --no-pad               do not pad output\n"
534 "  -p, --preserve-date        preserve access times on files\n"
535 "  -r, --raw                  don't translate unprintable chars to \\ooo\n"
536 "  -s, --special-files        treat special (block/char devices) files as\n"
537 "                             ordinary ones\n"
538 "      --help                 display this help and exit\n"
539 "      --version              output version information and exit\n"
540 );
541         exit(0);
542 }
543 #endif