Annotation of mandoc/mandoc.c, Revision 1.119
1.119 ! schwarze 1: /* $Id: mandoc.c,v 1.118 2020/10/24 22:57:39 schwarze Exp $ */
1.1 kristaps 2: /*
1.90 schwarze 3: * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
1.119 ! schwarze 4: * Copyright (c) 2011-2015, 2017-2021 Ingo Schwarze <schwarze@openbsd.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
1.36 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.1 kristaps 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.36 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.1 kristaps 13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
1.9 kristaps 18: #include "config.h"
1.7 kristaps 19:
1.2 kristaps 20: #include <sys/types.h>
21:
1.1 kristaps 22: #include <assert.h>
23: #include <ctype.h>
1.50 kristaps 24: #include <errno.h>
25: #include <limits.h>
1.1 kristaps 26: #include <stdlib.h>
1.4 kristaps 27: #include <stdio.h>
28: #include <string.h>
1.7 kristaps 29: #include <time.h>
1.1 kristaps 30:
1.101 schwarze 31: #include "mandoc_aux.h"
1.18 kristaps 32: #include "mandoc.h"
1.101 schwarze 33: #include "roff.h"
1.1 kristaps 34: #include "libmandoc.h"
1.114 schwarze 35: #include "roff_int.h"
1.37 schwarze 36:
1.18 kristaps 37: static int a2time(time_t *, const char *, const char *);
1.37 schwarze 38: static char *time2a(time_t);
1.7 kristaps 39:
1.45 kristaps 40:
41: enum mandoc_esc
1.112 schwarze 42: mandoc_font(const char *cp, int sz)
43: {
44: switch (sz) {
45: case 0:
46: return ESCAPE_FONTPREV;
47: case 1:
48: switch (cp[0]) {
49: case 'B':
50: case '3':
51: return ESCAPE_FONTBOLD;
52: case 'I':
53: case '2':
54: return ESCAPE_FONTITALIC;
55: case 'P':
56: return ESCAPE_FONTPREV;
57: case 'R':
58: case '1':
59: return ESCAPE_FONTROMAN;
60: case '4':
61: return ESCAPE_FONTBI;
62: default:
63: return ESCAPE_ERROR;
64: }
65: case 2:
66: switch (cp[0]) {
67: case 'B':
68: switch (cp[1]) {
69: case 'I':
70: return ESCAPE_FONTBI;
71: default:
72: return ESCAPE_ERROR;
73: }
74: case 'C':
75: switch (cp[1]) {
76: case 'B':
1.119 ! schwarze 77: return ESCAPE_FONTCB;
1.112 schwarze 78: case 'I':
1.119 ! schwarze 79: return ESCAPE_FONTCI;
1.112 schwarze 80: case 'R':
81: case 'W':
1.119 ! schwarze 82: return ESCAPE_FONTCR;
1.112 schwarze 83: default:
84: return ESCAPE_ERROR;
85: }
86: default:
87: return ESCAPE_ERROR;
88: }
89: default:
90: return ESCAPE_ERROR;
91: }
92: }
93:
94: enum mandoc_esc
1.74 schwarze 95: mandoc_escape(const char **end, const char **start, int *sz)
1.1 kristaps 96: {
1.65 schwarze 97: const char *local_start;
1.105 schwarze 98: int local_sz, c, i;
1.65 schwarze 99: char term;
1.79 schwarze 100: enum mandoc_esc gly;
1.45 kristaps 101:
1.65 schwarze 102: /*
103: * When the caller doesn't provide return storage,
104: * use local storage.
105: */
106:
107: if (NULL == start)
108: start = &local_start;
109: if (NULL == sz)
110: sz = &local_sz;
111:
112: /*
1.111 schwarze 113: * Treat "\E" just like "\";
114: * it only makes a difference in copy mode.
115: */
116:
117: if (**end == 'E')
118: ++*end;
119:
120: /*
1.65 schwarze 121: * Beyond the backslash, at least one input character
122: * is part of the escape sequence. With one exception
123: * (see below), that character won't be returned.
124: */
125:
1.45 kristaps 126: gly = ESCAPE_ERROR;
1.65 schwarze 127: *start = ++*end;
128: *sz = 0;
1.64 schwarze 129: term = '\0';
1.18 kristaps 130:
1.65 schwarze 131: switch ((*start)[-1]) {
1.45 kristaps 132: /*
133: * First the glyphs. There are several different forms of
134: * these, but each eventually returns a substring of the glyph
135: * name.
136: */
1.79 schwarze 137: case '(':
1.45 kristaps 138: gly = ESCAPE_SPECIAL;
1.65 schwarze 139: *sz = 2;
1.45 kristaps 140: break;
1.79 schwarze 141: case '[':
1.111 schwarze 142: if (**start == ' ') {
143: ++*end;
144: return ESCAPE_ERROR;
145: }
1.45 kristaps 146: gly = ESCAPE_SPECIAL;
147: term = ']';
148: break;
1.79 schwarze 149: case 'C':
1.65 schwarze 150: if ('\'' != **start)
1.94 schwarze 151: return ESCAPE_ERROR;
1.65 schwarze 152: *start = ++*end;
1.87 schwarze 153: gly = ESCAPE_SPECIAL;
1.45 kristaps 154: term = '\'';
155: break;
1.72 schwarze 156:
157: /*
158: * Escapes taking no arguments at all.
159: */
1.111 schwarze 160: case '!':
161: case '?':
162: return ESCAPE_UNSUPP;
163: case '%':
164: case '&':
165: case ')':
166: case ',':
167: case '/':
168: case '^':
169: case 'a':
1.79 schwarze 170: case 'd':
1.111 schwarze 171: case 'r':
172: case 't':
1.79 schwarze 173: case 'u':
1.111 schwarze 174: case '{':
175: case '|':
176: case '}':
1.94 schwarze 177: return ESCAPE_IGNORE;
1.111 schwarze 178: case 'c':
179: return ESCAPE_NOSPACE;
1.102 schwarze 180: case 'p':
181: return ESCAPE_BREAK;
1.63 schwarze 182:
183: /*
184: * The \z escape is supposed to output the following
1.79 schwarze 185: * character without advancing the cursor position.
1.63 schwarze 186: * Since we are mostly dealing with terminal mode,
187: * let us just skip the next character.
188: */
1.79 schwarze 189: case 'z':
1.94 schwarze 190: return ESCAPE_SKIPCHAR;
1.1 kristaps 191:
1.45 kristaps 192: /*
193: * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
194: * 'X' is the trigger. These have opaque sub-strings.
195: */
1.79 schwarze 196: case 'F':
1.111 schwarze 197: case 'f':
1.79 schwarze 198: case 'g':
199: case 'k':
200: case 'M':
201: case 'm':
202: case 'n':
1.111 schwarze 203: case 'O':
1.79 schwarze 204: case 'V':
205: case 'Y':
1.118 schwarze 206: case '*':
207: switch ((*start)[-1]) {
208: case 'f':
209: gly = ESCAPE_FONT;
210: break;
211: case '*':
212: gly = ESCAPE_DEVICE;
213: break;
214: default:
215: gly = ESCAPE_IGNORE;
216: break;
217: }
1.65 schwarze 218: switch (**start) {
1.79 schwarze 219: case '(':
1.111 schwarze 220: if ((*start)[-1] == 'O')
221: gly = ESCAPE_ERROR;
1.65 schwarze 222: *start = ++*end;
223: *sz = 2;
1.45 kristaps 224: break;
1.79 schwarze 225: case '[':
1.111 schwarze 226: if ((*start)[-1] == 'O')
227: gly = (*start)[1] == '5' ?
228: ESCAPE_UNSUPP : ESCAPE_ERROR;
1.65 schwarze 229: *start = ++*end;
1.45 kristaps 230: term = ']';
231: break;
232: default:
1.111 schwarze 233: if ((*start)[-1] == 'O') {
234: switch (**start) {
235: case '0':
236: gly = ESCAPE_UNSUPP;
237: break;
238: case '1':
239: case '2':
240: case '3':
241: case '4':
242: break;
243: default:
244: gly = ESCAPE_ERROR;
245: break;
246: }
247: }
1.65 schwarze 248: *sz = 1;
1.45 kristaps 249: break;
250: }
1.106 schwarze 251: break;
1.45 kristaps 252:
253: /*
254: * These escapes are of the form \X'Y', where 'X' is the trigger
255: * and 'Y' is any string. These have opaque sub-strings.
1.78 schwarze 256: * The \B and \w escapes are handled in roff.c, roff_res().
1.45 kristaps 257: */
1.79 schwarze 258: case 'A':
259: case 'b':
260: case 'D':
261: case 'R':
262: case 'X':
263: case 'Z':
1.91 schwarze 264: gly = ESCAPE_IGNORE;
265: /* FALLTHROUGH */
266: case 'o':
267: if (**start == '\0')
1.94 schwarze 268: return ESCAPE_ERROR;
1.91 schwarze 269: if (gly == ESCAPE_ERROR)
270: gly = ESCAPE_OVERSTRIKE;
1.77 schwarze 271: term = **start;
1.65 schwarze 272: *start = ++*end;
1.24 kristaps 273: break;
1.45 kristaps 274:
275: /*
276: * These escapes are of the form \X'N', where 'X' is the trigger
277: * and 'N' resolves to a numerical expression.
278: */
1.79 schwarze 279: case 'h':
280: case 'H':
281: case 'L':
282: case 'l':
283: case 'S':
284: case 'v':
285: case 'x':
1.82 schwarze 286: if (strchr(" %&()*+-./0123456789:<=>", **start)) {
1.86 kristaps 287: if ('\0' != **start)
288: ++*end;
1.94 schwarze 289: return ESCAPE_ERROR;
1.82 schwarze 290: }
1.100 schwarze 291: switch ((*start)[-1]) {
292: case 'h':
293: gly = ESCAPE_HORIZ;
294: break;
295: case 'l':
296: gly = ESCAPE_HLINE;
297: break;
298: default:
299: gly = ESCAPE_IGNORE;
300: break;
301: }
1.77 schwarze 302: term = **start;
1.65 schwarze 303: *start = ++*end;
1.45 kristaps 304: break;
1.60 schwarze 305:
306: /*
307: * Special handling for the numbered character escape.
308: * XXX Do any other escapes need similar handling?
309: */
1.79 schwarze 310: case 'N':
1.65 schwarze 311: if ('\0' == **start)
1.94 schwarze 312: return ESCAPE_ERROR;
1.65 schwarze 313: (*end)++;
314: if (isdigit((unsigned char)**start)) {
315: *sz = 1;
1.94 schwarze 316: return ESCAPE_IGNORE;
1.65 schwarze 317: }
318: (*start)++;
1.60 schwarze 319: while (isdigit((unsigned char)**end))
320: (*end)++;
1.65 schwarze 321: *sz = *end - *start;
1.60 schwarze 322: if ('\0' != **end)
323: (*end)++;
1.94 schwarze 324: return ESCAPE_NUMBERED;
1.45 kristaps 325:
1.79 schwarze 326: /*
1.45 kristaps 327: * Sizes get a special category of their own.
328: */
1.79 schwarze 329: case 's':
1.45 kristaps 330: gly = ESCAPE_IGNORE;
1.28 kristaps 331:
1.45 kristaps 332: /* See +/- counts as a sign. */
1.65 schwarze 333: if ('+' == **end || '-' == **end || ASCII_HYPH == **end)
1.90 schwarze 334: *start = ++*end;
1.8 kristaps 335:
1.65 schwarze 336: switch (**end) {
1.79 schwarze 337: case '(':
1.65 schwarze 338: *start = ++*end;
339: *sz = 2;
1.22 kristaps 340: break;
1.79 schwarze 341: case '[':
1.65 schwarze 342: *start = ++*end;
1.64 schwarze 343: term = ']';
1.22 kristaps 344: break;
1.79 schwarze 345: case '\'':
1.65 schwarze 346: *start = ++*end;
1.64 schwarze 347: term = '\'';
1.92 schwarze 348: break;
349: case '3':
350: case '2':
351: case '1':
352: *sz = (*end)[-1] == 's' &&
353: isdigit((unsigned char)(*end)[1]) ? 2 : 1;
1.22 kristaps 354: break;
355: default:
1.65 schwarze 356: *sz = 1;
1.22 kristaps 357: break;
1.8 kristaps 358: }
359:
1.45 kristaps 360: break;
1.33 kristaps 361:
1.45 kristaps 362: /*
1.111 schwarze 363: * Several special characters can be encoded as
364: * one-byte escape sequences without using \[].
1.45 kristaps 365: */
1.111 schwarze 366: case ' ':
367: case '\'':
368: case '-':
369: case '.':
370: case '0':
371: case ':':
372: case '_':
373: case '`':
374: case 'e':
375: case '~':
376: gly = ESCAPE_SPECIAL;
377: /* FALLTHROUGH */
1.45 kristaps 378: default:
1.111 schwarze 379: if (gly == ESCAPE_ERROR)
380: gly = ESCAPE_UNDEF;
1.65 schwarze 381: *start = --*end;
382: *sz = 1;
1.22 kristaps 383: break;
1.45 kristaps 384: }
385:
386: /*
1.64 schwarze 387: * Read up to the terminating character,
388: * paying attention to nested escapes.
1.45 kristaps 389: */
390:
391: if ('\0' != term) {
1.64 schwarze 392: while (**end != term) {
393: switch (**end) {
1.79 schwarze 394: case '\0':
1.94 schwarze 395: return ESCAPE_ERROR;
1.79 schwarze 396: case '\\':
1.64 schwarze 397: (*end)++;
398: if (ESCAPE_ERROR ==
399: mandoc_escape(end, NULL, NULL))
1.94 schwarze 400: return ESCAPE_ERROR;
1.64 schwarze 401: break;
402: default:
403: (*end)++;
404: break;
405: }
406: }
1.65 schwarze 407: *sz = (*end)++ - *start;
1.111 schwarze 408:
409: /*
410: * The file chars.c only provides one common list
411: * of character names, but \[-] == \- is the only
412: * one of the characters with one-byte names that
413: * allows enclosing the name in brackets.
414: */
415: if (gly == ESCAPE_SPECIAL && *sz == 1 && **start != '-')
416: return ESCAPE_ERROR;
1.64 schwarze 417: } else {
1.65 schwarze 418: assert(*sz > 0);
419: if ((size_t)*sz > strlen(*start))
1.94 schwarze 420: return ESCAPE_ERROR;
1.65 schwarze 421: *end += *sz;
1.45 kristaps 422: }
423:
424: /* Run post-processors. */
425:
426: switch (gly) {
1.79 schwarze 427: case ESCAPE_FONT:
1.112 schwarze 428: gly = mandoc_font(*start, *sz);
1.46 kristaps 429: break;
1.79 schwarze 430: case ESCAPE_SPECIAL:
1.105 schwarze 431: if (**start == 'c') {
432: if (*sz < 6 || *sz > 7 ||
433: strncmp(*start, "char", 4) != 0 ||
434: (int)strspn(*start + 4, "0123456789") + 4 < *sz)
435: break;
436: c = 0;
437: for (i = 4; i < *sz; i++)
438: c = 10 * c + ((*start)[i] - '0');
439: if (c < 0x21 || (c > 0x7e && c < 0xa0) || c > 0xff)
440: break;
441: *start += 4;
442: *sz -= 4;
443: gly = ESCAPE_NUMBERED;
444: break;
445: }
446:
1.87 schwarze 447: /*
1.88 schwarze 448: * Unicode escapes are defined in groff as \[u0000]
1.87 schwarze 449: * to \[u10FFFF], where the contained value must be
450: * a valid Unicode codepoint. Here, however, only
1.88 schwarze 451: * check the length and range.
1.87 schwarze 452: */
1.88 schwarze 453: if (**start != 'u' || *sz < 5 || *sz > 7)
454: break;
455: if (*sz == 7 && ((*start)[1] != '1' || (*start)[2] != '0'))
456: break;
457: if (*sz == 6 && (*start)[1] == '0')
1.96 schwarze 458: break;
459: if (*sz == 5 && (*start)[1] == 'D' &&
460: strchr("89ABCDEF", (*start)[2]) != NULL)
1.88 schwarze 461: break;
462: if ((int)strspn(*start + 1, "0123456789ABCDEFabcdef")
1.87 schwarze 463: + 1 == *sz)
464: gly = ESCAPE_UNICODE;
1.118 schwarze 465: break;
466: case ESCAPE_DEVICE:
467: assert(*sz == 2 && (*start)[0] == '.' && (*start)[1] == 'T');
1.22 kristaps 468: break;
1.1 kristaps 469: default:
1.22 kristaps 470: break;
1.1 kristaps 471: }
472:
1.94 schwarze 473: return gly;
1.4 kristaps 474: }
1.7 kristaps 475:
476: static int
477: a2time(time_t *t, const char *fmt, const char *p)
478: {
479: struct tm tm;
480: char *pp;
481:
482: memset(&tm, 0, sizeof(struct tm));
483:
1.56 kristaps 484: pp = NULL;
1.85 schwarze 485: #if HAVE_STRPTIME
1.7 kristaps 486: pp = strptime(p, fmt, &tm);
1.56 kristaps 487: #endif
1.7 kristaps 488: if (NULL != pp && '\0' == *pp) {
489: *t = mktime(&tm);
1.94 schwarze 490: return 1;
1.7 kristaps 491: }
492:
1.94 schwarze 493: return 0;
1.7 kristaps 494: }
495:
1.37 schwarze 496: static char *
497: time2a(time_t t)
498: {
1.56 kristaps 499: struct tm *tm;
1.38 schwarze 500: char *buf, *p;
501: size_t ssz;
1.37 schwarze 502: int isz;
503:
1.116 schwarze 504: buf = NULL;
1.56 kristaps 505: tm = localtime(&t);
1.89 schwarze 506: if (tm == NULL)
1.116 schwarze 507: goto fail;
1.37 schwarze 508:
1.38 schwarze 509: /*
510: * Reserve space:
511: * up to 9 characters for the month (September) + blank
512: * up to 2 characters for the day + comma + blank
513: * 4 characters for the year and a terminating '\0'
514: */
1.98 schwarze 515:
1.38 schwarze 516: p = buf = mandoc_malloc(10 + 4 + 4 + 1);
517:
1.98 schwarze 518: if ((ssz = strftime(p, 10 + 1, "%B ", tm)) == 0)
1.38 schwarze 519: goto fail;
520: p += (int)ssz;
1.37 schwarze 521:
1.98 schwarze 522: /*
523: * The output format is just "%d" here, not "%2d" or "%02d".
524: * That's also the reason why we can't just format the
525: * date as a whole with "%B %e, %Y" or "%B %d, %Y".
526: * Besides, the present approach is less prone to buffer
527: * overflows, in case anybody should ever introduce the bug
528: * of looking at LC_TIME.
529: */
530:
1.116 schwarze 531: isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday);
532: if (isz < 0 || isz > 4)
1.38 schwarze 533: goto fail;
1.37 schwarze 534: p += isz;
535:
1.98 schwarze 536: if (strftime(p, 4 + 1, "%Y", tm) == 0)
1.38 schwarze 537: goto fail;
1.94 schwarze 538: return buf;
1.38 schwarze 539:
540: fail:
541: free(buf);
1.116 schwarze 542: return mandoc_strdup("");
1.37 schwarze 543: }
544:
545: char *
1.117 schwarze 546: mandoc_normdate(struct roff_node *nch, struct roff_node *nbl)
1.7 kristaps 547: {
1.103 schwarze 548: char *cp;
1.7 kristaps 549: time_t t;
1.116 schwarze 550:
1.117 schwarze 551: /* No date specified. */
1.7 kristaps 552:
1.117 schwarze 553: if (nch == NULL) {
554: if (nbl == NULL)
555: mandoc_msg(MANDOCERR_DATE_MISSING, 0, 0, NULL);
556: else
557: mandoc_msg(MANDOCERR_DATE_MISSING, nbl->line,
558: nbl->pos, "%s", roff_name[nbl->tok]);
559: return mandoc_strdup("");
560: }
561: if (*nch->string == '\0') {
562: mandoc_msg(MANDOCERR_DATE_MISSING, nch->line,
563: nch->pos, "%s", roff_name[nbl->tok]);
564: return mandoc_strdup("");
565: }
566: if (strcmp(nch->string, "$" "Mdocdate$") == 0)
1.98 schwarze 567: return time2a(time(NULL));
568:
569: /* Valid mdoc(7) date format. */
570:
1.117 schwarze 571: if (a2time(&t, "$" "Mdocdate: %b %d %Y $", nch->string) ||
572: a2time(&t, "%b %d, %Y", nch->string)) {
1.103 schwarze 573: cp = time2a(t);
574: if (t > time(NULL) + 86400)
1.117 schwarze 575: mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line,
576: nch->pos, "%s %s", roff_name[nbl->tok], cp);
577: else if (*nch->string != '$' &&
578: strcmp(nch->string, cp) != 0)
579: mandoc_msg(MANDOCERR_DATE_NORM, nch->line,
580: nch->pos, "%s %s", roff_name[nbl->tok], cp);
1.103 schwarze 581: return cp;
582: }
1.98 schwarze 583:
1.101 schwarze 584: /* In man(7), do not warn about the legacy format. */
1.98 schwarze 585:
1.117 schwarze 586: if (a2time(&t, "%Y-%m-%d", nch->string) == 0)
587: mandoc_msg(MANDOCERR_DATE_BAD, nch->line, nch->pos,
588: "%s %s", roff_name[nbl->tok], nch->string);
1.103 schwarze 589: else if (t > time(NULL) + 86400)
1.117 schwarze 590: mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line, nch->pos,
591: "%s %s", roff_name[nbl->tok], nch->string);
592: else if (nbl->tok == MDOC_Dd)
593: mandoc_msg(MANDOCERR_DATE_LEGACY, nch->line, nch->pos,
594: "Dd %s", nch->string);
1.98 schwarze 595:
596: /* Use any non-mdoc(7) date verbatim. */
597:
1.117 schwarze 598: return mandoc_strdup(nch->string);
1.7 kristaps 599: }
600:
1.12 kristaps 601: int
1.75 schwarze 602: mandoc_eos(const char *p, size_t sz)
1.12 kristaps 603: {
1.75 schwarze 604: const char *q;
605: int enclosed, found;
1.12 kristaps 606:
1.13 kristaps 607: if (0 == sz)
1.94 schwarze 608: return 0;
1.12 kristaps 609:
1.14 kristaps 610: /*
611: * End-of-sentence recognition must include situations where
612: * some symbols, such as `)', allow prior EOS punctuation to
1.49 kristaps 613: * propagate outward.
1.14 kristaps 614: */
615:
1.75 schwarze 616: enclosed = found = 0;
1.25 kristaps 617: for (q = p + (int)sz - 1; q >= p; q--) {
1.23 schwarze 618: switch (*q) {
1.79 schwarze 619: case '\"':
620: case '\'':
621: case ']':
622: case ')':
1.23 schwarze 623: if (0 == found)
624: enclosed = 1;
1.14 kristaps 625: break;
1.79 schwarze 626: case '.':
627: case '!':
628: case '?':
1.23 schwarze 629: found = 1;
630: break;
1.14 kristaps 631: default:
1.94 schwarze 632: return found &&
633: (!enclosed || isalnum((unsigned char)*q));
1.14 kristaps 634: }
1.12 kristaps 635: }
636:
1.94 schwarze 637: return found && !enclosed;
1.44 kristaps 638: }
1.50 kristaps 639:
640: /*
641: * Convert a string to a long that may not be <0.
642: * If the string is invalid, or is less than 0, return -1.
643: */
644: int
1.54 kristaps 645: mandoc_strntoi(const char *p, size_t sz, int base)
1.50 kristaps 646: {
647: char buf[32];
648: char *ep;
649: long v;
650:
651: if (sz > 31)
1.94 schwarze 652: return -1;
1.50 kristaps 653:
654: memcpy(buf, p, sz);
1.51 kristaps 655: buf[(int)sz] = '\0';
1.50 kristaps 656:
657: errno = 0;
658: v = strtol(buf, &ep, base);
659:
660: if (buf[0] == '\0' || *ep != '\0')
1.94 schwarze 661: return -1;
1.50 kristaps 662:
1.54 kristaps 663: if (v > INT_MAX)
664: v = INT_MAX;
665: if (v < INT_MIN)
666: v = INT_MIN;
1.50 kristaps 667:
1.94 schwarze 668: return (int)v;
1.50 kristaps 669: }
CVSweb