[BACK]Return to mandoc.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / external / bsd / mdocml / dist

File: [cvs.NetBSD.org] / src / external / bsd / mdocml / dist / mandoc.c (download)

Revision 1.1.1.17 (vendor branch), Fri Jul 15 14:25:56 2016 UTC (7 years, 9 months ago) by christos
Branch: MAIN, KRISTAPS
CVS Tags: prg-localcount2-base3, prg-localcount2-base2, prg-localcount2-base1, prg-localcount2-base, prg-localcount2, phil-wifi-base, pgoyette-localcount-20170426, pgoyette-localcount-20170320, pgoyette-localcount-20170107, pgoyette-localcount-20161104, pgoyette-localcount-20160806, pgoyette-localcount-20160726, pgoyette-compat-base, pgoyette-compat-0728, pgoyette-compat-0625, pgoyette-compat-0521, pgoyette-compat-0502, pgoyette-compat-0422, pgoyette-compat-0415, pgoyette-compat-0407, pgoyette-compat-0330, pgoyette-compat-0322, pgoyette-compat-0315, perseant-stdc-iso10646-base, perseant-stdc-iso10646, netbsd-8-base, netbsd-8-2-RELEASE, netbsd-8-1-RELEASE, netbsd-8-1-RC1, netbsd-8-0-RELEASE, netbsd-8-0-RC2, netbsd-8-0-RC1, netbsd-8, mdocml-1-14-1, mdocml-1-13-4, matt-nb8-mediatek-base, matt-nb8-mediatek, localcount-20160914, bouyer-socketcan-base1, bouyer-socketcan-base, bouyer-socketcan
Branch point for: phil-wifi, pgoyette-compat
Changes since 1.1.1.16: +62 -71 lines

