xref: /dragonfly/contrib/file/src/file.c (revision 235099c3)
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 }
499