Upgrade to file-5.03.
[dragonfly.git] / contrib / file / 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
34 #ifndef lint
35 FILE_RCSID("@(#)$File: file.c,v 1.131 2009/02/13 18:48:05 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_LOCALE_H
58 #include <locale.h>
59 #endif
60 #ifdef HAVE_WCHAR_H
61 #include <wchar.h>
62 #endif
63
64 #if defined(HAVE_GETOPT_H) && defined(HAVE_STRUCT_OPTION)
65 #include <getopt.h>
66 #else
67 #include "mygetopt.h"
68 #endif
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
73 #include <netinet/in.h>         /* for byte swapping */
74
75 #include "patchlevel.h"
76
77 #ifdef S_IFLNK
78 #define SYMLINKFLAG "Lh"
79 #else
80 #define SYMLINKFLAG ""
81 #endif
82
83 # define USAGE  "Usage: %s [-bcik" SYMLINKFLAG "nNrsvz0] [-e test] [-f namefile] [-F separator] [-m magicfiles] file...\n       %s -C -m magicfiles\n"
84
85 #ifndef MAXPATHLEN
86 #define MAXPATHLEN      1024
87 #endif
88
89 private int             /* Global command-line options          */
90         bflag = 0,      /* brief output format                  */
91         nopad = 0,      /* Don't pad output                     */
92         nobuffer = 0,   /* Do not buffer stdout                 */
93         nulsep = 0;     /* Append '\0' to the separator         */
94
95 private const char *default_magicfile = MAGIC;
96 private const char *separator = ":";    /* Default field separator      */
97 private const char hmagic[] = "/.magic";
98 private const struct option long_options[] = {
99 #define OPT(shortname, longname, opt, doc)      \
100     {longname, opt, NULL, shortname},
101 #define OPT_LONGONLY(longname, opt, doc)        \
102     {longname, opt, NULL, 0},
103 #include "file_opts.h"
104 #undef OPT
105 #undef OPT_LONGONLY
106     {0, 0, NULL, 0}
107 };
108 #define OPTSTRING       "bcCde:f:F:hikLm:nNprsvz0"
109
110 private const struct {
111         const char *name;
112         int value;
113 } nv[] = {
114         { "apptype",    MAGIC_NO_CHECK_APPTYPE },
115         { "ascii",      MAGIC_NO_CHECK_ASCII },
116         { "cdf",        MAGIC_NO_CHECK_CDF },
117         { "compress",   MAGIC_NO_CHECK_COMPRESS },
118         { "elf",        MAGIC_NO_CHECK_ELF },
119         { "encoding",   MAGIC_NO_CHECK_ENCODING },
120         { "soft",       MAGIC_NO_CHECK_SOFT },
121         { "tar",        MAGIC_NO_CHECK_TAR },
122         { "tokens",     MAGIC_NO_CHECK_TOKENS },
123 };
124
125 private char *progname;         /* used throughout              */
126
127 private void usage(void);
128 private void help(void);
129 int main(int, char *[]);
130
131 private int unwrap(struct magic_set *, const char *);
132 private int process(struct magic_set *ms, const char *, int);
133 private struct magic_set *load(const char *, int);
134
135
136 /*
137  * main - parse arguments and handle options
138  */
139 int
140 main(int argc, char *argv[])
141 {
142         int c;
143         size_t i;
144         int action = 0, didsomefiles = 0, errflg = 0;
145         int flags = 0, e = 0;
146         char *home, *usermagic;
147         struct magic_set *magic = NULL;
148         char magicpath[2 * MAXPATHLEN + 2];
149         int longindex;
150         const char *magicfile;          /* where the magic is   */
151
152         /* makes islower etc work for other langs */
153         (void)setlocale(LC_CTYPE, "");
154
155 #ifdef __EMX__
156         /* sh-like wildcard expansion! Shouldn't hurt at least ... */
157         _wildcard(&argc, &argv);
158 #endif
159
160         if ((progname = strrchr(argv[0], '/')) != NULL)
161                 progname++;
162         else
163                 progname = argv[0];
164
165         magicfile = default_magicfile;
166         if ((usermagic = getenv("MAGIC")) != NULL)
167                 magicfile = usermagic;
168         else
169                 if ((home = getenv("HOME")) != NULL) {
170                         (void)snprintf(magicpath, sizeof(magicpath), "%s%s",
171                              home, hmagic);
172                         if (access(magicpath, R_OK) == 0) {
173                                 (void)snprintf(magicpath, sizeof(magicpath),
174                                     "%s%s:%s", home, hmagic, magicfile);
175                                 magicfile = magicpath;
176                         }
177                 }
178
179 #ifdef S_IFLNK
180         flags |= getenv("POSIXLY_CORRECT") ? MAGIC_SYMLINK : 0;
181 #endif
182         while ((c = getopt_long(argc, argv, OPTSTRING, long_options,
183             &longindex)) != -1)
184                 switch (c) {
185                 case 0 :
186                         switch (longindex) {
187                         case 0:
188                                 help();
189                                 break;
190                         case 10:
191                                 flags |= MAGIC_APPLE;
192                                 break;
193                         case 11:
194                                 flags |= MAGIC_MIME_TYPE;
195                                 break;
196                         case 12:
197                                 flags |= MAGIC_MIME_ENCODING;
198                                 break;
199                         }
200                         break;
201                 case '0':
202                         nulsep = 1;
203                         break;
204                 case 'b':
205                         bflag++;
206                         break;
207                 case 'c':
208                         action = FILE_CHECK;
209                         break;
210                 case 'C':
211                         action = FILE_COMPILE;
212                         break;
213                 case 'd':
214                         flags |= MAGIC_DEBUG|MAGIC_CHECK;
215                         break;
216                 case 'e':
217                         for (i = 0; i < sizeof(nv) / sizeof(nv[0]); i++)
218                                 if (strcmp(nv[i].name, optarg) == 0)
219                                         break;
220
221                         if (i == sizeof(nv) / sizeof(nv[0]))
222                                 errflg++;
223                         else
224                                 flags |= nv[i].value;
225                         break;
226
227                 case 'f':
228                         if(action)
229                                 usage();
230                         if (magic == NULL)
231                                 if ((magic = load(magicfile, flags)) == NULL)
232                                         return 1;
233                         e |= unwrap(magic, optarg);
234                         ++didsomefiles;
235                         break;
236                 case 'F':
237                         separator = optarg;
238                         break;
239                 case 'i':
240                         flags |= MAGIC_MIME;
241                         break;
242                 case 'k':
243                         flags |= MAGIC_CONTINUE;
244                         break;
245                 case 'm':
246                         magicfile = optarg;
247                         break;
248                 case 'n':
249                         ++nobuffer;
250                         break;
251                 case 'N':
252                         ++nopad;
253                         break;
254 #if defined(HAVE_UTIME) || defined(HAVE_UTIMES)
255                 case 'p':
256                         flags |= MAGIC_PRESERVE_ATIME;
257                         break;
258 #endif
259                 case 'r':
260                         flags |= MAGIC_RAW;
261                         break;
262                 case 's':
263                         flags |= MAGIC_DEVICES;
264                         break;
265                 case 'v':
266                         (void)fprintf(stderr, "%s-%d.%.2d\n", progname,
267                                        FILE_VERSION_MAJOR, patchlevel);
268                         (void)fprintf(stderr, "magic file from %s\n",
269                                        magicfile);
270                         return 1;
271                 case 'z':
272                         flags |= MAGIC_COMPRESS;
273                         break;
274 #ifdef S_IFLNK
275                 case 'L':
276                         flags |= MAGIC_SYMLINK;
277                         break;
278                 case 'h':
279                         flags &= ~MAGIC_SYMLINK;
280                         break;
281 #endif
282                 case '?':
283                 default:
284                         errflg++;
285                         break;
286                 }
287
288         if (errflg) {
289                 usage();
290         }
291         if (e)
292                 return e;
293
294         switch(action) {
295         case FILE_CHECK:
296         case FILE_COMPILE:
297                 /*
298                  * Don't try to check/compile ~/.magic unless we explicitly
299                  * ask for it.
300                  */
301                 if (magicfile == magicpath)
302                         magicfile = default_magicfile;
303                 magic = magic_open(flags|MAGIC_CHECK);
304                 if (magic == NULL) {
305                         (void)fprintf(stderr, "%s: %s\n", progname,
306                             strerror(errno));
307                         return 1;
308                 }
309                 c = action == FILE_CHECK ? magic_check(magic, magicfile) :
310                     magic_compile(magic, magicfile);
311                 if (c == -1) {
312                         (void)fprintf(stderr, "%s: %s\n", progname,
313                             magic_error(magic));
314                         return 1;
315                 }
316                 return 0;
317         default:
318                 if (magic == NULL)
319                         if ((magic = load(magicfile, flags)) == NULL)
320                                 return 1;
321                 break;
322         }
323
324         if (optind == argc) {
325                 if (!didsomefiles)
326                         usage();
327         }
328         else {
329                 size_t j, wid, nw;
330                 for (wid = 0, j = (size_t)optind; j < (size_t)argc; j++) {
331                         nw = file_mbswidth(argv[j]);
332                         if (nw > wid)
333                                 wid = nw;
334                 }
335                 /*
336                  * If bflag is only set twice, set it depending on
337                  * number of files [this is undocumented, and subject to change]
338                  */
339                 if (bflag == 2) {
340                         bflag = optind >= argc - 1;
341                 }
342                 for (; optind < argc; optind++)
343                         e |= process(magic, argv[optind], wid);
344         }
345
346         if (magic)
347                 magic_close(magic);
348         return e;
349 }
350
351
352 private struct magic_set *
353 /*ARGSUSED*/
354 load(const char *magicfile, int flags)
355 {
356         struct magic_set *magic = magic_open(flags);
357         if (magic == NULL) {
358                 (void)fprintf(stderr, "%s: %s\n", progname, strerror(errno));
359                 return NULL;
360         }
361         if (magic_load(magic, magicfile) == -1) {
362                 (void)fprintf(stderr, "%s: %s\n",
363                     progname, magic_error(magic));
364                 magic_close(magic);
365                 return NULL;
366         }
367         return magic;
368 }
369
370 /*
371  * unwrap -- read a file of filenames, do each one.
372  */
373 private int
374 unwrap(struct magic_set *ms, const char *fn)
375 {
376         char buf[MAXPATHLEN];
377         FILE *f;
378         int wid = 0, cwid;
379         int e = 0;
380
381         if (strcmp("-", fn) == 0) {
382                 f = stdin;
383                 wid = 1;
384         } else {
385                 if ((f = fopen(fn, "r")) == NULL) {
386                         (void)fprintf(stderr, "%s: Cannot open `%s' (%s).\n",
387                             progname, fn, strerror(errno));
388                         return 1;
389                 }
390
391                 while (fgets(buf, sizeof(buf), f) != NULL) {
392                         buf[strcspn(buf, "\n")] = '\0';
393                         cwid = file_mbswidth(buf);
394                         if (cwid > wid)
395                                 wid = cwid;
396                 }
397
398                 rewind(f);
399         }
400
401         while (fgets(buf, sizeof(buf), f) != NULL) {
402                 buf[strcspn(buf, "\n")] = '\0';
403                 e |= process(ms, buf, wid);
404                 if(nobuffer)
405                         (void)fflush(stdout);
406         }
407
408         (void)fclose(f);
409         return e;
410 }
411
412 /*
413  * Called for each input file on the command line (or in a list of files)
414  */
415 private int
416 process(struct magic_set *ms, const char *inname, int wid)
417 {
418         const char *type;
419         int std_in = strcmp(inname, "-") == 0;
420
421         if (wid > 0 && !bflag) {
422                 (void)printf("%s", std_in ? "/dev/stdin" : inname);
423                 if (nulsep)
424                         (void)putc('\0', stdout);
425                 else
426                         (void)printf("%s", separator);
427                 (void)printf("%*s ",
428                     (int) (nopad ? 0 : (wid - file_mbswidth(inname))), "");
429         }
430
431         type = magic_file(ms, std_in ? NULL : inname);
432         if (type == NULL) {
433                 (void)printf("ERROR: %s\n", magic_error(ms));
434                 return 1;
435         } else {
436                 (void)printf("%s\n", type);
437                 return 0;
438         }
439 }
440
441 size_t
442 file_mbswidth(const char *s)
443 {
444 #if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH)
445         size_t bytesconsumed, old_n, n, width = 0;
446         mbstate_t state;
447         wchar_t nextchar;
448         (void)memset(&state, 0, sizeof(mbstate_t));
449         old_n = n = strlen(s);
450
451         while (n > 0) {
452                 bytesconsumed = mbrtowc(&nextchar, s, n, &state);
453                 if (bytesconsumed == (size_t)(-1) ||
454                     bytesconsumed == (size_t)(-2)) {
455                         /* Something went wrong, return something reasonable */
456                         return old_n;
457                 }
458                 if (s[0] == '\n') {
459                         /*
460                          * do what strlen() would do, so that caller
461                          * is always right
462                          */
463                         width++;
464                 } else
465                         width += wcwidth(nextchar);
466
467                 s += bytesconsumed, n -= bytesconsumed;
468         }
469         return width;
470 #else
471         return strlen(s);
472 #endif
473 }
474
475 private void
476 usage(void)
477 {
478         (void)fprintf(stderr, USAGE, progname, progname);
479         (void)fputs("Try `file --help' for more information.\n", stderr);
480         exit(1);
481 }
482
483 private void
484 help(void)
485 {
486         (void)fputs(
487 "Usage: file [OPTION...] [FILE...]\n"
488 "Determine type of FILEs.\n"
489 "\n", stderr);
490 #define OPT(shortname, longname, opt, doc)      \
491         fprintf(stderr, "  -%c, --" longname doc, shortname);
492 #define OPT_LONGONLY(longname, opt, doc)        \
493         fprintf(stderr, "      --" longname doc);
494 #include "file_opts.h"
495 #undef OPT
496 #undef OPT_LONGONLY
497         exit(0);
498 }