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