Changes in version 1.13.4, released on July 14, 2016

    --- MAJOR NEW FEATURES ---
 * man.conf(5): Design and implement a simpler configuration file format.
 * man(1): Leverage less(1) -T and :t in a way resembling ctags(1)
   to jump to the definitions of various terms inside manual pages.
 * soelim(1): New implementation by Baptiste Daroussin.
 * privilege limitation: Use OpenBSD pledge(2) or OS X sandbox_init(3)
   when available.
 * man.cgi(8): Support short URIs like http://man.openbsd.org/mdoc .
 * mandoc.css: Use one unified stylesheet rather than three different ones.
    --- MAJOR FUNCTIONALLY RELEVANT BUGFIXES ---
 * mdoc(7): Fix multiple aspects of SYNOPSIS .Nm formatting.
 * man(1): Fix process group handling, avoiding unclean shutdowns.
    --- PORTABILITY IMPROVEMENTS ---
 * Correctly use the ohash(3) compatibility implementation
   even when building without SQLite support.
 * Add compat glue for building on Solaris 9 and 10.
 * Let ./configure select a supported RE syntax for word boundaries.
 * Support LDFLAGS, to be used for example for hardening options.
 * Avoid mixing putchar(3) and putwchar(3) on the same file descriptor,
   it resulted in output corruption on some platforms.
 * Avoid reusing va_lists, use va_copy(3) for better portability.
 * Do not hardcode the path to the more(1) program.
    --- MINOR NEW FEATURES ---
 * roff(7): Implement \n(.$ (number of macro arguments).
 * roff(7): Fully implement \z (do not advance cursor).
 * roff(7): Implement the `r'  conditional (register exists).
 * roff(7): Implement \\$* (interpolate all arguments).
 * roff(7): Parse and ignore \, and \/ (italic corrections).
 * When there is no -m, no -M, no MANPATH and no /etc/man.conf,
   fall back to /usr/share/man:/usr/X11R6/man:/usr/local/man.
 * man(1): Give manuals in purely numerical sections priority over
   manuals of the same name in sections with an alphabetical suffix.
 * man.cgi(8): Support "header.html" and "footer.html".
 * man.cgi(8): Set the "autofocus" attribute on the query text box.
 * man.cgi(8): Simplify the search form, drop two useless buttons.
 * man.cgi(8): Delete the pseudo-manpath "mandoc", assume that
   apropos(1) and man.cgi(8) are installed in the default manpath.
    --- RELIABILITY BUGFIXES ---
 * mdoc(7): Avoid a use after free and an assertion failure when nodes
   are deleted during validation.
 * mdoc(7): Avoid a NULL pointer access when .Bd has no arguments.
 * mdoc(7): Avoid a NULL pointer access triggered by mismatching end macros.
 * mdoc(7): Avoid an assertion when .Fo has no argument.
 * mdoc(7): Avoid an assertion when .Ta<tab> occurs in .Bl -column.
 * mdoc(7): Avoid an assertion when a body gets broken and has a tail.
 * roff(7): Avoid an assertion caused by blanks inside \o.
 * roff(7): Make .so links to gziped manuals work without mandoc.db(5).
 * tbl(7): Avoid a use after free when the last line of a layout is empty.
 * eqn(7): Avoid an infinite loop caused by recursive "define".
 * makewhatis(8): Avoid a segfault caused by unusual directory structures.
 * Fix handling of leading, trailing, and double colons in MANPATH and -m.
    --- MINOR BUGFIXES ---
 * mdoc(7): Put arguments to end macros of broken partial explicit blocks
   inside the breaking block.
 * mdoc(7): Let .Dv force normal font.
 * mdoc(7): Make trailing whitespace significant in .Bl -tag widths.
 * mdoc(7): Fix macro interpretation around tabs in .Bl -column.
 * man(7): Use the default width for .RS without arguments.
 * man(7): On a new RS nesting level, the saved width starts from
   the default width, not from the saved width of the previous level.
 * man(7): Allow .PD in next-line scope.
 * man(7): Improve handling of empty .HP.
 * man(7): Improve formatting of .br and .sp inside .HP.
 * man(7): Do not mistreat empty arguments to font alternating
   macros as vertical spacing requests.
 * man(7): Allow fill mode changes in tagged paragraph next-line scope.
 * man(7): Fix minor bugs in block rewinding and simplify the related code.
 * man(7): Add missing line breaks before subsection headers.
 * man(7): Give section and subsection headers hanging indentation.
 * man(7): Make trailing whitespace significant in .TP widths.
 * roff(7): Don't allow breaking the output line after hyphens
   that immediately follow escape sequences.
 * roff(7): Ignore blank characters at the beginning of conditional blocks.
 * roff(7): Escape breakable hyphens only after handling input line traps.
 * roff(7): Reject \[uD800] to \[uDFFF] (surrogates) in the parser.
 * tbl(7): Allow more than one data field after T} on the same input line.
 * terminal output: Apply bold and italic to non-ASCII Unicode codepoints.
 * terminal output: Improve rounding rules for horizontal scaling widths.
 * HTML output: Render ASCII_NBRSP as "&nbsp;", not "-".
 * man(1): Do not match the first part of a name if it continues with a dot.
 * man(1): Keep working even if the current directory is unusable.
 * man(1): Better error message when $PAGER is invalid.
 * makewhatis(8): Improve handling of .Va and .Vt macros.
 * apropos(1): Print "nothing appropriate" to stderr when appropriate.
 * apropos(1): Abort with a useful error message when elementary
   database operations like preparing queries or binding variables fail.
    --- STRUCTURAL CHANGES, no functional change ---
 * mdoc(7) and man(7): Unified data structures struct roff_node etc.
 * mdoc(7) and man(7): Unified node handling library in roff.c.
 * mdoc(7) and man(7): Seperate validation phase from parsing.
 * roff(7): Major character table cleanup.
 * Link with libz rather than forking gunzip(1).
    --- THANKS TO ---
 * Baptiste Daroussin (FreeBSD) for the new soelim(1)
   and for release testing.
 * Anthony Bentley (OpenBSD) for unifying mandoc.css, two nice
   patches for man.cgi(8), some documentation patches, some bug
   reports, and various useful discussions.
 * Todd Miller (OpenBSD) for lots of help with process group and
   signal handling, a few patches, some bug reports and some useful
   discussions.
 * Jonathan Gray (OpenBSD) for yet more testing with afl(1)
   again resulting in more than half a dozen important bug reports.
 * Svyatoslav Mishyn (Crux Linux) for some patches, several bug
   reports, and extensive release testing.
 * Christian Neukirchen (void Linux) for a number of compatibility
   patches and suggestions and several bug reports.
 * Christos Zoulas (NetBSD) for a bug fix patch and some useful
   suggestions for cleanup.
 * Florian Obser (OpenBSD) for a bugfix patch and some bug reports.
 * Sevan Janiyan for help with Solaris compatibility and release
   testing on many platforms.
 * Jan Holzhueter and OpenCSW in general for help with Solaris
   compatibility, and for providing me with a Solaris 9/10/11 testing
   environment.
 * Michael McConville (OpenBSD) for some simple cleanup patches.
 * Thomas Klausner (NetBSD) for some bug reports and release testing.
 * Christian Weisgerber, Dmitrij Czarkoff, Igor Sobrado,
   Ken Westerback, Marc Espie, Mike Belopuhov, Rafael Neves,
   Ted Unangst, Tim van der Molen, Theo Buehler, Theo de Raadt
   (OpenBSD), Kurt Jaeger, Dag Erling Smoergrav (FreeBSD),
   Joerg Sonnenberger (NetBSD), Carsten Kunze (Heirloom troff),
   Daniel Levai, Fabian Raetz, Jan Stary, Jean-Yves Migeon,
   Lorenzo Beretta, Markus Waldeck, Maxim Belooussov, Michael Reed,
   Peter Bray, and Serguey Parkhomovsky for bug reports and feature
   suggestions.
 * Alexander Hall, Andrew Fresh, Antoine Jacoutot, Doug Hogan,
   Jason McIntyre, Jasper Lievisse Adriaanse, Kent Spillner,
   Nicholas Marriott, Peter Hessler, Sebastien Marie, Stefan Sperling,
   and Theo de Raadt (OpenBSD) for helpful discussions and feedback.

/*	Id: mandoc.c,v 1.98 2015/11/12 22:44:27 schwarze Exp  */
/*
 * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
 * Copyright (c) 2011-2015 Ingo Schwarze <schwarze@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "config.h"

#include <sys/types.h>

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "mandoc.h"
#include "mandoc_aux.h"
#include "libmandoc.h"

static	int	 a2time(time_t *, const char *, const char *);
static	char	*time2a(time_t);


enum mandoc_esc
mandoc_escape(const char **end, const char **start, int *sz)
{
	const char	*local_start;
	int		 local_sz;
	char		 term;
	enum mandoc_esc	 gly;

	/*
	 * When the caller doesn't provide return storage,
	 * use local storage.
	 */

	if (NULL == start)
		start = &local_start;
	if (NULL == sz)
		sz = &local_sz;

	/*
	 * Beyond the backslash, at least one input character
	 * is part of the escape sequence.  With one exception
	 * (see below), that character won't be returned.
	 */

	gly = ESCAPE_ERROR;
	*start = ++*end;
	*sz = 0;
	term = '\0';

	switch ((*start)[-1]) {
	/*
	 * First the glyphs.  There are several different forms of
	 * these, but each eventually returns a substring of the glyph
	 * name.
	 */
	case '(':
		gly = ESCAPE_SPECIAL;
		*sz = 2;
		break;
	case '[':
		gly = ESCAPE_SPECIAL;
		term = ']';
		break;
	case 'C':
		if ('\'' != **start)
			return ESCAPE_ERROR;
		*start = ++*end;
		gly = ESCAPE_SPECIAL;
		term = '\'';
		break;

	/*
	 * Escapes taking no arguments at all.
	 */
	case 'd':
	case 'u':
	case ',':
	case '/':
		return ESCAPE_IGNORE;

	/*
	 * The \z escape is supposed to output the following
	 * character without advancing the cursor position.
	 * Since we are mostly dealing with terminal mode,
	 * let us just skip the next character.
	 */
	case 'z':
		return ESCAPE_SKIPCHAR;

	/*
	 * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
	 * 'X' is the trigger.  These have opaque sub-strings.
	 */
	case 'F':
	case 'g':
	case 'k':
	case 'M':
	case 'm':
	case 'n':
	case 'V':
	case 'Y':
		gly = ESCAPE_IGNORE;
		/* FALLTHROUGH */
	case 'f':
		if (ESCAPE_ERROR == gly)
			gly = ESCAPE_FONT;
		switch (**start) {
		case '(':
			*start = ++*end;
			*sz = 2;
			break;
		case '[':
			*start = ++*end;
			term = ']';
			break;
		default:
			*sz = 1;
			break;
		}
		break;

	/*
	 * These escapes are of the form \X'Y', where 'X' is the trigger
	 * and 'Y' is any string.  These have opaque sub-strings.
	 * The \B and \w escapes are handled in roff.c, roff_res().
	 */
	case 'A':
	case 'b':
	case 'D':
	case 'R':
	case 'X':
	case 'Z':
		gly = ESCAPE_IGNORE;
		/* FALLTHROUGH */
	case 'o':
		if (**start == '\0')
			return ESCAPE_ERROR;
		if (gly == ESCAPE_ERROR)
			gly = ESCAPE_OVERSTRIKE;
		term = **start;
		*start = ++*end;
		break;

	/*
	 * These escapes are of the form \X'N', where 'X' is the trigger
	 * and 'N' resolves to a numerical expression.
	 */
	case 'h':
	case 'H':
	case 'L':
	case 'l':
	case 'S':
	case 'v':
	case 'x':
		if (strchr(" %&()*+-./0123456789:<=>", **start)) {
			if ('\0' != **start)
				++*end;
			return ESCAPE_ERROR;
		}
		gly = ESCAPE_IGNORE;
		term = **start;
		*start = ++*end;
		break;

	/*
	 * Special handling for the numbered character escape.
	 * XXX Do any other escapes need similar handling?
	 */
	case 'N':
		if ('\0' == **start)
			return ESCAPE_ERROR;
		(*end)++;
		if (isdigit((unsigned char)**start)) {
			*sz = 1;
			return ESCAPE_IGNORE;
		}
		(*start)++;
		while (isdigit((unsigned char)**end))
			(*end)++;
		*sz = *end - *start;
		if ('\0' != **end)
			(*end)++;
		return ESCAPE_NUMBERED;

	/*
	 * Sizes get a special category of their own.
	 */
	case 's':
		gly = ESCAPE_IGNORE;

		/* See +/- counts as a sign. */
		if ('+' == **end || '-' == **end || ASCII_HYPH == **end)
			*start = ++*end;

		switch (**end) {
		case '(':
			*start = ++*end;
			*sz = 2;
			break;
		case '[':
			*start = ++*end;
			term = ']';
			break;
		case '\'':
			*start = ++*end;
			term = '\'';
			break;
		case '3':
		case '2':
		case '1':
			*sz = (*end)[-1] == 's' &&
			    isdigit((unsigned char)(*end)[1]) ? 2 : 1;
			break;
		default:
			*sz = 1;
			break;
		}

		break;

	/*
	 * Anything else is assumed to be a glyph.
	 * In this case, pass back the character after the backslash.
	 */
	default:
		gly = ESCAPE_SPECIAL;
		*start = --*end;
		*sz = 1;
		break;
	}

	assert(ESCAPE_ERROR != gly);

	/*
	 * Read up to the terminating character,
	 * paying attention to nested escapes.
	 */

	if ('\0' != term) {
		while (**end != term) {
			switch (**end) {
			case '\0':
				return ESCAPE_ERROR;
			case '\\':
				(*end)++;
				if (ESCAPE_ERROR ==
				    mandoc_escape(end, NULL, NULL))
					return ESCAPE_ERROR;
				break;
			default:
				(*end)++;
				break;
			}
		}
		*sz = (*end)++ - *start;
	} else {
		assert(*sz > 0);
		if ((size_t)*sz > strlen(*start))
			return ESCAPE_ERROR;
		*end += *sz;
	}

	/* Run post-processors. */

	switch (gly) {
	case ESCAPE_FONT:
		if (2 == *sz) {
			if ('C' == **start) {
				/*
				 * Treat constant-width font modes
				 * just like regular font modes.
				 */
				(*start)++;
				(*sz)--;
			} else {
				if ('B' == (*start)[0] && 'I' == (*start)[1])
					gly = ESCAPE_FONTBI;
				break;
			}
		} else if (1 != *sz)
			break;

		switch (**start) {
		case '3':
		case 'B':
			gly = ESCAPE_FONTBOLD;
			break;
		case '2':
		case 'I':
			gly = ESCAPE_FONTITALIC;
			break;
		case 'P':
			gly = ESCAPE_FONTPREV;
			break;
		case '1':
		case 'R':
			gly = ESCAPE_FONTROMAN;
			break;
		}
		break;
	case ESCAPE_SPECIAL:
		if (1 == *sz && 'c' == **start)
			gly = ESCAPE_NOSPACE;
		/*
		 * Unicode escapes are defined in groff as \[u0000]
		 * to \[u10FFFF], where the contained value must be
		 * a valid Unicode codepoint.  Here, however, only
		 * check the length and range.
		 */
		if (**start != 'u' || *sz < 5 || *sz > 7)
			break;
		if (*sz == 7 && ((*start)[1] != '1' || (*start)[2] != '0'))
			break;
		if (*sz == 6 && (*start)[1] == '0')
			break;
		if (*sz == 5 && (*start)[1] == 'D' &&
		    strchr("89ABCDEF", (*start)[2]) != NULL)
			break;
		if ((int)strspn(*start + 1, "0123456789ABCDEFabcdef")
		    + 1 == *sz)
			gly = ESCAPE_UNICODE;
		break;
	default:
		break;
	}

	return gly;
}

/*
 * Parse a quoted or unquoted roff-style request or macro argument.
 * Return a pointer to the parsed argument, which is either the original
 * pointer or advanced by one byte in case the argument is quoted.
 * NUL-terminate the argument in place.
 * Collapse pairs of quotes inside quoted arguments.
 * Advance the argument pointer to the next argument,
 * or to the NUL byte terminating the argument line.
 */
char *
mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
{
	char	 *start, *cp;
	int	  quoted, pairs, white;

	/* Quoting can only start with a new word. */
	start = *cpp;
	quoted = 0;
	if ('"' == *start) {
		quoted = 1;
		start++;
	}

	pairs = 0;
	white = 0;
	for (cp = start; '\0' != *cp; cp++) {

		/*
		 * Move the following text left
		 * after quoted quotes and after "\\" and "\t".
		 */
		if (pairs)
			cp[-pairs] = cp[0];

		if ('\\' == cp[0]) {
			/*
			 * In copy mode, translate double to single
			 * backslashes and backslash-t to literal tabs.
			 */
			switch (cp[1]) {
			case 't':
				cp[0] = '\t';
				/* FALLTHROUGH */
			case '\\':
				pairs++;
				cp++;
				break;
			case ' ':
				/* Skip escaped blanks. */
				if (0 == quoted)
					cp++;
				break;
			default:
				break;
			}
		} else if (0 == quoted) {
			if (' ' == cp[0]) {
				/* Unescaped blanks end unquoted args. */
				white = 1;
				break;
			}
		} else if ('"' == cp[0]) {
			if ('"' == cp[1]) {
				/* Quoted quotes collapse. */
				pairs++;
				cp++;
			} else {
				/* Unquoted quotes end quoted args. */
				quoted = 2;
				break;
			}
		}
	}

	/* Quoted argument without a closing quote. */
	if (1 == quoted)
		mandoc_msg(MANDOCERR_ARG_QUOTE, parse, ln, *pos, NULL);

	/* NUL-terminate this argument and move to the next one. */
	if (pairs)
		cp[-pairs] = '\0';
	if ('\0' != *cp) {
		*cp++ = '\0';
		while (' ' == *cp)
			cp++;
	}
	*pos += (int)(cp - start) + (quoted ? 1 : 0);
	*cpp = cp;

	if ('\0' == *cp && (white || ' ' == cp[-1]))
		mandoc_msg(MANDOCERR_SPACE_EOL, parse, ln, *pos, NULL);

	return start;
}

static int
a2time(time_t *t, const char *fmt, const char *p)
{
	struct tm	 tm;
	char		*pp;

	memset(&tm, 0, sizeof(struct tm));

	pp = NULL;
#if HAVE_STRPTIME
	pp = strptime(p, fmt, &tm);
#endif
	if (NULL != pp && '\0' == *pp) {
		*t = mktime(&tm);
		return 1;
	}

	return 0;
}

static char *
time2a(time_t t)
{
	struct tm	*tm;
	char		*buf, *p;
	size_t		 ssz;
	int		 isz;

	tm = localtime(&t);
	if (tm == NULL)
		return NULL;

	/*
	 * Reserve space:
	 * up to 9 characters for the month (September) + blank
	 * up to 2 characters for the day + comma + blank
	 * 4 characters for the year and a terminating '\0'
	 */

	p = buf = mandoc_malloc(10 + 4 + 4 + 1);

	if ((ssz = strftime(p, 10 + 1, "%B ", tm)) == 0)
		goto fail;
	p += (int)ssz;

	/*
	 * The output format is just "%d" here, not "%2d" or "%02d".
	 * That's also the reason why we can't just format the
	 * date as a whole with "%B %e, %Y" or "%B %d, %Y".
	 * Besides, the present approach is less prone to buffer
	 * overflows, in case anybody should ever introduce the bug
	 * of looking at LC_TIME.
	 */

	if ((isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday)) == -1)
		goto fail;
	p += isz;

	if (strftime(p, 4 + 1, "%Y", tm) == 0)
		goto fail;
	return buf;

fail:
	free(buf);
	return NULL;
}

char *
mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
{
	time_t		 t;

	/* No date specified: use today's date. */

	if (in == NULL || *in == '\0' || strcmp(in, "$" "Mdocdate$") == 0) {
		mandoc_msg(MANDOCERR_DATE_MISSING, parse, ln, pos, NULL);
		return time2a(time(NULL));
	}

	/* Valid mdoc(7) date format. */

	if (a2time(&t, "$" "Mdocdate: %b %d %Y $", in) ||
	    a2time(&t, "%b %d, %Y", in))
		return time2a(t);

	/* Do not warn about the legacy man(7) format. */

	if ( ! a2time(&t, "%Y-%m-%d", in))
		mandoc_msg(MANDOCERR_DATE_BAD, parse, ln, pos, in);

	/* Use any non-mdoc(7) date verbatim. */

	return mandoc_strdup(in);
}

int
mandoc_eos(const char *p, size_t sz)
{
	const char	*q;
	int		 enclosed, found;

	if (0 == sz)
		return 0;

	/*
	 * End-of-sentence recognition must include situations where
	 * some symbols, such as `)', allow prior EOS punctuation to
	 * propagate outward.
	 */

	enclosed = found = 0;
	for (q = p + (int)sz - 1; q >= p; q--) {
		switch (*q) {
		case '\"':
		case '\'':
		case ']':
		case ')':
			if (0 == found)
				enclosed = 1;
			break;
		case '.':
		case '!':
		case '?':
			found = 1;
			break;
		default:
			return found &&
			    (!enclosed || isalnum((unsigned char)*q));
		}
	}

	return found && !enclosed;
}

/*
 * Convert a string to a long that may not be <0.
 * If the string is invalid, or is less than 0, return -1.
 */
int
mandoc_strntoi(const char *p, size_t sz, int base)
{
	char		 buf[32];
	char		*ep;
	long		 v;

	if (sz > 31)
		return -1;

	memcpy(buf, p, sz);
	buf[(int)sz] = '\0';

	errno = 0;
	v = strtol(buf, &ep, base);

	if (buf[0] == '\0' || *ep != '\0')
		return -1;

	if (v > INT_MAX)
		v = INT_MAX;
	if (v < INT_MIN)
		v = INT_MIN;

	return (int)v;
}