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