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