Annotation of src/usr.bin/mandoc/man_validate.c, Revision 1.76
1.76 ! schwarze 1: /* $Id: man_validate.c,v 1.75 2014/08/08 15:57:05 schwarze Exp $ */
1.1 kristaps 2: /*
1.38 schwarze 3: * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.60 schwarze 4: * Copyright (c) 2010, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
1.2 schwarze 7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
1.1 kristaps 9: *
1.2 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
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.
1.1 kristaps 17: */
18: #include <sys/types.h>
19:
20: #include <assert.h>
21: #include <ctype.h>
1.6 schwarze 22: #include <errno.h>
23: #include <limits.h>
1.1 kristaps 24: #include <stdarg.h>
25: #include <stdlib.h>
1.28 schwarze 26: #include <string.h>
1.36 schwarze 27: #include <time.h>
1.1 kristaps 28:
1.44 schwarze 29: #include "man.h"
1.25 schwarze 30: #include "mandoc.h"
1.61 schwarze 31: #include "mandoc_aux.h"
1.1 kristaps 32: #include "libman.h"
1.5 schwarze 33: #include "libmandoc.h"
1.1 kristaps 34:
1.57 schwarze 35: #define CHKARGS struct man *man, struct man_node *n
1.1 kristaps 36:
1.7 schwarze 37: typedef int (*v_check)(CHKARGS);
1.1 kristaps 38:
1.7 schwarze 39: static int check_eq0(CHKARGS);
1.52 schwarze 40: static int check_eq2(CHKARGS);
1.10 schwarze 41: static int check_le1(CHKARGS);
1.7 schwarze 42: static int check_ge2(CHKARGS);
43: static int check_le5(CHKARGS);
44: static int check_par(CHKARGS);
1.8 schwarze 45: static int check_part(CHKARGS);
1.7 schwarze 46: static int check_root(CHKARGS);
1.76 ! schwarze 47: static int check_text(CHKARGS);
1.7 schwarze 48:
1.34 schwarze 49: static int post_AT(CHKARGS);
1.55 schwarze 50: static int post_IP(CHKARGS);
1.46 schwarze 51: static int post_vs(CHKARGS);
1.34 schwarze 52: static int post_fi(CHKARGS);
1.47 schwarze 53: static int post_ft(CHKARGS);
1.34 schwarze 54: static int post_nf(CHKARGS);
55: static int post_TH(CHKARGS);
56: static int post_UC(CHKARGS);
1.76 ! schwarze 57: static int post_UR(CHKARGS);
1.34 schwarze 58:
1.76 ! schwarze 59: static v_check man_valids[MAN_MAX] = {
! 60: post_vs, /* br */
! 61: post_TH, /* TH */
! 62: NULL, /* SH */
! 63: NULL, /* SS */
! 64: NULL, /* TP */
! 65: check_par, /* LP */
! 66: check_par, /* PP */
! 67: check_par, /* P */
! 68: post_IP, /* IP */
! 69: NULL, /* HP */
! 70: NULL, /* SM */
! 71: NULL, /* SB */
! 72: NULL, /* BI */
! 73: NULL, /* IB */
! 74: NULL, /* BR */
! 75: NULL, /* RB */
! 76: NULL, /* R */
! 77: NULL, /* B */
! 78: NULL, /* I */
! 79: NULL, /* IR */
! 80: NULL, /* RI */
! 81: check_eq0, /* na */
! 82: post_vs, /* sp */
! 83: post_nf, /* nf */
! 84: post_fi, /* fi */
! 85: NULL, /* RE */
! 86: check_part, /* RS */
! 87: NULL, /* DT */
! 88: post_UC, /* UC */
! 89: check_le1, /* PD */
! 90: post_AT, /* AT */
! 91: NULL, /* in */
! 92: post_ft, /* ft */
! 93: check_eq2, /* OP */
! 94: post_nf, /* EX */
! 95: post_fi, /* EE */
! 96: post_UR, /* UR */
! 97: NULL, /* UE */
! 98: NULL, /* ll */
1.1 kristaps 99: };
100:
101:
102: int
1.57 schwarze 103: man_valid_post(struct man *man)
1.1 kristaps 104: {
1.76 ! schwarze 105: struct man_node *n;
1.7 schwarze 106: v_check *cp;
1.1 kristaps 107:
1.76 ! schwarze 108: n = man->last;
! 109: if (n->flags & MAN_VALID)
1.1 kristaps 110: return(1);
1.76 ! schwarze 111: n->flags |= MAN_VALID;
1.1 kristaps 112:
1.76 ! schwarze 113: switch (n->type) {
1.63 schwarze 114: case MAN_TEXT:
1.76 ! schwarze 115: return(check_text(man, n));
1.63 schwarze 116: case MAN_ROOT:
1.76 ! schwarze 117: return(check_root(man, n));
1.63 schwarze 118: case MAN_EQN:
1.42 schwarze 119: /* FALLTHROUGH */
1.63 schwarze 120: case MAN_TBL:
1.38 schwarze 121: return(1);
1.1 kristaps 122: default:
1.76 ! schwarze 123: cp = man_valids + n->tok;
! 124: return(*cp ? (*cp)(man, n) : 1);
1.1 kristaps 125: }
126: }
127:
1.4 schwarze 128: static int
1.63 schwarze 129: check_root(CHKARGS)
1.4 schwarze 130: {
1.7 schwarze 131:
1.73 schwarze 132: assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
1.7 schwarze 133:
1.65 schwarze 134: if (NULL == man->first->child)
1.75 schwarze 135: mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
136: n->line, n->pos, NULL);
1.65 schwarze 137: else
138: man->meta.hasbody = 1;
139:
140: if (NULL == man->meta.title) {
1.75 schwarze 141: mandoc_msg(MANDOCERR_TH_MISSING, man->parse,
142: n->line, n->pos, NULL);
1.35 schwarze 143:
1.18 schwarze 144: /*
145: * If a title hasn't been set, do so now (by
146: * implication, date and section also aren't set).
147: */
1.35 schwarze 148:
1.63 schwarze 149: man->meta.title = mandoc_strdup("unknown");
1.57 schwarze 150: man->meta.msec = mandoc_strdup("1");
1.60 schwarze 151: man->meta.date = man->quick ? mandoc_strdup("") :
152: mandoc_normdate(man->parse, NULL, n->line, n->pos);
1.17 schwarze 153: }
1.15 schwarze 154:
155: return(1);
156: }
157:
1.76 ! schwarze 158: static int
1.47 schwarze 159: check_text(CHKARGS)
1.4 schwarze 160: {
1.47 schwarze 161: char *cp, *p;
162:
1.57 schwarze 163: if (MAN_LITERAL & man->flags)
1.76 ! schwarze 164: return(1);
1.48 schwarze 165:
166: cp = n->string;
167: for (p = cp; NULL != (p = strchr(p, '\t')); p++)
1.71 schwarze 168: mandoc_msg(MANDOCERR_FI_TAB, man->parse,
169: n->line, n->pos + (p - cp), NULL);
1.76 ! schwarze 170: return(1);
1.1 kristaps 171: }
172:
173: #define INEQ_DEFINE(x, ineq, name) \
174: static int \
1.7 schwarze 175: check_##name(CHKARGS) \
1.1 kristaps 176: { \
1.4 schwarze 177: if (n->nchild ineq (x)) \
1.1 kristaps 178: return(1); \
1.57 schwarze 179: mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line, n->pos, \
1.63 schwarze 180: "line arguments %s %d (have %d)", \
181: #ineq, (x), n->nchild); \
1.40 schwarze 182: return(1); \
1.1 kristaps 183: }
184:
185: INEQ_DEFINE(0, ==, eq0)
1.52 schwarze 186: INEQ_DEFINE(2, ==, eq2)
1.10 schwarze 187: INEQ_DEFINE(1, <=, le1)
1.1 kristaps 188: INEQ_DEFINE(2, >=, ge2)
189: INEQ_DEFINE(5, <=, le5)
1.58 schwarze 190:
191: static int
1.76 ! schwarze 192: post_UR(CHKARGS)
1.58 schwarze 193: {
194:
195: if (MAN_HEAD == n->type && 1 != n->nchild)
196: mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
197: n->pos, "line arguments eq 1 (have %d)", n->nchild);
198:
1.76 ! schwarze 199: return(check_part(man, n));
1.58 schwarze 200: }
1.32 schwarze 201:
202: static int
1.47 schwarze 203: post_ft(CHKARGS)
1.32 schwarze 204: {
205: char *cp;
206: int ok;
207:
208: if (0 == n->nchild)
209: return(1);
210:
211: ok = 0;
212: cp = n->child->string;
213: switch (*cp) {
1.63 schwarze 214: case '1':
1.32 schwarze 215: /* FALLTHROUGH */
1.63 schwarze 216: case '2':
1.32 schwarze 217: /* FALLTHROUGH */
1.63 schwarze 218: case '3':
1.32 schwarze 219: /* FALLTHROUGH */
1.63 schwarze 220: case '4':
1.32 schwarze 221: /* FALLTHROUGH */
1.63 schwarze 222: case 'I':
1.32 schwarze 223: /* FALLTHROUGH */
1.63 schwarze 224: case 'P':
1.32 schwarze 225: /* FALLTHROUGH */
1.63 schwarze 226: case 'R':
1.32 schwarze 227: if ('\0' == cp[1])
228: ok = 1;
229: break;
1.63 schwarze 230: case 'B':
1.32 schwarze 231: if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
232: ok = 1;
233: break;
1.63 schwarze 234: case 'C':
1.32 schwarze 235: if ('W' == cp[1] && '\0' == cp[2])
236: ok = 1;
237: break;
238: default:
239: break;
240: }
241:
242: if (0 == ok) {
1.70 schwarze 243: mandoc_vmsg(MANDOCERR_FT_BAD, man->parse,
244: n->line, n->pos, "ft %s", cp);
1.32 schwarze 245: *cp = '\0';
246: }
247:
248: if (1 < n->nchild)
1.63 schwarze 249: mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
250: n->pos, "want one child (have %d)", n->nchild);
1.32 schwarze 251:
252: return(1);
253: }
1.7 schwarze 254:
255: static int
1.8 schwarze 256: check_part(CHKARGS)
257: {
258:
259: if (MAN_BODY == n->type && 0 == n->nchild)
1.63 schwarze 260: mandoc_msg(MANDOCERR_ARGCWARN, man->parse, n->line,
261: n->pos, "want children (have none)");
1.35 schwarze 262:
1.8 schwarze 263: return(1);
264: }
265:
1.36 schwarze 266: static int
267: check_par(CHKARGS)
268: {
269:
1.39 schwarze 270: switch (n->type) {
1.63 schwarze 271: case MAN_BLOCK:
1.39 schwarze 272: if (0 == n->body->nchild)
1.57 schwarze 273: man_node_delete(man, n);
1.39 schwarze 274: break;
1.63 schwarze 275: case MAN_BODY:
1.39 schwarze 276: if (0 == n->nchild)
1.67 schwarze 277: mandoc_vmsg(MANDOCERR_PAR_SKIP,
278: man->parse, n->line, n->pos,
279: "%s empty", man_macronames[n->tok]);
1.39 schwarze 280: break;
1.63 schwarze 281: case MAN_HEAD:
1.39 schwarze 282: if (n->nchild)
1.69 schwarze 283: mandoc_vmsg(MANDOCERR_ARG_SKIP,
284: man->parse, n->line, n->pos,
285: "%s %s%s", man_macronames[n->tok],
286: n->child->string,
287: n->nchild > 1 ? " ..." : "");
1.39 schwarze 288: break;
289: default:
290: break;
291: }
1.36 schwarze 292:
293: return(1);
294: }
295:
1.55 schwarze 296: static int
297: post_IP(CHKARGS)
298: {
299:
300: switch (n->type) {
1.63 schwarze 301: case MAN_BLOCK:
1.55 schwarze 302: if (0 == n->head->nchild && 0 == n->body->nchild)
1.57 schwarze 303: man_node_delete(man, n);
1.55 schwarze 304: break;
1.63 schwarze 305: case MAN_BODY:
1.55 schwarze 306: if (0 == n->parent->head->nchild && 0 == n->nchild)
1.67 schwarze 307: mandoc_vmsg(MANDOCERR_PAR_SKIP,
308: man->parse, n->line, n->pos,
309: "%s empty", man_macronames[n->tok]);
1.55 schwarze 310: break;
311: default:
312: break;
313: }
314: return(1);
315: }
1.36 schwarze 316:
1.34 schwarze 317: static int
318: post_TH(CHKARGS)
319: {
1.64 schwarze 320: struct man_node *nb;
1.40 schwarze 321: const char *p;
1.34 schwarze 322:
1.76 ! schwarze 323: check_ge2(man, n);
! 324: check_le5(man, n);
! 325:
1.57 schwarze 326: free(man->meta.title);
327: free(man->meta.vol);
328: free(man->meta.source);
329: free(man->meta.msec);
330: free(man->meta.date);
1.34 schwarze 331:
1.57 schwarze 332: man->meta.title = man->meta.vol = man->meta.date =
1.63 schwarze 333: man->meta.msec = man->meta.source = NULL;
1.34 schwarze 334:
1.64 schwarze 335: nb = n;
336:
1.34 schwarze 337: /* ->TITLE<- MSEC DATE SOURCE VOL */
338:
339: n = n->child;
1.40 schwarze 340: if (n && n->string) {
341: for (p = n->string; '\0' != *p; p++) {
342: /* Only warn about this once... */
1.63 schwarze 343: if (isalpha((unsigned char)*p) &&
344: ! isupper((unsigned char)*p)) {
1.74 schwarze 345: mandoc_vmsg(MANDOCERR_TITLE_CASE,
1.66 schwarze 346: man->parse, n->line,
347: n->pos + (p - n->string),
1.74 schwarze 348: "TH %s", n->string);
1.40 schwarze 349: break;
350: }
351: }
1.57 schwarze 352: man->meta.title = mandoc_strdup(n->string);
1.40 schwarze 353: } else
1.57 schwarze 354: man->meta.title = mandoc_strdup("");
1.34 schwarze 355:
356: /* TITLE ->MSEC<- DATE SOURCE VOL */
357:
1.40 schwarze 358: if (n)
359: n = n->next;
360: if (n && n->string)
1.57 schwarze 361: man->meta.msec = mandoc_strdup(n->string);
1.40 schwarze 362: else
1.57 schwarze 363: man->meta.msec = mandoc_strdup("");
1.34 schwarze 364:
365: /* TITLE MSEC ->DATE<- SOURCE VOL */
366:
1.40 schwarze 367: if (n)
368: n = n->next;
1.49 schwarze 369: if (n && n->string && '\0' != n->string[0]) {
1.60 schwarze 370: man->meta.date = man->quick ?
371: mandoc_strdup(n->string) :
372: mandoc_normdate(man->parse, n->string,
373: n->line, n->pos);
1.64 schwarze 374: } else {
1.57 schwarze 375: man->meta.date = mandoc_strdup("");
1.74 schwarze 376: mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
377: n ? n->line : nb->line,
378: n ? n->pos : nb->pos, "TH");
1.64 schwarze 379: }
1.34 schwarze 380:
381: /* TITLE MSEC DATE ->SOURCE<- VOL */
382:
383: if (n && (n = n->next))
1.57 schwarze 384: man->meta.source = mandoc_strdup(n->string);
1.34 schwarze 385:
386: /* TITLE MSEC DATE SOURCE ->VOL<- */
1.51 schwarze 387: /* If missing, use the default VOL name for MSEC. */
1.34 schwarze 388:
389: if (n && (n = n->next))
1.57 schwarze 390: man->meta.vol = mandoc_strdup(n->string);
391: else if ('\0' != man->meta.msec[0] &&
392: (NULL != (p = mandoc_a2msec(man->meta.msec))))
393: man->meta.vol = mandoc_strdup(p);
1.34 schwarze 394:
395: /*
396: * Remove the `TH' node after we've processed it for our
397: * meta-data.
398: */
1.57 schwarze 399: man_node_delete(man, man->last);
1.34 schwarze 400: return(1);
401: }
402:
403: static int
404: post_nf(CHKARGS)
405: {
406:
1.76 ! schwarze 407: check_eq0(man, n);
! 408:
1.57 schwarze 409: if (MAN_LITERAL & man->flags)
1.74 schwarze 410: mandoc_msg(MANDOCERR_NF_SKIP, man->parse,
411: n->line, n->pos, "nf");
1.34 schwarze 412:
1.57 schwarze 413: man->flags |= MAN_LITERAL;
1.34 schwarze 414: return(1);
415: }
416:
417: static int
418: post_fi(CHKARGS)
419: {
420:
1.76 ! schwarze 421: check_eq0(man, n);
! 422:
1.57 schwarze 423: if ( ! (MAN_LITERAL & man->flags))
1.74 schwarze 424: mandoc_msg(MANDOCERR_FI_SKIP, man->parse,
425: n->line, n->pos, "fi");
1.34 schwarze 426:
1.57 schwarze 427: man->flags &= ~MAN_LITERAL;
1.34 schwarze 428: return(1);
429: }
430:
431: static int
432: post_UC(CHKARGS)
433: {
434: static const char * const bsd_versions[] = {
435: "3rd Berkeley Distribution",
436: "4th Berkeley Distribution",
437: "4.2 Berkeley Distribution",
438: "4.3 Berkeley Distribution",
439: "4.4 Berkeley Distribution",
440: };
441:
442: const char *p, *s;
443:
444: n = n->child;
445:
446: if (NULL == n || MAN_TEXT != n->type)
447: p = bsd_versions[0];
448: else {
449: s = n->string;
450: if (0 == strcmp(s, "3"))
451: p = bsd_versions[0];
452: else if (0 == strcmp(s, "4"))
453: p = bsd_versions[1];
454: else if (0 == strcmp(s, "5"))
455: p = bsd_versions[2];
456: else if (0 == strcmp(s, "6"))
457: p = bsd_versions[3];
458: else if (0 == strcmp(s, "7"))
459: p = bsd_versions[4];
460: else
461: p = bsd_versions[0];
462: }
463:
1.57 schwarze 464: free(man->meta.source);
465: man->meta.source = mandoc_strdup(p);
1.34 schwarze 466: return(1);
467: }
468:
469: static int
470: post_AT(CHKARGS)
471: {
472: static const char * const unix_versions[] = {
473: "7th Edition",
474: "System III",
475: "System V",
476: "System V Release 2",
477: };
478:
479: const char *p, *s;
480: struct man_node *nn;
481:
482: n = n->child;
483:
484: if (NULL == n || MAN_TEXT != n->type)
485: p = unix_versions[0];
486: else {
487: s = n->string;
488: if (0 == strcmp(s, "3"))
489: p = unix_versions[0];
490: else if (0 == strcmp(s, "4"))
491: p = unix_versions[1];
492: else if (0 == strcmp(s, "5")) {
493: nn = n->next;
494: if (nn && MAN_TEXT == nn->type && nn->string[0])
495: p = unix_versions[3];
496: else
497: p = unix_versions[2];
498: } else
499: p = unix_versions[0];
500: }
501:
1.57 schwarze 502: free(man->meta.source);
503: man->meta.source = mandoc_strdup(p);
1.46 schwarze 504: return(1);
505: }
506:
507: static int
508: post_vs(CHKARGS)
509: {
1.76 ! schwarze 510:
! 511: if (n->tok == MAN_br)
! 512: check_eq0(man, n);
! 513: else
! 514: check_le1(man, n);
1.46 schwarze 515:
1.54 schwarze 516: if (NULL != n->prev)
517: return(1);
518:
519: switch (n->parent->tok) {
1.63 schwarze 520: case MAN_SH:
1.54 schwarze 521: /* FALLTHROUGH */
1.63 schwarze 522: case MAN_SS:
1.67 schwarze 523: mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
524: "%s after %s", man_macronames[n->tok],
525: man_macronames[n->parent->tok]);
1.54 schwarze 526: /* FALLTHROUGH */
1.63 schwarze 527: case MAN_MAX:
528: /*
1.54 schwarze 529: * Don't warn about this because it occurs in pod2man
530: * and would cause considerable (unfixable) warnage.
531: */
1.57 schwarze 532: man_node_delete(man, n);
1.54 schwarze 533: break;
534: default:
535: break;
536: }
1.46 schwarze 537:
1.34 schwarze 538: return(1);
539: }