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