1/*	$NetBSD: funcs.c,v 1.21 2023/08/18 19:00:11 christos Exp $	*/
2
3/*
4 * Copyright (c) Christos Zoulas 2003.
5 * All Rights Reserved.
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#include "file.h"
30
31#ifndef	lint
32#if 0
33FILE_RCSID("@(#)$File: funcs.c,v 1.140 2023/05/21 17:08:34 christos Exp $")
34#else
35__RCSID("$NetBSD: funcs.c,v 1.21 2023/08/18 19:00:11 christos Exp $");
36#endif
37#endif	/* lint */
38
39#include "magic.h"
40#include <assert.h>
41#include <stdarg.h>
42#include <stdlib.h>
43#include <string.h>
44#include <ctype.h>
45#ifdef HAVE_UNISTD_H
46#include <unistd.h>	/* for pipe2() */
47#endif
48#if defined(HAVE_WCHAR_H)
49#include <wchar.h>
50#endif
51#if defined(HAVE_WCTYPE_H)
52#include <wctype.h>
53#endif
54#include <limits.h>
55
56#ifndef SIZE_MAX
57#define SIZE_MAX	((size_t)~0)
58#endif
59
60file_protected char *
61file_copystr(char *buf, size_t blen, size_t width, const char *str)
62{
63	if (blen == 0)
64		return buf;
65	if (width >= blen)
66		width = blen - 1;
67	memcpy(buf, str, width);
68	buf[width] = '\0';
69	return buf;
70}
71
72file_private void
73file_clearbuf(struct magic_set *ms)
74{
75	free(ms->o.buf);
76	ms->o.buf = NULL;
77	ms->o.blen = 0;
78}
79
80file_private int
81file_checkfield(char *msg, size_t mlen, const char *what, const char **pp)
82{
83	const char *p = *pp;
84	int fw = 0;
85
86	while (*p && isdigit((unsigned char)*p))
87		fw = fw * 10 + (*p++ - '0');
88
89	*pp = p;
90
91	if (fw < 1024)
92		return 1;
93	if (msg)
94		snprintf(msg, mlen, "field %s too large: %d", what, fw);
95
96	return 0;
97}
98
99file_protected int
100file_checkfmt(char *msg, size_t mlen, const char *fmt)
101{
102	const char *p;
103	for (p = fmt; *p; p++) {
104		if (*p != '%')
105			continue;
106		if (*++p == '%')
107			continue;
108		// Skip uninteresting.
109		while (strchr("#0.'+- ", *p) != NULL)
110			p++;
111		if (*p == '*') {
112			if (msg)
113				snprintf(msg, mlen, "* not allowed in format");
114			return -1;
115		}
116
117		if (!file_checkfield(msg, mlen, "width", &p))
118			return -1;
119
120		if (*p == '.') {
121			p++;
122			if (!file_checkfield(msg, mlen, "precision", &p))
123				return -1;
124		}
125
126		if (!isalpha((unsigned char)*p)) {
127			if (msg)
128				snprintf(msg, mlen, "bad format char: %c", *p);
129			return -1;
130		}
131	}
132	return 0;
133}
134
135/*
136 * Like printf, only we append to a buffer.
137 */
138file_protected int
139file_vprintf(struct magic_set *ms, const char *fmt, va_list ap)
140{
141	int len;
142	char *buf, *newstr;
143	char tbuf[1024];
144
145	if (ms->event_flags & EVENT_HAD_ERR)
146		return 0;
147
148	if (file_checkfmt(tbuf, sizeof(tbuf), fmt)) {
149		file_clearbuf(ms);
150		file_error(ms, 0, "Bad magic format `%s' (%s)", fmt, tbuf);
151		return -1;
152	}
153
154	len = vasprintf(&buf, fmt, ap);
155	if (len < 0 || (size_t)len > 1024 || len + ms->o.blen > 1024 * 1024) {
156		size_t blen = ms->o.blen;
157		free(buf);
158		file_clearbuf(ms);
159		file_error(ms, 0, "Output buffer space exceeded %d+%"
160		    SIZE_T_FORMAT "u", len, blen);
161		return -1;
162	}
163
164	if (ms->o.buf != NULL) {
165		len = asprintf(&newstr, "%s%s", ms->o.buf, buf);
166		free(buf);
167		if (len < 0)
168			goto out;
169		free(ms->o.buf);
170		buf = newstr;
171	}
172	ms->o.buf = buf;
173	ms->o.blen = len;
174	return 0;
175out:
176	file_clearbuf(ms);
177	file_error(ms, errno, "vasprintf failed");
178	return -1;
179}
180
181file_protected int
182file_printf(struct magic_set *ms, const char *fmt, ...)
183{
184	int rv;
185	va_list ap;
186
187	va_start(ap, fmt);
188	rv = file_vprintf(ms, fmt, ap);
189	va_end(ap);
190	return rv;
191}
192
193/*
194 * error - print best error message possible
195 */
196/*VARARGS*/
197__attribute__((__format__(__printf__, 3, 0)))
198file_private void
199file_error_core(struct magic_set *ms, int error, const char *f, va_list va,
200    size_t lineno)
201{
202	/* Only the first error is ok */
203	if (ms->event_flags & EVENT_HAD_ERR)
204		return;
205	if (lineno != 0) {
206		file_clearbuf(ms);
207		(void)file_printf(ms, "line %" SIZE_T_FORMAT "u:", lineno);
208	}
209	if (ms->o.buf && *ms->o.buf)
210		(void)file_printf(ms, " ");
211	(void)file_vprintf(ms, f, va);
212	if (error > 0)
213		(void)file_printf(ms, " (%s)", strerror(error));
214	ms->event_flags |= EVENT_HAD_ERR;
215	ms->error = error;
216}
217
218/*VARARGS*/
219file_protected void
220file_error(struct magic_set *ms, int error, const char *f, ...)
221{
222	va_list va;
223	va_start(va, f);
224	file_error_core(ms, error, f, va, 0);
225	va_end(va);
226}
227
228/*
229 * Print an error with magic line number.
230 */
231/*VARARGS*/
232file_protected void
233file_magerror(struct magic_set *ms, const char *f, ...)
234{
235	va_list va;
236	va_start(va, f);
237	file_error_core(ms, 0, f, va, ms->line);
238	va_end(va);
239}
240
241file_protected void
242file_oomem(struct magic_set *ms, size_t len)
243{
244	file_error(ms, errno, "cannot allocate %" SIZE_T_FORMAT "u bytes",
245	    len);
246}
247
248file_protected void
249file_badseek(struct magic_set *ms)
250{
251	file_error(ms, errno, "error seeking");
252}
253
254file_protected void
255file_badread(struct magic_set *ms)
256{
257	file_error(ms, errno, "error reading");
258}
259
260#ifndef COMPILE_ONLY
261#define FILE_SEPARATOR "\n- "
262
263file_protected int
264file_separator(struct magic_set *ms)
265{
266	return file_printf(ms, FILE_SEPARATOR);
267}
268
269static void
270trim_separator(struct magic_set *ms)
271{
272	size_t l;
273
274	if (ms->o.buf == NULL)
275		return;
276
277	l = strlen(ms->o.buf);
278	if (l < sizeof(FILE_SEPARATOR))
279		return;
280
281	l -= sizeof(FILE_SEPARATOR) - 1;
282	if (strcmp(ms->o.buf + l, FILE_SEPARATOR) != 0)
283		return;
284
285	ms->o.buf[l] = '\0';
286}
287
288static int
289checkdone(struct magic_set *ms, int *rv)
290{
291	if ((ms->flags & MAGIC_CONTINUE) == 0)
292		return 1;
293	if (file_separator(ms) == -1)
294		*rv = -1;
295	return 0;
296}
297
298file_protected int
299file_default(struct magic_set *ms, size_t nb)
300{
301	if (ms->flags & MAGIC_MIME) {
302		if ((ms->flags & MAGIC_MIME_TYPE) &&
303		    file_printf(ms, "application/%s",
304			nb ? "octet-stream" : "x-empty") == -1)
305			return -1;
306		return 1;
307	}
308	if (ms->flags & MAGIC_APPLE) {
309		if (file_printf(ms, "UNKNUNKN") == -1)
310			return -1;
311		return 1;
312	}
313	if (ms->flags & MAGIC_EXTENSION) {
314		if (file_printf(ms, "???") == -1)
315			return -1;
316		return 1;
317	}
318	return 0;
319}
320
321/*
322 * The magic detection functions return:
323 *	 1: found
324 *	 0: not found
325 *	-1: error
326 */
327/*ARGSUSED*/
328file_protected int
329file_buffer(struct magic_set *ms, int fd, struct stat *st,
330    const char *inname __attribute__ ((__unused__)),
331    const void *buf, size_t nb)
332{
333	int m = 0, rv = 0, looks_text = 0;
334	const char *code = NULL;
335	const char *code_mime = "binary";
336	const char *def = "data";
337	const char *ftype = NULL;
338	char *rbuf = NULL;
339	struct buffer b;
340
341	buffer_init(&b, fd, st, buf, nb);
342	ms->mode = b.st.st_mode;
343
344	if (nb == 0) {
345		def = "empty";
346		goto simple;
347	} else if (nb == 1) {
348		def = "very short file (no magic)";
349		goto simple;
350	}
351
352	if ((ms->flags & MAGIC_NO_CHECK_ENCODING) == 0) {
353		looks_text = file_encoding(ms, &b, NULL, 0,
354		    &code, &code_mime, &ftype);
355	}
356
357#ifdef __EMX__
358	if ((ms->flags & MAGIC_NO_CHECK_APPTYPE) == 0 && inname) {
359		m = file_os2_apptype(ms, inname, &b);
360		if ((ms->flags & MAGIC_DEBUG) != 0)
361			(void)fprintf(stderr, "[try os2_apptype %d]\n", m);
362		switch (m) {
363		case -1:
364			return -1;
365		case 0:
366			break;
367		default:
368			return 1;
369		}
370	}
371#endif
372#if HAVE_FORK
373	/* try compression stuff */
374	if ((ms->flags & MAGIC_NO_CHECK_COMPRESS) == 0) {
375		m = file_zmagic(ms, &b, inname);
376		if ((ms->flags & MAGIC_DEBUG) != 0)
377			(void)fprintf(stderr, "[try zmagic %d]\n", m);
378		if (m) {
379			goto done_encoding;
380		}
381	}
382#endif
383	/* Check if we have a tar file */
384	if ((ms->flags & MAGIC_NO_CHECK_TAR) == 0) {
385		m = file_is_tar(ms, &b);
386		if ((ms->flags & MAGIC_DEBUG) != 0)
387			(void)fprintf(stderr, "[try tar %d]\n", m);
388		if (m) {
389			if (checkdone(ms, &rv))
390				goto done;
391		}
392	}
393
394	/* Check if we have a JSON file */
395	if ((ms->flags & MAGIC_NO_CHECK_JSON) == 0) {
396		m = file_is_json(ms, &b);
397		if ((ms->flags & MAGIC_DEBUG) != 0)
398			(void)fprintf(stderr, "[try json %d]\n", m);
399		if (m) {
400			if (checkdone(ms, &rv))
401				goto done;
402		}
403	}
404
405	/* Check if we have a CSV file */
406	if ((ms->flags & MAGIC_NO_CHECK_CSV) == 0) {
407		m = file_is_csv(ms, &b, looks_text, code);
408		if ((ms->flags & MAGIC_DEBUG) != 0)
409			(void)fprintf(stderr, "[try csv %d]\n", m);
410		if (m) {
411			if (checkdone(ms, &rv))
412				goto done;
413		}
414	}
415
416	/* Check if we have a SIMH tape file */
417	if ((ms->flags & MAGIC_NO_CHECK_SIMH) == 0) {
418		m = file_is_simh(ms, &b);
419		if ((ms->flags & MAGIC_DEBUG) != 0)
420			(void)fprintf(stderr, "[try simh %d]\n", m);
421		if (m) {
422			if (checkdone(ms, &rv))
423				goto done;
424		}
425	}
426
427	/* Check if we have a CDF file */
428	if ((ms->flags & MAGIC_NO_CHECK_CDF) == 0) {
429		m = file_trycdf(ms, &b);
430		if ((ms->flags & MAGIC_DEBUG) != 0)
431			(void)fprintf(stderr, "[try cdf %d]\n", m);
432		if (m) {
433			if (checkdone(ms, &rv))
434				goto done;
435		}
436	}
437#ifdef BUILTIN_ELF
438	if ((ms->flags & MAGIC_NO_CHECK_ELF) == 0 && nb > 5 && fd != -1) {
439		file_pushbuf_t *pb;
440		/*
441		 * We matched something in the file, so this
442		 * *might* be an ELF file, and the file is at
443		 * least 5 bytes long, so if it's an ELF file
444		 * it has at least one byte past the ELF magic
445		 * number - try extracting information from the
446		 * ELF headers that cannot easily be  extracted
447		 * with rules in the magic file. We we don't
448		 * print the information yet.
449		 */
450		if ((pb = file_push_buffer(ms)) == NULL)
451			return -1;
452
453		rv = file_tryelf(ms, &b);
454		rbuf = file_pop_buffer(ms, pb);
455		if (rv == -1) {
456			free(rbuf);
457			rbuf = NULL;
458		}
459		if ((ms->flags & MAGIC_DEBUG) != 0)
460			(void)fprintf(stderr, "[try elf %d]\n", m);
461	}
462#endif
463
464	/* try soft magic tests */
465	if ((ms->flags & MAGIC_NO_CHECK_SOFT) == 0) {
466		m = file_softmagic(ms, &b, NULL, NULL, BINTEST, looks_text);
467		if ((ms->flags & MAGIC_DEBUG) != 0)
468			(void)fprintf(stderr, "[try softmagic %d]\n", m);
469		if (m == 1 && rbuf) {
470			if (file_printf(ms, "%s", rbuf) == -1)
471				goto done;
472		}
473		if (m) {
474			if (checkdone(ms, &rv))
475				goto done;
476		}
477	}
478
479	/* try text properties */
480	if ((ms->flags & MAGIC_NO_CHECK_TEXT) == 0) {
481
482		m = file_ascmagic(ms, &b, looks_text);
483		if ((ms->flags & MAGIC_DEBUG) != 0)
484			(void)fprintf(stderr, "[try ascmagic %d]\n", m);
485		if (m) {
486			goto done;
487		}
488	}
489
490simple:
491	/* give up */
492	if (m == 0) {
493		m = 1;
494		rv = file_default(ms, nb);
495		if (rv == 0)
496			if (file_printf(ms, "%s", def) == -1)
497				rv = -1;
498	}
499 done:
500	trim_separator(ms);
501	if ((ms->flags & MAGIC_MIME_ENCODING) != 0) {
502		if (ms->flags & MAGIC_MIME_TYPE)
503			if (file_printf(ms, "; charset=") == -1)
504				rv = -1;
505		if (file_printf(ms, "%s", code_mime) == -1)
506			rv = -1;
507	}
508#if HAVE_FORK
509 done_encoding:
510#endif
511	free(rbuf);
512	buffer_fini(&b);
513	if (rv)
514		return rv;
515
516	return m;
517}
518#endif
519
520file_protected int
521file_reset(struct magic_set *ms, int checkloaded)
522{
523	if (checkloaded && ms->mlist[0] == NULL) {
524		file_error(ms, 0, "no magic files loaded");
525		return -1;
526	}
527	file_clearbuf(ms);
528	if (ms->o.pbuf) {
529		free(ms->o.pbuf);
530		ms->o.pbuf = NULL;
531	}
532	ms->event_flags &= ~EVENT_HAD_ERR;
533	ms->error = -1;
534	return 0;
535}
536
537#define OCTALIFY(n, o)	\
538	/*LINTED*/ \
539	(void)(*(n)++ = '\\', \
540	*(n)++ = ((CAST(uint32_t, *(o)) >> 6) & 3) + '0', \
541	*(n)++ = ((CAST(uint32_t, *(o)) >> 3) & 7) + '0', \
542	*(n)++ = ((CAST(uint32_t, *(o)) >> 0) & 7) + '0', \
543	(o)++)
544
545file_protected const char *
546file_getbuffer(struct magic_set *ms)
547{
548	char *pbuf, *op, *np;
549	size_t psize, len;
550
551	if (ms->event_flags & EVENT_HAD_ERR)
552		return NULL;
553
554	if (ms->flags & MAGIC_RAW)
555		return ms->o.buf;
556
557	if (ms->o.buf == NULL)
558		return NULL;
559
560	/* * 4 is for octal representation, + 1 is for NUL */
561	len = strlen(ms->o.buf);
562	if (len > (SIZE_MAX - 1) / 4) {
563		file_oomem(ms, len);
564		return NULL;
565	}
566	psize = len * 4 + 1;
567	if ((pbuf = CAST(char *, realloc(ms->o.pbuf, psize))) == NULL) {
568		file_oomem(ms, psize);
569		return NULL;
570	}
571	ms->o.pbuf = pbuf;
572
573#if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH)
574	{
575		mbstate_t state;
576		wchar_t nextchar;
577		int mb_conv = 1;
578		size_t bytesconsumed;
579		char *eop;
580		(void)memset(&state, 0, sizeof(mbstate_t));
581
582		np = ms->o.pbuf;
583		op = ms->o.buf;
584		eop = op + len;
585
586		while (op < eop) {
587			bytesconsumed = mbrtowc(&nextchar, op,
588			    CAST(size_t, eop - op), &state);
589			if (bytesconsumed == CAST(size_t, -1) ||
590			    bytesconsumed == CAST(size_t, -2)) {
591				mb_conv = 0;
592				break;
593			}
594
595			if (iswprint(nextchar)) {
596				(void)memcpy(np, op, bytesconsumed);
597				op += bytesconsumed;
598				np += bytesconsumed;
599			} else {
600				while (bytesconsumed-- > 0)
601					OCTALIFY(np, op);
602			}
603		}
604		*np = '\0';
605
606		/* Parsing succeeded as a multi-byte sequence */
607		if (mb_conv != 0)
608			return ms->o.pbuf;
609	}
610#endif
611
612	for (np = ms->o.pbuf, op = ms->o.buf; *op;) {
613		if (isprint(CAST(unsigned char, *op))) {
614			*np++ = *op++;
615		} else {
616			OCTALIFY(np, op);
617		}
618	}
619	*np = '\0';
620	return ms->o.pbuf;
621}
622
623file_protected int
624file_check_mem(struct magic_set *ms, unsigned int level)
625{
626	size_t len;
627
628	if (level >= ms->c.len) {
629		len = (ms->c.len = 20 + level) * sizeof(*ms->c.li);
630		ms->c.li = CAST(struct level_info *, (ms->c.li == NULL) ?
631		    malloc(len) :
632		    realloc(ms->c.li, len));
633		if (ms->c.li == NULL) {
634			file_oomem(ms, len);
635			return -1;
636		}
637	}
638	ms->c.li[level].got_match = 0;
639#ifdef ENABLE_CONDITIONALS
640	ms->c.li[level].last_match = 0;
641	ms->c.li[level].last_cond = COND_NONE;
642#endif /* ENABLE_CONDITIONALS */
643	return 0;
644}
645
646file_protected size_t
647file_printedlen(const struct magic_set *ms)
648{
649	return ms->o.blen;
650}
651
652file_protected int
653file_replace(struct magic_set *ms, const char *pat, const char *rep)
654{
655	file_regex_t rx;
656	int rc, rv = -1;
657
658	rc = file_regcomp(ms, &rx, pat, REG_EXTENDED);
659	if (rc == 0) {
660		regmatch_t rm;
661		int nm = 0;
662		while (file_regexec(ms, &rx, ms->o.buf, 1, &rm, 0) == 0) {
663			ms->o.buf[rm.rm_so] = '\0';
664			if (file_printf(ms, "%s%s", rep,
665			    rm.rm_eo != 0 ? ms->o.buf + rm.rm_eo : "") == -1)
666				goto out;
667			nm++;
668		}
669		rv = nm;
670	}
671out:
672	file_regfree(&rx);
673	return rv;
674}
675
676file_private int
677check_regex(struct magic_set *ms, const char *pat)
678{
679	char sbuf[512];
680	unsigned char oc = '\0';
681	const char *p;
682
683	for (p = pat; *p; p++) {
684		unsigned char c = *p;
685		// Avoid repetition
686		if (c == oc && strchr("?*+{", c) != NULL) {
687			size_t len = strlen(pat);
688			file_magwarn(ms,
689			    "repetition-operator operand `%c' "
690			    "invalid in regex `%s'", c,
691			    file_printable(ms, sbuf, sizeof(sbuf), pat, len));
692			return -1;
693		}
694		oc = c;
695		if (isprint(c) || isspace(c) || c == '\b'
696		    || c == 0x8a) // XXX: apple magic fixme
697			continue;
698		size_t len = strlen(pat);
699		file_magwarn(ms,
700		    "non-ascii characters in regex \\%#o `%s'",
701		    c, file_printable(ms, sbuf, sizeof(sbuf), pat, len));
702		return -1;
703	}
704	return 0;
705}
706
707file_protected int
708file_regcomp(struct magic_set *ms file_locale_used, file_regex_t *rx,
709    const char *pat, int flags)
710{
711	if (check_regex(ms, pat) == -1)
712		return -1;
713
714#ifdef USE_C_LOCALE
715	locale_t old = uselocale(ms->c_lc_ctype);
716	assert(old != NULL);
717#else
718	char old[1024];
719	strlcpy(old, setlocale(LC_CTYPE, NULL), sizeof(old));
720	(void)setlocale(LC_CTYPE, "C");
721#endif
722	int rc;
723	rc = regcomp(rx, pat, flags);
724
725#ifdef USE_C_LOCALE
726	uselocale(old);
727#else
728	(void)setlocale(LC_CTYPE, old);
729#endif
730	if (rc > 0 && (ms->flags & MAGIC_CHECK)) {
731		char errmsg[512], buf[512];
732
733		(void)regerror(rc, rx, errmsg, sizeof(errmsg));
734		file_magerror(ms, "regex error %d for `%s', (%s)", rc,
735		    file_printable(ms, buf, sizeof(buf), pat, strlen(pat)),
736		    errmsg);
737	}
738	return rc;
739}
740
741/*ARGSUSED*/
742file_protected int
743file_regexec(struct magic_set *ms file_locale_used, file_regex_t *rx,
744    const char *str, size_t nmatch, regmatch_t* pmatch, int eflags)
745{
746#ifdef USE_C_LOCALE
747	locale_t old = uselocale(ms->c_lc_ctype);
748	assert(old != NULL);
749#else
750	char old[1024];
751	strlcpy(old, setlocale(LC_CTYPE, NULL), sizeof(old));
752	(void)setlocale(LC_CTYPE, "C");
753#endif
754	int rc;
755	/* XXX: force initialization because glibc does not always do this */
756	if (nmatch != 0)
757		memset(pmatch, 0, nmatch * sizeof(*pmatch));
758	rc = regexec(rx, str, nmatch, pmatch, eflags);
759#ifdef USE_C_LOCALE
760	uselocale(old);
761#else
762	(void)setlocale(LC_CTYPE, old);
763#endif
764	return rc;
765}
766
767file_protected void
768file_regfree(file_regex_t *rx)
769{
770	regfree(rx);
771}
772
773file_protected file_pushbuf_t *
774file_push_buffer(struct magic_set *ms)
775{
776	file_pushbuf_t *pb;
777
778	if (ms->event_flags & EVENT_HAD_ERR)
779		return NULL;
780
781	if ((pb = (CAST(file_pushbuf_t *, malloc(sizeof(*pb))))) == NULL)
782		return NULL;
783
784	pb->buf = ms->o.buf;
785	pb->blen = ms->o.blen;
786	pb->offset = ms->offset;
787
788	ms->o.buf = NULL;
789	ms->o.blen = 0;
790	ms->offset = 0;
791
792	return pb;
793}
794
795file_protected char *
796file_pop_buffer(struct magic_set *ms, file_pushbuf_t *pb)
797{
798	char *rbuf;
799
800	if (ms->event_flags & EVENT_HAD_ERR) {
801		free(pb->buf);
802		free(pb);
803		return NULL;
804	}
805
806	rbuf = ms->o.buf;
807
808	ms->o.buf = pb->buf;
809	ms->o.blen = pb->blen;
810	ms->offset = pb->offset;
811
812	free(pb);
813	return rbuf;
814}
815
816/*
817 * convert string to ascii printable format.
818 */
819file_protected char *
820file_printable(struct magic_set *ms, char *buf, size_t bufsiz,
821    const char *str, size_t slen)
822{
823	char *ptr, *eptr = buf + bufsiz - 1;
824	const unsigned char *s = RCAST(const unsigned char *, str);
825	const unsigned char *es = s + slen;
826
827	for (ptr = buf;  ptr < eptr && s < es && *s; s++) {
828		if ((ms->flags & MAGIC_RAW) != 0 || isprint(*s)) {
829			*ptr++ = *s;
830			continue;
831		}
832		if (ptr >= eptr - 3)
833			break;
834		*ptr++ = '\\';
835		*ptr++ = ((CAST(unsigned int, *s) >> 6) & 7) + '0';
836		*ptr++ = ((CAST(unsigned int, *s) >> 3) & 7) + '0';
837		*ptr++ = ((CAST(unsigned int, *s) >> 0) & 7) + '0';
838	}
839	*ptr = '\0';
840	return buf;
841}
842
843struct guid {
844	uint32_t data1;
845	uint16_t data2;
846	uint16_t data3;
847	uint8_t data4[8];
848};
849
850file_protected int
851file_parse_guid(const char *s, uint64_t *guid)
852{
853	struct guid *g = CAST(struct guid *, CAST(void *, guid));
854#ifndef WIN32
855	return sscanf(s,
856	    "%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
857	    &g->data1, &g->data2, &g->data3, &g->data4[0], &g->data4[1],
858	    &g->data4[2], &g->data4[3], &g->data4[4], &g->data4[5],
859	    &g->data4[6], &g->data4[7]) == 11 ? 0 : -1;
860#else
861	/* MS-Windows runtime doesn't support %hhx, except under
862	   non-default __USE_MINGW_ANSI_STDIO.  */
863	uint16_t data16[8];
864	int rv = sscanf(s, "%8x-%4hx-%4hx-%2hx%2hx-%2hx%2hx%2hx%2hx%2hx%2hx",
865	    &g->data1, &g->data2, &g->data3, &data16[0], &data16[1],
866	    &data16[2], &data16[3], &data16[4], &data16[5],
867	    &data16[6], &data16[7]) == 11 ? 0 : -1;
868	int i;
869	for (i = 0; i < 8; i++)
870	    g->data4[i] = data16[i];
871	return rv;
872#endif
873}
874
875file_protected int
876file_print_guid(char *str, size_t len, const uint64_t *guid)
877{
878	const struct guid *g = CAST(const struct guid *,
879	    CAST(const void *, guid));
880
881#ifndef WIN32
882	return snprintf(str, len, "%.8X-%.4hX-%.4hX-%.2hhX%.2hhX-"
883	    "%.2hhX%.2hhX%.2hhX%.2hhX%.2hhX%.2hhX",
884	    g->data1, g->data2, g->data3, g->data4[0], g->data4[1],
885	    g->data4[2], g->data4[3], g->data4[4], g->data4[5],
886	    g->data4[6], g->data4[7]);
887#else
888	return snprintf(str, len, "%.8X-%.4hX-%.4hX-%.2hX%.2hX-"
889	    "%.2hX%.2hX%.2hX%.2hX%.2hX%.2hX",
890	    g->data1, g->data2, g->data3, g->data4[0], g->data4[1],
891	    g->data4[2], g->data4[3], g->data4[4], g->data4[5],
892	    g->data4[6], g->data4[7]);
893#endif
894}
895
896file_protected int
897file_pipe_closexec(int *fds)
898{
899#ifdef __MINGW32__
900	return 0;
901#elif defined(HAVE_PIPE2)
902	return pipe2(fds, O_CLOEXEC);
903#else
904	if (pipe(fds) == -1)
905		return -1;
906# ifdef F_SETFD
907	(void)fcntl(fds[0], F_SETFD, FD_CLOEXEC);
908	(void)fcntl(fds[1], F_SETFD, FD_CLOEXEC);
909# endif
910	return 0;
911#endif
912}
913
914file_protected int
915file_clear_closexec(int fd) {
916#ifdef F_SETFD
917	return fcntl(fd, F_SETFD, 0);
918#else
919	return 0;
920#endif
921}
922
923file_protected char *
924file_strtrim(char *str)
925{
926	char *last;
927
928	while (isspace(CAST(unsigned char, *str)))
929		str++;
930	last = str;
931	while (*last)
932		last++;
933	--last;
934	while (isspace(CAST(unsigned char, *last)))
935		last--;
936	*++last = '\0';
937	return str;
938}
939