168349Sobrien/*
2133359Sobrien * Copyright (c) Ian F. Darwin 1986-1995.
3133359Sobrien * Software written by Ian F. Darwin and others;
4133359Sobrien * maintained 1995-present by Christos Zoulas and others.
5191736Sobrien *
6133359Sobrien * Redistribution and use in source and binary forms, with or without
7133359Sobrien * modification, are permitted provided that the following conditions
8133359Sobrien * are met:
9133359Sobrien * 1. Redistributions of source code must retain the above copyright
10133359Sobrien *    notice immediately at the beginning of the file, without modification,
11133359Sobrien *    this list of conditions, and the following disclaimer.
12133359Sobrien * 2. Redistributions in binary form must reproduce the above copyright
13133359Sobrien *    notice, this list of conditions and the following disclaimer in the
14133359Sobrien *    documentation and/or other materials provided with the distribution.
15191736Sobrien *
16133359Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17133359Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18133359Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19133359Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
20133359Sobrien * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21133359Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22133359Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23133359Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24133359Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25133359Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26133359Sobrien * SUCH DAMAGE.
27133359Sobrien */
28133359Sobrien/*
2968349Sobrien * file - find type of a file or files - main program.
3068349Sobrien */
31103373Sobrien
32103373Sobrien#include "file.h"
33191736Sobrien
34191736Sobrien#ifndef	lint
35284193SdelphijFILE_RCSID("@(#)$File: file.c,v 1.160 2014/12/16 23:18:40 christos Exp $")
36191736Sobrien#endif	/* lint */
37191736Sobrien
38133359Sobrien#include "magic.h"
39133359Sobrien
4068349Sobrien#include <stdlib.h>
4169216Sobrien#include <unistd.h>
4268349Sobrien#include <string.h>
4368349Sobrien#ifdef RESTORE_TIME
4468349Sobrien# if (__COHERENT__ >= 0x420)
4568349Sobrien#  include <sys/utime.h>
4668349Sobrien# else
4768349Sobrien#  ifdef USE_UTIMES
4868349Sobrien#   include <sys/time.h>
4968349Sobrien#  else
5068349Sobrien#   include <utime.h>
5168349Sobrien#  endif
5268349Sobrien# endif
5368349Sobrien#endif
5468349Sobrien#ifdef HAVE_UNISTD_H
5568349Sobrien#include <unistd.h>	/* for read() */
5668349Sobrien#endif
57133359Sobrien#ifdef HAVE_WCHAR_H
58133359Sobrien#include <wchar.h>
59133359Sobrien#endif
6068349Sobrien
61192348Sdelphij#if defined(HAVE_GETOPT_H) && defined(HAVE_STRUCT_OPTION)
62186690Sobrien#include <getopt.h>
63226048Sobrien#ifndef HAVE_GETOPT_LONG
64226048Sobrienint getopt_long(int argc, char * const *argv, const char *optstring, const struct option *longopts, int *longindex);
65226048Sobrien#endif
66133359Sobrien#else
67186690Sobrien#include "mygetopt.h"
68103373Sobrien#endif
69103373Sobrien
7068349Sobrien#ifdef S_IFLNK
71267843Sdelphij#define FILE_FLAGS "-bcEhikLlNnprsvz0"
7268349Sobrien#else
73267843Sdelphij#define FILE_FLAGS "-bcEiklNnprsvz0"
7468349Sobrien#endif
7568349Sobrien
76226048Sobrien# define USAGE  \
77226048Sobrien    "Usage: %s [" FILE_FLAGS \
78226048Sobrien	"] [--apple] [--mime-encoding] [--mime-type]\n" \
79226048Sobrien    "            [-e testname] [-F separator] [-f namefile] [-m magicfiles] " \
80226048Sobrien    "file ...\n" \
81226048Sobrien    "       %s -C [-m magicfiles]\n" \
82226048Sobrien    "       %s [--help]\n"
83103373Sobrien
84133359Sobrienprivate int 		/* Global command-line options 		*/
8568349Sobrien	bflag = 0,	/* brief output format	 		*/
86110949Sobrien	nopad = 0,	/* Don't pad output			*/
87169942Sobrien	nobuffer = 0,   /* Do not buffer stdout 		*/
88169942Sobrien	nulsep = 0;	/* Append '\0' to the separator		*/
8968349Sobrien
90159764Sobrienprivate const char *separator = ":";	/* Default field separator	*/
91191736Sobrienprivate const struct option long_options[] = {
92191736Sobrien#define OPT(shortname, longname, opt, doc)      \
93191736Sobrien    {longname, opt, NULL, shortname},
94191736Sobrien#define OPT_LONGONLY(longname, opt, doc)        \
95191736Sobrien    {longname, opt, NULL, 0},
96191736Sobrien#include "file_opts.h"
97191736Sobrien#undef OPT
98191736Sobrien#undef OPT_LONGONLY
99191736Sobrien    {0, 0, NULL, 0}
100191736Sobrien};
101284193Sdelphij#define OPTSTRING	"bcCde:Ef:F:hiklLm:nNpP:rsvz0"
10268349Sobrien
103191736Sobrienprivate const struct {
104191736Sobrien	const char *name;
105191736Sobrien	int value;
106191736Sobrien} nv[] = {
107191736Sobrien	{ "apptype",	MAGIC_NO_CHECK_APPTYPE },
108191736Sobrien	{ "ascii",	MAGIC_NO_CHECK_ASCII },
109191736Sobrien	{ "cdf",	MAGIC_NO_CHECK_CDF },
110191736Sobrien	{ "compress",	MAGIC_NO_CHECK_COMPRESS },
111191736Sobrien	{ "elf",	MAGIC_NO_CHECK_ELF },
112191736Sobrien	{ "encoding",	MAGIC_NO_CHECK_ENCODING },
113191736Sobrien	{ "soft",	MAGIC_NO_CHECK_SOFT },
114191736Sobrien	{ "tar",	MAGIC_NO_CHECK_TAR },
115226048Sobrien	{ "text",	MAGIC_NO_CHECK_TEXT },	/* synonym for ascii */
116234250Sobrien	{ "tokens",	MAGIC_NO_CHECK_TOKENS }, /* OBSOLETE: ignored for backwards compatibility */
117191736Sobrien};
118191736Sobrien
119284193Sdelphijprivate struct {
120284193Sdelphij	const char *name;
121284193Sdelphij	int tag;
122284193Sdelphij	size_t value;
123284193Sdelphij} pm[] = {
124284193Sdelphij	{ "indir",	MAGIC_PARAM_INDIR_MAX, 0 },
125284193Sdelphij	{ "name",	MAGIC_PARAM_NAME_MAX, 0 },
126284193Sdelphij	{ "elf_phnum",	MAGIC_PARAM_ELF_PHNUM_MAX, 0 },
127284193Sdelphij	{ "elf_shnum",	MAGIC_PARAM_ELF_SHNUM_MAX, 0 },
128284193Sdelphij	{ "elf_notes",	MAGIC_PARAM_ELF_NOTES_MAX, 0 },
129284193Sdelphij};
130284193Sdelphij
131133359Sobrienprivate char *progname;		/* used throughout 		*/
13268349Sobrien
133133359Sobrienprivate void usage(void);
134267843Sdelphijprivate void docprint(const char *);
135133359Sobrienprivate void help(void);
13668349Sobrien
137191736Sobrienprivate int unwrap(struct magic_set *, const char *);
138191736Sobrienprivate int process(struct magic_set *ms, const char *, int);
139191736Sobrienprivate struct magic_set *load(const char *, int);
140284193Sdelphijprivate void setparam(const char *);
141284193Sdelphijprivate void applyparam(magic_t);
142133359Sobrien
143191736Sobrien
14468349Sobrien/*
14568349Sobrien * main - parse arguments and handle options
14668349Sobrien */
14768349Sobrienint
148133359Sobrienmain(int argc, char *argv[])
14968349Sobrien{
150175296Sobrien	int c;
151175296Sobrien	size_t i;
152133359Sobrien	int action = 0, didsomefiles = 0, errflg = 0;
153191736Sobrien	int flags = 0, e = 0;
154191736Sobrien	struct magic_set *magic = NULL;
155103373Sobrien	int longindex;
156226048Sobrien	const char *magicfile = NULL;		/* where the magic is	*/
15768349Sobrien
158159764Sobrien	/* makes islower etc work for other langs */
159284193Sdelphij#ifdef HAVE_SETLOCALE
160159764Sobrien	(void)setlocale(LC_CTYPE, "");
161284193Sdelphij#endif
16268349Sobrien
163103373Sobrien#ifdef __EMX__
164103373Sobrien	/* sh-like wildcard expansion! Shouldn't hurt at least ... */
165103373Sobrien	_wildcard(&argc, &argv);
166103373Sobrien#endif
167103373Sobrien
16868349Sobrien	if ((progname = strrchr(argv[0], '/')) != NULL)
16968349Sobrien		progname++;
17068349Sobrien	else
17168349Sobrien		progname = argv[0];
17268349Sobrien
173159764Sobrien#ifdef S_IFLNK
174159764Sobrien	flags |= getenv("POSIXLY_CORRECT") ? MAGIC_SYMLINK : 0;
175159764Sobrien#endif
176103373Sobrien	while ((c = getopt_long(argc, argv, OPTSTRING, long_options,
177103373Sobrien	    &longindex)) != -1)
17868349Sobrien		switch (c) {
179103373Sobrien		case 0 :
180175296Sobrien			switch (longindex) {
181175296Sobrien			case 0:
182103373Sobrien				help();
183175296Sobrien				break;
184175296Sobrien			case 10:
185191736Sobrien				flags |= MAGIC_APPLE;
186191736Sobrien				break;
187191736Sobrien			case 11:
188175296Sobrien				flags |= MAGIC_MIME_TYPE;
189175296Sobrien				break;
190191736Sobrien			case 12:
191175296Sobrien				flags |= MAGIC_MIME_ENCODING;
192175296Sobrien				break;
193175296Sobrien			}
194103373Sobrien			break;
195169942Sobrien		case '0':
196169942Sobrien			nulsep = 1;
197169942Sobrien			break;
19868349Sobrien		case 'b':
199175296Sobrien			bflag++;
20068349Sobrien			break;
20168349Sobrien		case 'c':
202133359Sobrien			action = FILE_CHECK;
20368349Sobrien			break;
20474784Sobrien		case 'C':
205133359Sobrien			action = FILE_COMPILE;
20674784Sobrien			break;
20768349Sobrien		case 'd':
208133359Sobrien			flags |= MAGIC_DEBUG|MAGIC_CHECK;
20968349Sobrien			break;
210267843Sdelphij		case 'E':
211267843Sdelphij			flags |= MAGIC_ERROR;
212267843Sdelphij			break;
213169962Sobrien		case 'e':
214169962Sobrien			for (i = 0; i < sizeof(nv) / sizeof(nv[0]); i++)
215169962Sobrien				if (strcmp(nv[i].name, optarg) == 0)
216169962Sobrien					break;
217169962Sobrien
218169962Sobrien			if (i == sizeof(nv) / sizeof(nv[0]))
219169962Sobrien				errflg++;
220169962Sobrien			else
221169962Sobrien				flags |= nv[i].value;
222169962Sobrien			break;
223191736Sobrien
22468349Sobrien		case 'f':
225133359Sobrien			if(action)
226133359Sobrien				usage();
227191736Sobrien			if (magic == NULL)
228191736Sobrien				if ((magic = load(magicfile, flags)) == NULL)
229191736Sobrien					return 1;
230191736Sobrien			e |= unwrap(magic, optarg);
23168349Sobrien			++didsomefiles;
23268349Sobrien			break;
233110949Sobrien		case 'F':
234133359Sobrien			separator = optarg;
235110949Sobrien			break;
23668349Sobrien		case 'i':
237133359Sobrien			flags |= MAGIC_MIME;
23868349Sobrien			break;
23968349Sobrien		case 'k':
240133359Sobrien			flags |= MAGIC_CONTINUE;
24168349Sobrien			break;
242226048Sobrien		case 'l':
243226048Sobrien			action = FILE_LIST;
244226048Sobrien			break;
24568349Sobrien		case 'm':
24668349Sobrien			magicfile = optarg;
24768349Sobrien			break;
24868349Sobrien		case 'n':
24968349Sobrien			++nobuffer;
25068349Sobrien			break;
251110949Sobrien		case 'N':
252110949Sobrien			++nopad;
253110949Sobrien			break;
254133359Sobrien#if defined(HAVE_UTIME) || defined(HAVE_UTIMES)
255133359Sobrien		case 'p':
256133359Sobrien			flags |= MAGIC_PRESERVE_ATIME;
257133359Sobrien			break;
258133359Sobrien#endif
259284193Sdelphij		case 'P':
260284193Sdelphij			setparam(optarg);
261284193Sdelphij			break;
262133359Sobrien		case 'r':
263133359Sobrien			flags |= MAGIC_RAW;
264133359Sobrien			break;
265284193Sdelphij			break;
26668349Sobrien		case 's':
267133359Sobrien			flags |= MAGIC_DEVICES;
26868349Sobrien			break;
26968349Sobrien		case 'v':
270226048Sobrien			if (magicfile == NULL)
271226048Sobrien				magicfile = magic_getpath(magicfile, action);
272226048Sobrien			(void)fprintf(stdout, "%s-%s\n", progname, VERSION);
273226048Sobrien			(void)fprintf(stdout, "magic file from %s\n",
27468349Sobrien				       magicfile);
275267843Sdelphij			return 0;
27668349Sobrien		case 'z':
277133359Sobrien			flags |= MAGIC_COMPRESS;
27868349Sobrien			break;
27968349Sobrien#ifdef S_IFLNK
28068349Sobrien		case 'L':
281133359Sobrien			flags |= MAGIC_SYMLINK;
28268349Sobrien			break;
283159764Sobrien		case 'h':
284159764Sobrien			flags &= ~MAGIC_SYMLINK;
285159764Sobrien			break;
28668349Sobrien#endif
28768349Sobrien		case '?':
28868349Sobrien		default:
28968349Sobrien			errflg++;
29068349Sobrien			break;
29168349Sobrien		}
29268349Sobrien
29368349Sobrien	if (errflg) {
29474784Sobrien		usage();
29568349Sobrien	}
296191736Sobrien	if (e)
297191736Sobrien		return e;
29868349Sobrien
299267843Sdelphij	if (MAGIC_VERSION != magic_version())
300267843Sdelphij		(void)fprintf(stderr, "%s: compiled magic version [%d] "
301267843Sdelphij		    "does not match with shared library magic version [%d]\n",
302267843Sdelphij		    progname, MAGIC_VERSION, magic_version());
303267843Sdelphij
304133359Sobrien	switch(action) {
305133359Sobrien	case FILE_CHECK:
306133359Sobrien	case FILE_COMPILE:
307226048Sobrien	case FILE_LIST:
308191736Sobrien		/*
309191736Sobrien		 * Don't try to check/compile ~/.magic unless we explicitly
310191736Sobrien		 * ask for it.
311191736Sobrien		 */
312133359Sobrien		magic = magic_open(flags|MAGIC_CHECK);
313133359Sobrien		if (magic == NULL) {
314133359Sobrien			(void)fprintf(stderr, "%s: %s\n", progname,
315133359Sobrien			    strerror(errno));
316133359Sobrien			return 1;
317133359Sobrien		}
318284193Sdelphij
319284193Sdelphij
320226048Sobrien		switch(action) {
321226048Sobrien		case FILE_CHECK:
322226048Sobrien			c = magic_check(magic, magicfile);
323226048Sobrien			break;
324226048Sobrien		case FILE_COMPILE:
325226048Sobrien			c = magic_compile(magic, magicfile);
326226048Sobrien			break;
327226048Sobrien		case FILE_LIST:
328226048Sobrien			c = magic_list(magic, magicfile);
329226048Sobrien			break;
330226048Sobrien		default:
331226048Sobrien			abort();
332226048Sobrien		}
333133359Sobrien		if (c == -1) {
334133359Sobrien			(void)fprintf(stderr, "%s: %s\n", progname,
335133359Sobrien			    magic_error(magic));
336191736Sobrien			return 1;
337133359Sobrien		}
338133359Sobrien		return 0;
339133359Sobrien	default:
340191736Sobrien		if (magic == NULL)
341191736Sobrien			if ((magic = load(magicfile, flags)) == NULL)
342191736Sobrien				return 1;
343284193Sdelphij		applyparam(magic);
34468349Sobrien	}
34568349Sobrien
34668349Sobrien	if (optind == argc) {
347191736Sobrien		if (!didsomefiles)
34874784Sobrien			usage();
34968349Sobrien	}
35068349Sobrien	else {
351175296Sobrien		size_t j, wid, nw;
352175296Sobrien		for (wid = 0, j = (size_t)optind; j < (size_t)argc; j++) {
353175296Sobrien			nw = file_mbswidth(argv[j]);
35468349Sobrien			if (nw > wid)
35568349Sobrien				wid = nw;
35668349Sobrien		}
357175296Sobrien		/*
358175296Sobrien		 * If bflag is only set twice, set it depending on
359175296Sobrien		 * number of files [this is undocumented, and subject to change]
360175296Sobrien		 */
361175296Sobrien		if (bflag == 2) {
362175296Sobrien			bflag = optind >= argc - 1;
363175296Sobrien		}
36468349Sobrien		for (; optind < argc; optind++)
365191736Sobrien			e |= process(magic, argv[optind], wid);
36668349Sobrien	}
36768349Sobrien
368191736Sobrien	if (magic)
369191736Sobrien		magic_close(magic);
370191736Sobrien	return e;
37168349Sobrien}
37268349Sobrien
373284193Sdelphijprivate void
374284193Sdelphijapplyparam(magic_t magic)
375284193Sdelphij{
376284193Sdelphij	size_t i;
37768349Sobrien
378284193Sdelphij	for (i = 0; i < __arraycount(pm); i++) {
379284193Sdelphij		if (pm[i].value == 0)
380284193Sdelphij			continue;
381284193Sdelphij		if (magic_setparam(magic, pm[i].tag, &pm[i].value) == -1) {
382284193Sdelphij			(void)fprintf(stderr, "%s: Can't set %s %s\n", progname,
383284193Sdelphij				pm[i].name, strerror(errno));
384284193Sdelphij			exit(1);
385284193Sdelphij		}
386284193Sdelphij	}
387284193Sdelphij}
388284193Sdelphij
389284193Sdelphijprivate void
390284193Sdelphijsetparam(const char *p)
391284193Sdelphij{
392284193Sdelphij	size_t i;
393284193Sdelphij	char *s;
394284193Sdelphij
395284193Sdelphij	if ((s = strchr(p, '=')) == NULL)
396284193Sdelphij		goto badparm;
397284193Sdelphij
398284193Sdelphij	for (i = 0; i < __arraycount(pm); i++) {
399284193Sdelphij		if (strncmp(p, pm[i].name, s - p) != 0)
400284193Sdelphij			continue;
401284193Sdelphij		pm[i].value = atoi(s + 1);
402284193Sdelphij		return;
403284193Sdelphij	}
404284193Sdelphijbadparm:
405284193Sdelphij	(void)fprintf(stderr, "%s: Unknown param %s\n", progname, p);
406284193Sdelphij	exit(1);
407284193Sdelphij}
408284193Sdelphij
409191736Sobrienprivate struct magic_set *
410159764Sobrien/*ARGSUSED*/
411191736Sobrienload(const char *magicfile, int flags)
412133359Sobrien{
413191736Sobrien	struct magic_set *magic = magic_open(flags);
414133359Sobrien	if (magic == NULL) {
415133359Sobrien		(void)fprintf(stderr, "%s: %s\n", progname, strerror(errno));
416191736Sobrien		return NULL;
417133359Sobrien	}
418133359Sobrien	if (magic_load(magic, magicfile) == -1) {
419133359Sobrien		(void)fprintf(stderr, "%s: %s\n",
420133359Sobrien		    progname, magic_error(magic));
421191736Sobrien		magic_close(magic);
422191736Sobrien		return NULL;
423133359Sobrien	}
424191736Sobrien	return magic;
425133359Sobrien}
426133359Sobrien
42768349Sobrien/*
42868349Sobrien * unwrap -- read a file of filenames, do each one.
42968349Sobrien */
430191736Sobrienprivate int
431191736Sobrienunwrap(struct magic_set *ms, const char *fn)
43268349Sobrien{
43368349Sobrien	FILE *f;
434226048Sobrien	ssize_t len;
435226048Sobrien	char *line = NULL;
436226048Sobrien	size_t llen = 0;
43768349Sobrien	int wid = 0, cwid;
438191736Sobrien	int e = 0;
43968349Sobrien
44068349Sobrien	if (strcmp("-", fn) == 0) {
44168349Sobrien		f = stdin;
44268349Sobrien		wid = 1;
44368349Sobrien	} else {
44468349Sobrien		if ((f = fopen(fn, "r")) == NULL) {
445133359Sobrien			(void)fprintf(stderr, "%s: Cannot open `%s' (%s).\n",
446133359Sobrien			    progname, fn, strerror(errno));
447191736Sobrien			return 1;
44868349Sobrien		}
44968349Sobrien
450226048Sobrien		while ((len = getline(&line, &llen, f)) > 0) {
451226048Sobrien			if (line[len - 1] == '\n')
452226048Sobrien				line[len - 1] = '\0';
453226048Sobrien			cwid = file_mbswidth(line);
45468349Sobrien			if (cwid > wid)
45568349Sobrien				wid = cwid;
45668349Sobrien		}
45768349Sobrien
45868349Sobrien		rewind(f);
45968349Sobrien	}
46068349Sobrien
461226048Sobrien	while ((len = getline(&line, &llen, f)) > 0) {
462226048Sobrien		if (line[len - 1] == '\n')
463226048Sobrien			line[len - 1] = '\0';
464226048Sobrien		e |= process(ms, line, wid);
46568349Sobrien		if(nobuffer)
466159764Sobrien			(void)fflush(stdout);
46768349Sobrien	}
46868349Sobrien
469226048Sobrien	free(line);
470159764Sobrien	(void)fclose(f);
471191736Sobrien	return e;
47268349Sobrien}
47368349Sobrien
474169942Sobrien/*
475169942Sobrien * Called for each input file on the command line (or in a list of files)
476169942Sobrien */
477191736Sobrienprivate int
478191736Sobrienprocess(struct magic_set *ms, const char *inname, int wid)
479133359Sobrien{
480133359Sobrien	const char *type;
481133359Sobrien	int std_in = strcmp(inname, "-") == 0;
48268349Sobrien
483169942Sobrien	if (wid > 0 && !bflag) {
484169942Sobrien		(void)printf("%s", std_in ? "/dev/stdin" : inname);
485169942Sobrien		if (nulsep)
486169962Sobrien			(void)putc('\0', stdout);
487226048Sobrien		(void)printf("%s", separator);
488169942Sobrien		(void)printf("%*s ",
489169942Sobrien		    (int) (nopad ? 0 : (wid - file_mbswidth(inname))), "");
490169942Sobrien	}
491133359Sobrien
492191736Sobrien	type = magic_file(ms, std_in ? NULL : inname);
493191736Sobrien	if (type == NULL) {
494191736Sobrien		(void)printf("ERROR: %s\n", magic_error(ms));
495191736Sobrien		return 1;
496191736Sobrien	} else {
497159764Sobrien		(void)printf("%s\n", type);
498191736Sobrien		return 0;
499191736Sobrien	}
500133359Sobrien}
501133359Sobrien
502267843Sdelphijprotected size_t
503133359Sobrienfile_mbswidth(const char *s)
50468349Sobrien{
505133359Sobrien#if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH)
506133359Sobrien	size_t bytesconsumed, old_n, n, width = 0;
507133359Sobrien	mbstate_t state;
508133359Sobrien	wchar_t nextchar;
509133359Sobrien	(void)memset(&state, 0, sizeof(mbstate_t));
510133359Sobrien	old_n = n = strlen(s);
51168349Sobrien
512133359Sobrien	while (n > 0) {
513133359Sobrien		bytesconsumed = mbrtowc(&nextchar, s, n, &state);
514133359Sobrien		if (bytesconsumed == (size_t)(-1) ||
515133359Sobrien		    bytesconsumed == (size_t)(-2)) {
516133359Sobrien			/* Something went wrong, return something reasonable */
517133359Sobrien			return old_n;
51868349Sobrien		}
519133359Sobrien		if (s[0] == '\n') {
520133359Sobrien			/*
521133359Sobrien			 * do what strlen() would do, so that caller
522133359Sobrien			 * is always right
523133359Sobrien			 */
524133359Sobrien			width++;
525267843Sdelphij		} else {
526267843Sdelphij			int w = wcwidth(nextchar);
527267843Sdelphij			if (w > 0)
528267843Sdelphij				width += w;
529267843Sdelphij		}
53068349Sobrien
531133359Sobrien		s += bytesconsumed, n -= bytesconsumed;
53268349Sobrien	}
533133359Sobrien	return width;
534133359Sobrien#else
535133359Sobrien	return strlen(s);
53668349Sobrien#endif
53768349Sobrien}
53868349Sobrien
539133359Sobrienprivate void
540103373Sobrienusage(void)
54174784Sobrien{
542226048Sobrien	(void)fprintf(stderr, USAGE, progname, progname, progname);
54374784Sobrien	exit(1);
54474784Sobrien}
545103373Sobrien
546133359Sobrienprivate void
547267843Sdelphijdocprint(const char *opts)
548267843Sdelphij{
549267843Sdelphij	size_t i;
550267843Sdelphij	int comma;
551267843Sdelphij	char *sp, *p;
552267843Sdelphij
553267843Sdelphij	p = strstr(opts, "%o");
554267843Sdelphij	if (p == NULL) {
555267843Sdelphij		fprintf(stdout, "%s", opts);
556267843Sdelphij		return;
557267843Sdelphij	}
558267843Sdelphij
559267843Sdelphij	for (sp = p - 1; sp > opts && *sp == ' '; sp--)
560267843Sdelphij		continue;
561267843Sdelphij
562267843Sdelphij	fprintf(stdout, "%.*s", (int)(p - opts), opts);
563267843Sdelphij
564267843Sdelphij	comma = 0;
565267843Sdelphij	for (i = 0; i < __arraycount(nv); i++) {
566267843Sdelphij		fprintf(stdout, "%s%s", comma++ ? ", " : "", nv[i].name);
567267843Sdelphij		if (i && i % 5 == 0) {
568267843Sdelphij			fprintf(stdout, ",\n%*s", (int)(p - sp - 1), "");
569267843Sdelphij			comma = 0;
570267843Sdelphij		}
571267843Sdelphij	}
572267843Sdelphij
573267843Sdelphij	fprintf(stdout, "%s", opts + (p - opts) + 2);
574267843Sdelphij}
575267843Sdelphij
576267843Sdelphijprivate void
577103373Sobrienhelp(void)
578103373Sobrien{
579175296Sobrien	(void)fputs(
580175296Sobrien"Usage: file [OPTION...] [FILE...]\n"
581175296Sobrien"Determine type of FILEs.\n"
582226048Sobrien"\n", stdout);
583175296Sobrien#define OPT(shortname, longname, opt, doc)      \
584267843Sdelphij	fprintf(stdout, "  -%c, --" longname, shortname), \
585267843Sdelphij	docprint(doc);
586175296Sobrien#define OPT_LONGONLY(longname, opt, doc)        \
587267843Sdelphij	fprintf(stdout, "      --" longname),	\
588267843Sdelphij	docprint(doc);
589175296Sobrien#include "file_opts.h"
590175296Sobrien#undef OPT
591175296Sobrien#undef OPT_LONGONLY
592226048Sobrien	fprintf(stdout, "\nReport bugs to http://bugs.gw.com/\n");
593103373Sobrien	exit(0);
594103373Sobrien}
595