xref: /openbsd-src/usr.bin/file/file.c (revision 43003dfe3ad45d1698bed8a37f2b0f5b14f20d4f)
1 /*	$OpenBSD: file.c,v 1.20 2009/08/27 16:26:43 deraadt Exp $ */
2 /*
3  * Copyright (c) Ian F. Darwin 1986-1995.
4  * Software written by Ian F. Darwin and others;
5  * maintained 1995-present by Christos Zoulas and others.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice immediately at the beginning of the file, without modification,
12  *    this list of conditions, and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
21  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 /*
30  * file - find type of a file or files - main program.
31  */
32 
33 #include <sys/types.h>
34 #include <sys/param.h>	/* for MAXPATHLEN */
35 #include <sys/stat.h>
36 
37 #include "file.h"
38 #include "magic.h"
39 
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <string.h>
44 #ifdef RESTORE_TIME
45 # if (__COHERENT__ >= 0x420)
46 #  include <sys/utime.h>
47 # else
48 #  ifdef USE_UTIMES
49 #   include <sys/time.h>
50 #  else
51 #   include <utime.h>
52 #  endif
53 # endif
54 #endif
55 #ifdef HAVE_UNISTD_H
56 #include <unistd.h>	/* for read() */
57 #endif
58 #ifdef HAVE_LOCALE_H
59 #include <locale.h>
60 #endif
61 #ifdef HAVE_WCHAR_H
62 #include <wchar.h>
63 #endif
64 
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 
70 #include <netinet/in.h>		/* for byte swapping */
71 
72 #include "patchlevel.h"
73 
74 #ifndef	lint
75 FILE_RCSID("@(#)$Id: file.c,v 1.20 2009/08/27 16:26:43 deraadt Exp $")
76 #endif	/* lint */
77 
78 
79 #ifdef S_IFLNK
80 #define SYMLINKFLAG "Lh"
81 #else
82 #define SYMLINKFLAG ""
83 #endif
84 
85 # define USAGE  "Usage: %s [-bcik" SYMLINKFLAG "nNrsvz0] [-e test] [-f namefile] [-F separator] [-m magicfiles] file...\n" \
86 		" 	%s [-m magicfiles] -C\n"
87 
88 #ifndef MAXPATHLEN
89 #define	MAXPATHLEN	512
90 #endif
91 
92 private int 		/* Global command-line options 		*/
93 	bflag = 0,	/* brief output format	 		*/
94 	nopad = 0,	/* Don't pad output			*/
95 	nobuffer = 0,   /* Do not buffer stdout 		*/
96 	nulsep = 0;	/* Append '\0' to the separator		*/
97 
98 private const char *magicfile = 0;	/* where the magic is	*/
99 private const char *default_magicfile = MAGIC;
100 private const char *separator = ":";	/* Default field separator	*/
101 
102 extern char *__progname;		/* used throughout 		*/
103 
104 private struct magic_set *magic;
105 
106 private void unwrap(char *);
107 private void usage(void);
108 private void help(void);
109 
110 int main(int, char *[]);
111 private void process(const char *, int);
112 private void load(const char *, int);
113 
114 
115 /*
116  * main - parse arguments and handle options
117  */
118 int
119 main(int argc, char *argv[])
120 {
121 	int c;
122 	size_t i;
123 	int action = 0, didsomefiles = 0, errflg = 0;
124 	int flags = 0;
125 	char *home, *usermagic;
126 	struct stat sb;
127 	static const char hmagic[] = "/.magic";
128 #define OPTSTRING	"bcCde:f:F:hikLm:nNprsvz0"
129 	int longindex;
130 	static const struct option long_options[] =
131 	{
132 #define OPT(shortname, longname, opt, doc)      \
133     {longname, opt, NULL, shortname},
134 #define OPT_LONGONLY(longname, opt, doc)        \
135     {longname, opt, NULL, 0},
136 #include "file_opts.h"
137 #undef OPT
138 #undef OPT_LONGONLY
139     {0, 0, NULL, 0}
140 };
141 
142 	static const struct {
143 		const char *name;
144 		int value;
145 	} nv[] = {
146 		{ "apptype",	MAGIC_NO_CHECK_APPTYPE },
147 		{ "ascii",	MAGIC_NO_CHECK_ASCII },
148 		{ "compress",	MAGIC_NO_CHECK_COMPRESS },
149 		{ "elf",	MAGIC_NO_CHECK_ELF },
150 		{ "soft",	MAGIC_NO_CHECK_SOFT },
151 		{ "tar",	MAGIC_NO_CHECK_TAR },
152 		{ "tokens",	MAGIC_NO_CHECK_TOKENS },
153 	};
154 
155 	/* makes islower etc work for other langs */
156 	(void)setlocale(LC_CTYPE, "");
157 
158 #ifdef __EMX__
159 	/* sh-like wildcard expansion! Shouldn't hurt at least ... */
160 	_wildcard(&argc, &argv);
161 #endif
162 
163 	magicfile = default_magicfile;
164 	if ((usermagic = getenv("MAGIC")) != NULL)
165 		magicfile = usermagic;
166 	else
167 		if ((home = getenv("HOME")) != NULL) {
168 			size_t len = strlen(home) + sizeof(hmagic);
169 			if ((usermagic = malloc(len)) != NULL) {
170 				(void)strlcpy(usermagic, home, len);
171 				(void)strlcat(usermagic, hmagic, len);
172 				if (stat(usermagic, &sb)<0)
173 					free(usermagic);
174 				else
175 					magicfile = usermagic;
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_MIME_TYPE;
192 				break;
193 			case 11:
194 				flags |= MAGIC_MIME_ENCODING;
195 				break;
196 			}
197 			break;
198 		case '0':
199 			nulsep = 1;
200 			break;
201 		case 'b':
202 			bflag++;
203 			break;
204 		case 'c':
205 			action = FILE_CHECK;
206 			break;
207 		case 'C':
208 			action = FILE_COMPILE;
209 			break;
210 		case 'd':
211 			flags |= MAGIC_DEBUG|MAGIC_CHECK;
212 			break;
213 		case 'e':
214 			for (i = 0; i < sizeof(nv) / sizeof(nv[0]); i++)
215 				if (strcmp(nv[i].name, optarg) == 0)
216 					break;
217 
218 			if (i == sizeof(nv) / sizeof(nv[0]))
219 				errflg++;
220 			else
221 				flags |= nv[i].value;
222 			break;
223 
224 		case 'f':
225 			if(action)
226 				usage();
227 			load(magicfile, flags);
228 			unwrap(optarg);
229 			++didsomefiles;
230 			break;
231 		case 'F':
232 			separator = optarg;
233 			break;
234 		case 'i':
235 			flags |= MAGIC_MIME;
236 			break;
237 		case 'k':
238 			flags |= MAGIC_CONTINUE;
239 			break;
240 		case 'm':
241 			magicfile = optarg;
242 			break;
243 		case 'n':
244 			++nobuffer;
245 			break;
246 		case 'N':
247 			++nopad;
248 			break;
249 #if defined(HAVE_UTIME) || defined(HAVE_UTIMES)
250 		case 'p':
251 			flags |= MAGIC_PRESERVE_ATIME;
252 			break;
253 #endif
254 		case 'r':
255 			flags |= MAGIC_RAW;
256 			break;
257 		case 's':
258 			flags |= MAGIC_DEVICES;
259 			break;
260 		case 'v':
261 			(void)fprintf(stderr, "%s-%d.%.2d\n", __progname,
262 				       FILE_VERSION_MAJOR, patchlevel);
263 			(void)fprintf(stderr, "magic file from %s\n",
264 				       magicfile);
265 			return 1;
266 		case 'z':
267 			flags |= MAGIC_COMPRESS;
268 			break;
269 #ifdef S_IFLNK
270 		case 'L':
271 			flags |= MAGIC_SYMLINK;
272 			break;
273 		case 'h':
274 			flags &= ~MAGIC_SYMLINK;
275 			break;
276 #endif
277 		case '?':
278 		default:
279 			errflg++;
280 			break;
281 		}
282 
283 	if (errflg) {
284 		usage();
285 	}
286 
287 	switch(action) {
288 	case FILE_CHECK:
289 	case FILE_COMPILE:
290 		magic = magic_open(flags|MAGIC_CHECK);
291 		if (magic == NULL) {
292 			(void)fprintf(stderr, "%s: %s\n", __progname,
293 			    strerror(errno));
294 			return 1;
295 		}
296 		c = action == FILE_CHECK ? magic_check(magic, magicfile) :
297 		    magic_compile(magic, magicfile);
298 		if (c == -1) {
299 			(void)fprintf(stderr, "%s: %s\n", __progname,
300 			    magic_error(magic));
301 			return -1;
302 		}
303 		return 0;
304 	default:
305 		load(magicfile, flags);
306 		break;
307 	}
308 
309 	if (optind == argc) {
310 		if (!didsomefiles) {
311 			usage();
312 		}
313 	}
314 	else {
315 		size_t j, wid, nw;
316 		for (wid = 0, j = (size_t)optind; j < (size_t)argc; j++) {
317 			nw = file_mbswidth(argv[j]);
318 			if (nw > wid)
319 				wid = nw;
320 		}
321 		/*
322 		 * If bflag is only set twice, set it depending on
323 		 * number of files [this is undocumented, and subject to change]
324 		 */
325 		if (bflag == 2) {
326 			bflag = optind >= argc - 1;
327 		}
328 		for (; optind < argc; optind++)
329 			process(argv[optind], wid);
330 	}
331 
332 	c = magic->haderr ? 1 : 0;
333 	magic_close(magic);
334 	return c;
335 }
336 
337 
338 private void
339 /*ARGSUSED*/
340 load(const char *m, int flags)
341 {
342 	if (magic || m == NULL)
343 		return;
344 	magic = magic_open(flags);
345 	if (magic == NULL) {
346 		(void)fprintf(stderr, "%s: %s\n", __progname, strerror(errno));
347 		exit(1);
348 	}
349 	if (magic_load(magic, magicfile) == -1) {
350 		(void)fprintf(stderr, "%s: %s\n",
351 		    __progname, magic_error(magic));
352 		exit(1);
353 	}
354 }
355 
356 /*
357  * unwrap -- read a file of filenames, do each one.
358  */
359 private void
360 unwrap(char *fn)
361 {
362 	char buf[MAXPATHLEN];
363 	FILE *f;
364 	int wid = 0, cwid;
365 
366 	if (strcmp("-", fn) == 0) {
367 		f = stdin;
368 		wid = 1;
369 	} else {
370 		if ((f = fopen(fn, "r")) == NULL) {
371 			(void)fprintf(stderr, "%s: Cannot open `%s' (%s).\n",
372 			    __progname, fn, strerror(errno));
373 			exit(1);
374 		}
375 
376 		while (fgets(buf, sizeof(buf), f) != NULL) {
377 			buf[strcspn(buf, "\n")] = '\0';
378 			cwid = file_mbswidth(buf);
379 			if (cwid > wid)
380 				wid = cwid;
381 		}
382 
383 		rewind(f);
384 	}
385 
386 	while (fgets(buf, sizeof(buf), f) != NULL) {
387 		buf[strcspn(buf, "\n")] = '\0';
388 		process(buf, wid);
389 		if(nobuffer)
390 			(void)fflush(stdout);
391 	}
392 
393 	(void)fclose(f);
394 }
395 
396 /*
397  * Called for each input file on the command line (or in a list of files)
398  */
399 private void
400 process(const char *inname, int wid)
401 {
402 	const char *type;
403 	int std_in = strcmp(inname, "-") == 0;
404 
405 	if (wid > 0 && !bflag) {
406 		(void)printf("%s", std_in ? "/dev/stdin" : inname);
407 		if (nulsep)
408 			(void)putc('\0', stdout);
409 		else
410 			(void)printf("%s", separator);
411 		(void)printf("%*s ",
412 		    (int) (nopad ? 0 : (wid - file_mbswidth(inname))), "");
413 	}
414 
415 	type = magic_file(magic, std_in ? NULL : inname);
416 	if (type == NULL)
417 		(void)printf("ERROR: %s\n", magic_error(magic));
418 	else
419 		(void)printf("%s\n", type);
420 }
421 
422 size_t
423 file_mbswidth(const char *s)
424 {
425 #if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH)
426 	size_t bytesconsumed, old_n, n, width = 0;
427 	mbstate_t state;
428 	wchar_t nextchar;
429 	(void)memset(&state, 0, sizeof(mbstate_t));
430 	old_n = n = strlen(s);
431 
432 	while (n > 0) {
433 		bytesconsumed = mbrtowc(&nextchar, s, n, &state);
434 		if (bytesconsumed == (size_t)(-1) ||
435 		    bytesconsumed == (size_t)(-2)) {
436 			/* Something went wrong, return something reasonable */
437 			return old_n;
438 		}
439 		if (s[0] == '\n') {
440 			/*
441 			 * do what strlen() would do, so that caller
442 			 * is always right
443 			 */
444 			width++;
445 		} else
446 			width += wcwidth(nextchar);
447 
448 		s += bytesconsumed, n -= bytesconsumed;
449 	}
450 	return width;
451 #else
452 	return strlen(s);
453 #endif
454 }
455 
456 private void
457 usage(void)
458 {
459 	(void)fprintf(stderr, USAGE, __progname, __progname);
460 	(void)fputs("Try `file --help' for more information.\n", stderr);
461 	exit(1);
462 }
463 
464 private void
465 help(void)
466 {
467 	(void)fputs(
468 "Usage: file [OPTION...] [FILE...]\n"
469 "Determine type of FILEs.\n"
470 "\n", stderr);
471 #define OPT(shortname, longname, opt, doc)      \
472         fprintf(stderr, "  -%c, --" longname doc, shortname);
473 #define OPT_LONGONLY(longname, opt, doc)        \
474         fprintf(stderr, "      --" longname doc);
475 #include "file_opts.h"
476 #undef OPT
477 #undef OPT_LONGONLY
478 	exit(0);
479 }
480