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