man_validate.c revision 1.2 1 1.2 christos /* Id: man_validate.c,v 1.146 2018/12/31 10:04:39 schwarze Exp */
2 1.1 joerg /*
3 1.2 christos * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps (at) bsd.lv>
4 1.2 christos * Copyright (c) 2010, 2012-2018 Ingo Schwarze <schwarze (at) openbsd.org>
5 1.1 joerg *
6 1.1 joerg * Permission to use, copy, modify, and distribute this software for any
7 1.1 joerg * purpose with or without fee is hereby granted, provided that the above
8 1.1 joerg * copyright notice and this permission notice appear in all copies.
9 1.1 joerg *
10 1.2 christos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 1.1 joerg * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 1.2 christos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 1.1 joerg * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 1.1 joerg * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 1.1 joerg * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 1.1 joerg * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 1.1 joerg */
18 1.2 christos #include "config.h"
19 1.2 christos
20 1.1 joerg #include <sys/types.h>
21 1.1 joerg
22 1.1 joerg #include <assert.h>
23 1.1 joerg #include <ctype.h>
24 1.1 joerg #include <errno.h>
25 1.1 joerg #include <limits.h>
26 1.1 joerg #include <stdarg.h>
27 1.2 christos #include <stdio.h>
28 1.1 joerg #include <stdlib.h>
29 1.2 christos #include <string.h>
30 1.2 christos #include <time.h>
31 1.1 joerg
32 1.2 christos #include "mandoc_aux.h"
33 1.2 christos #include "mandoc.h"
34 1.2 christos #include "roff.h"
35 1.2 christos #include "man.h"
36 1.2 christos #include "libmandoc.h"
37 1.2 christos #include "roff_int.h"
38 1.1 joerg #include "libman.h"
39 1.1 joerg
40 1.2 christos #define CHKARGS struct roff_man *man, struct roff_node *n
41 1.1 joerg
42 1.2 christos typedef void (*v_check)(CHKARGS);
43 1.1 joerg
44 1.2 christos static void check_abort(CHKARGS) __dead;
45 1.2 christos static void check_par(CHKARGS);
46 1.2 christos static void check_part(CHKARGS);
47 1.2 christos static void check_root(CHKARGS);
48 1.2 christos static void check_text(CHKARGS);
49 1.2 christos
50 1.2 christos static void post_AT(CHKARGS);
51 1.2 christos static void post_EE(CHKARGS);
52 1.2 christos static void post_EX(CHKARGS);
53 1.2 christos static void post_IP(CHKARGS);
54 1.2 christos static void post_OP(CHKARGS);
55 1.2 christos static void post_SH(CHKARGS);
56 1.2 christos static void post_TH(CHKARGS);
57 1.2 christos static void post_UC(CHKARGS);
58 1.2 christos static void post_UR(CHKARGS);
59 1.2 christos static void post_in(CHKARGS);
60 1.2 christos
61 1.2 christos static const v_check man_valids[MAN_MAX - MAN_TH] = {
62 1.2 christos post_TH, /* TH */
63 1.2 christos post_SH, /* SH */
64 1.2 christos post_SH, /* SS */
65 1.2 christos NULL, /* TP */
66 1.2 christos NULL, /* TQ */
67 1.2 christos check_abort,/* LP */
68 1.2 christos check_par, /* PP */
69 1.2 christos check_abort,/* P */
70 1.2 christos post_IP, /* IP */
71 1.2 christos NULL, /* HP */
72 1.2 christos NULL, /* SM */
73 1.2 christos NULL, /* SB */
74 1.2 christos NULL, /* BI */
75 1.2 christos NULL, /* IB */
76 1.2 christos NULL, /* BR */
77 1.2 christos NULL, /* RB */
78 1.2 christos NULL, /* R */
79 1.2 christos NULL, /* B */
80 1.2 christos NULL, /* I */
81 1.2 christos NULL, /* IR */
82 1.2 christos NULL, /* RI */
83 1.2 christos NULL, /* RE */
84 1.2 christos check_part, /* RS */
85 1.2 christos NULL, /* DT */
86 1.2 christos post_UC, /* UC */
87 1.2 christos NULL, /* PD */
88 1.2 christos post_AT, /* AT */
89 1.2 christos post_in, /* in */
90 1.2 christos NULL, /* SY */
91 1.2 christos NULL, /* YS */
92 1.2 christos post_OP, /* OP */
93 1.2 christos post_EX, /* EX */
94 1.2 christos post_EE, /* EE */
95 1.2 christos post_UR, /* UR */
96 1.2 christos NULL, /* UE */
97 1.2 christos post_UR, /* MT */
98 1.2 christos NULL, /* ME */
99 1.1 joerg };
100 1.1 joerg
101 1.1 joerg
102 1.2 christos /* Validate the subtree rooted at man->last. */
103 1.2 christos void
104 1.2 christos man_validate(struct roff_man *man)
105 1.1 joerg {
106 1.2 christos struct roff_node *n;
107 1.2 christos const v_check *cp;
108 1.1 joerg
109 1.2 christos /*
110 1.2 christos * Translate obsolete macros such that later code
111 1.2 christos * does not need to look for them.
112 1.2 christos */
113 1.2 christos
114 1.2 christos n = man->last;
115 1.2 christos switch (n->tok) {
116 1.2 christos case MAN_LP:
117 1.2 christos case MAN_P:
118 1.2 christos n->tok = MAN_PP;
119 1.2 christos break;
120 1.2 christos default:
121 1.2 christos break;
122 1.2 christos }
123 1.1 joerg
124 1.2 christos /*
125 1.2 christos * Iterate over all children, recursing into each one
126 1.2 christos * in turn, depth-first.
127 1.2 christos */
128 1.2 christos
129 1.2 christos man->last = man->last->child;
130 1.2 christos while (man->last != NULL) {
131 1.2 christos man_validate(man);
132 1.2 christos if (man->last == n)
133 1.2 christos man->last = man->last->child;
134 1.2 christos else
135 1.2 christos man->last = man->last->next;
136 1.2 christos }
137 1.1 joerg
138 1.2 christos /* Finally validate the macro itself. */
139 1.1 joerg
140 1.2 christos man->last = n;
141 1.2 christos man->next = ROFF_NEXT_SIBLING;
142 1.2 christos switch (n->type) {
143 1.2 christos case ROFFT_TEXT:
144 1.2 christos check_text(man, n);
145 1.2 christos break;
146 1.2 christos case ROFFT_ROOT:
147 1.2 christos check_root(man, n);
148 1.2 christos break;
149 1.2 christos case ROFFT_COMMENT:
150 1.2 christos case ROFFT_EQN:
151 1.2 christos case ROFFT_TBL:
152 1.2 christos break;
153 1.1 joerg default:
154 1.2 christos if (n->tok < ROFF_MAX) {
155 1.2 christos roff_validate(man);
156 1.2 christos break;
157 1.2 christos }
158 1.2 christos assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
159 1.2 christos cp = man_valids + (n->tok - MAN_TH);
160 1.2 christos if (*cp)
161 1.2 christos (*cp)(man, n);
162 1.2 christos if (man->last == n)
163 1.2 christos n->flags |= NODE_VALID;
164 1.1 joerg break;
165 1.1 joerg }
166 1.2 christos }
167 1.1 joerg
168 1.2 christos static void
169 1.2 christos check_root(CHKARGS)
170 1.2 christos {
171 1.2 christos assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
172 1.1 joerg
173 1.2 christos if (n->last == NULL || n->last->type == ROFFT_COMMENT)
174 1.2 christos mandoc_msg(MANDOCERR_DOC_EMPTY, n->line, n->pos, NULL);
175 1.2 christos else
176 1.2 christos man->meta.hasbody = 1;
177 1.2 christos
178 1.2 christos if (NULL == man->meta.title) {
179 1.2 christos mandoc_msg(MANDOCERR_TH_NOTITLE, n->line, n->pos, NULL);
180 1.2 christos
181 1.2 christos /*
182 1.2 christos * If a title hasn't been set, do so now (by
183 1.2 christos * implication, date and section also aren't set).
184 1.2 christos */
185 1.2 christos
186 1.2 christos man->meta.title = mandoc_strdup("");
187 1.2 christos man->meta.msec = mandoc_strdup("");
188 1.2 christos man->meta.date = man->quick ? mandoc_strdup("") :
189 1.2 christos mandoc_normdate(man, NULL, n->line, n->pos);
190 1.2 christos }
191 1.2 christos
192 1.2 christos if (man->meta.os_e &&
193 1.2 christos (man->meta.rcsids & (1 << man->meta.os_e)) == 0)
194 1.2 christos mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
195 1.2 christos man->meta.os_e == MANDOC_OS_OPENBSD ?
196 1.2 christos "(OpenBSD)" : "(NetBSD)");
197 1.1 joerg }
198 1.1 joerg
199 1.2 christos static void
200 1.2 christos check_abort(CHKARGS)
201 1.2 christos {
202 1.2 christos abort();
203 1.2 christos }
204 1.1 joerg
205 1.2 christos static void
206 1.2 christos check_text(CHKARGS)
207 1.1 joerg {
208 1.2 christos char *cp, *p;
209 1.1 joerg
210 1.2 christos if (n->flags & NODE_NOFILL)
211 1.2 christos return;
212 1.1 joerg
213 1.2 christos cp = n->string;
214 1.2 christos for (p = cp; NULL != (p = strchr(p, '\t')); p++)
215 1.2 christos mandoc_msg(MANDOCERR_FI_TAB,
216 1.2 christos n->line, n->pos + (int)(p - cp), NULL);
217 1.2 christos }
218 1.1 joerg
219 1.2 christos static void
220 1.2 christos post_EE(CHKARGS)
221 1.2 christos {
222 1.2 christos if ((n->flags & NODE_NOFILL) == 0)
223 1.2 christos mandoc_msg(MANDOCERR_FI_SKIP, n->line, n->pos, "EE");
224 1.2 christos }
225 1.1 joerg
226 1.2 christos static void
227 1.2 christos post_EX(CHKARGS)
228 1.2 christos {
229 1.2 christos if (n->flags & NODE_NOFILL)
230 1.2 christos mandoc_msg(MANDOCERR_NF_SKIP, n->line, n->pos, "EX");
231 1.1 joerg }
232 1.1 joerg
233 1.2 christos static void
234 1.2 christos post_OP(CHKARGS)
235 1.2 christos {
236 1.2 christos
237 1.2 christos if (n->child == NULL)
238 1.2 christos mandoc_msg(MANDOCERR_OP_EMPTY, n->line, n->pos, "OP");
239 1.2 christos else if (n->child->next != NULL && n->child->next->next != NULL) {
240 1.2 christos n = n->child->next->next;
241 1.2 christos mandoc_msg(MANDOCERR_ARG_EXCESS,
242 1.2 christos n->line, n->pos, "OP ... %s", n->string);
243 1.2 christos }
244 1.2 christos }
245 1.1 joerg
246 1.2 christos static void
247 1.2 christos post_SH(CHKARGS)
248 1.1 joerg {
249 1.2 christos struct roff_node *nc;
250 1.1 joerg
251 1.2 christos if (n->type != ROFFT_BODY || (nc = n->child) == NULL)
252 1.2 christos return;
253 1.1 joerg
254 1.2 christos if (nc->tok == MAN_PP && nc->body->child != NULL) {
255 1.2 christos while (nc->body->last != NULL) {
256 1.2 christos man->next = ROFF_NEXT_CHILD;
257 1.2 christos roff_node_relink(man, nc->body->last);
258 1.2 christos man->last = n;
259 1.1 joerg }
260 1.2 christos }
261 1.1 joerg
262 1.2 christos if (nc->tok == MAN_PP || nc->tok == ROFF_sp || nc->tok == ROFF_br) {
263 1.2 christos mandoc_msg(MANDOCERR_PAR_SKIP, nc->line, nc->pos,
264 1.2 christos "%s after %s", roff_name[nc->tok], roff_name[n->tok]);
265 1.2 christos roff_node_delete(man, nc);
266 1.2 christos }
267 1.1 joerg
268 1.2 christos /*
269 1.2 christos * Trailing PP is empty, so it is deleted by check_par().
270 1.2 christos * Trailing sp is significant.
271 1.2 christos */
272 1.2 christos
273 1.2 christos if ((nc = n->last) != NULL && nc->tok == ROFF_br) {
274 1.2 christos mandoc_msg(MANDOCERR_PAR_SKIP,
275 1.2 christos nc->line, nc->pos, "%s at the end of %s",
276 1.2 christos roff_name[nc->tok], roff_name[n->tok]);
277 1.2 christos roff_node_delete(man, nc);
278 1.1 joerg }
279 1.2 christos }
280 1.1 joerg
281 1.2 christos static void
282 1.2 christos post_UR(CHKARGS)
283 1.2 christos {
284 1.2 christos if (n->type == ROFFT_HEAD && n->child == NULL)
285 1.2 christos mandoc_msg(MANDOCERR_UR_NOHEAD, n->line, n->pos,
286 1.2 christos "%s", roff_name[n->tok]);
287 1.2 christos check_part(man, n);
288 1.1 joerg }
289 1.1 joerg
290 1.2 christos static void
291 1.2 christos check_part(CHKARGS)
292 1.2 christos {
293 1.1 joerg
294 1.2 christos if (n->type == ROFFT_BODY && n->child == NULL)
295 1.2 christos mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
296 1.2 christos "%s", roff_name[n->tok]);
297 1.1 joerg }
298 1.1 joerg
299 1.2 christos static void
300 1.2 christos check_par(CHKARGS)
301 1.1 joerg {
302 1.1 joerg
303 1.2 christos switch (n->type) {
304 1.2 christos case ROFFT_BLOCK:
305 1.2 christos if (n->body->child == NULL)
306 1.2 christos roff_node_delete(man, n);
307 1.2 christos break;
308 1.2 christos case ROFFT_BODY:
309 1.2 christos if (n->child != NULL &&
310 1.2 christos (n->child->tok == ROFF_sp || n->child->tok == ROFF_br)) {
311 1.2 christos mandoc_msg(MANDOCERR_PAR_SKIP,
312 1.2 christos n->child->line, n->child->pos,
313 1.2 christos "%s after %s", roff_name[n->child->tok],
314 1.2 christos roff_name[n->tok]);
315 1.2 christos roff_node_delete(man, n->child);
316 1.2 christos }
317 1.2 christos if (n->child == NULL)
318 1.2 christos mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
319 1.2 christos "%s empty", roff_name[n->tok]);
320 1.2 christos break;
321 1.2 christos case ROFFT_HEAD:
322 1.2 christos if (n->child != NULL)
323 1.2 christos mandoc_msg(MANDOCERR_ARG_SKIP,
324 1.2 christos n->line, n->pos, "%s %s%s",
325 1.2 christos roff_name[n->tok], n->child->string,
326 1.2 christos n->child->next != NULL ? " ..." : "");
327 1.2 christos break;
328 1.2 christos default:
329 1.2 christos break;
330 1.2 christos }
331 1.1 joerg }
332 1.1 joerg
333 1.2 christos static void
334 1.2 christos post_IP(CHKARGS)
335 1.1 joerg {
336 1.1 joerg
337 1.2 christos switch (n->type) {
338 1.2 christos case ROFFT_BLOCK:
339 1.2 christos if (n->head->child == NULL && n->body->child == NULL)
340 1.2 christos roff_node_delete(man, n);
341 1.2 christos break;
342 1.2 christos case ROFFT_BODY:
343 1.2 christos if (n->parent->head->child == NULL && n->child == NULL)
344 1.2 christos mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
345 1.2 christos "%s empty", roff_name[n->tok]);
346 1.2 christos break;
347 1.2 christos default:
348 1.2 christos break;
349 1.2 christos }
350 1.1 joerg }
351 1.1 joerg
352 1.2 christos static void
353 1.2 christos post_TH(CHKARGS)
354 1.1 joerg {
355 1.2 christos struct roff_node *nb;
356 1.2 christos const char *p;
357 1.1 joerg
358 1.2 christos free(man->meta.title);
359 1.2 christos free(man->meta.vol);
360 1.2 christos free(man->meta.os);
361 1.2 christos free(man->meta.msec);
362 1.2 christos free(man->meta.date);
363 1.2 christos
364 1.2 christos man->meta.title = man->meta.vol = man->meta.date =
365 1.2 christos man->meta.msec = man->meta.os = NULL;
366 1.2 christos
367 1.2 christos nb = n;
368 1.2 christos
369 1.2 christos /* ->TITLE<- MSEC DATE OS VOL */
370 1.2 christos
371 1.2 christos n = n->child;
372 1.2 christos if (n && n->string) {
373 1.2 christos for (p = n->string; '\0' != *p; p++) {
374 1.2 christos /* Only warn about this once... */
375 1.2 christos if (isalpha((unsigned char)*p) &&
376 1.2 christos ! isupper((unsigned char)*p)) {
377 1.2 christos mandoc_msg(MANDOCERR_TITLE_CASE, n->line,
378 1.2 christos n->pos + (int)(p - n->string),
379 1.2 christos "TH %s", n->string);
380 1.1 joerg break;
381 1.2 christos }
382 1.1 joerg }
383 1.2 christos man->meta.title = mandoc_strdup(n->string);
384 1.2 christos } else {
385 1.2 christos man->meta.title = mandoc_strdup("");
386 1.2 christos mandoc_msg(MANDOCERR_TH_NOTITLE, nb->line, nb->pos, "TH");
387 1.2 christos }
388 1.2 christos
389 1.2 christos /* TITLE ->MSEC<- DATE OS VOL */
390 1.2 christos
391 1.2 christos if (n)
392 1.2 christos n = n->next;
393 1.2 christos if (n && n->string)
394 1.2 christos man->meta.msec = mandoc_strdup(n->string);
395 1.2 christos else {
396 1.2 christos man->meta.msec = mandoc_strdup("");
397 1.2 christos mandoc_msg(MANDOCERR_MSEC_MISSING,
398 1.2 christos nb->line, nb->pos, "TH %s", man->meta.title);
399 1.2 christos }
400 1.2 christos
401 1.2 christos /* TITLE MSEC ->DATE<- OS VOL */
402 1.2 christos
403 1.2 christos if (n)
404 1.2 christos n = n->next;
405 1.2 christos if (n && n->string && '\0' != n->string[0]) {
406 1.2 christos man->meta.date = man->quick ?
407 1.2 christos mandoc_strdup(n->string) :
408 1.2 christos mandoc_normdate(man, n->string, n->line, n->pos);
409 1.2 christos } else {
410 1.2 christos man->meta.date = mandoc_strdup("");
411 1.2 christos mandoc_msg(MANDOCERR_DATE_MISSING,
412 1.2 christos n ? n->line : nb->line,
413 1.2 christos n ? n->pos : nb->pos, "TH");
414 1.2 christos }
415 1.2 christos
416 1.2 christos /* TITLE MSEC DATE ->OS<- VOL */
417 1.2 christos
418 1.2 christos if (n && (n = n->next))
419 1.2 christos man->meta.os = mandoc_strdup(n->string);
420 1.2 christos else if (man->os_s != NULL)
421 1.2 christos man->meta.os = mandoc_strdup(man->os_s);
422 1.2 christos if (man->meta.os_e == MANDOC_OS_OTHER && man->meta.os != NULL) {
423 1.2 christos if (strstr(man->meta.os, "OpenBSD") != NULL)
424 1.2 christos man->meta.os_e = MANDOC_OS_OPENBSD;
425 1.2 christos else if (strstr(man->meta.os, "NetBSD") != NULL)
426 1.2 christos man->meta.os_e = MANDOC_OS_NETBSD;
427 1.2 christos }
428 1.2 christos
429 1.2 christos /* TITLE MSEC DATE OS ->VOL<- */
430 1.2 christos /* If missing, use the default VOL name for MSEC. */
431 1.1 joerg
432 1.2 christos if (n && (n = n->next))
433 1.2 christos man->meta.vol = mandoc_strdup(n->string);
434 1.2 christos else if ('\0' != man->meta.msec[0] &&
435 1.2 christos (NULL != (p = mandoc_a2msec(man->meta.msec))))
436 1.2 christos man->meta.vol = mandoc_strdup(p);
437 1.2 christos
438 1.2 christos if (n != NULL && (n = n->next) != NULL)
439 1.2 christos mandoc_msg(MANDOCERR_ARG_EXCESS,
440 1.2 christos n->line, n->pos, "TH ... %s", n->string);
441 1.2 christos
442 1.2 christos /*
443 1.2 christos * Remove the `TH' node after we've processed it for our
444 1.2 christos * meta-data.
445 1.2 christos */
446 1.2 christos roff_node_delete(man, man->last);
447 1.1 joerg }
448 1.1 joerg
449 1.2 christos static void
450 1.2 christos post_UC(CHKARGS)
451 1.2 christos {
452 1.2 christos static const char * const bsd_versions[] = {
453 1.2 christos "3rd Berkeley Distribution",
454 1.2 christos "4th Berkeley Distribution",
455 1.2 christos "4.2 Berkeley Distribution",
456 1.2 christos "4.3 Berkeley Distribution",
457 1.2 christos "4.4 Berkeley Distribution",
458 1.2 christos };
459 1.2 christos
460 1.2 christos const char *p, *s;
461 1.2 christos
462 1.2 christos n = n->child;
463 1.2 christos
464 1.2 christos if (n == NULL || n->type != ROFFT_TEXT)
465 1.2 christos p = bsd_versions[0];
466 1.2 christos else {
467 1.2 christos s = n->string;
468 1.2 christos if (0 == strcmp(s, "3"))
469 1.2 christos p = bsd_versions[0];
470 1.2 christos else if (0 == strcmp(s, "4"))
471 1.2 christos p = bsd_versions[1];
472 1.2 christos else if (0 == strcmp(s, "5"))
473 1.2 christos p = bsd_versions[2];
474 1.2 christos else if (0 == strcmp(s, "6"))
475 1.2 christos p = bsd_versions[3];
476 1.2 christos else if (0 == strcmp(s, "7"))
477 1.2 christos p = bsd_versions[4];
478 1.2 christos else
479 1.2 christos p = bsd_versions[0];
480 1.2 christos }
481 1.2 christos
482 1.2 christos free(man->meta.os);
483 1.2 christos man->meta.os = mandoc_strdup(p);
484 1.2 christos }
485 1.1 joerg
486 1.2 christos static void
487 1.2 christos post_AT(CHKARGS)
488 1.1 joerg {
489 1.2 christos static const char * const unix_versions[] = {
490 1.2 christos "7th Edition",
491 1.2 christos "System III",
492 1.2 christos "System V",
493 1.2 christos "System V Release 2",
494 1.2 christos };
495 1.2 christos
496 1.2 christos struct roff_node *nn;
497 1.2 christos const char *p, *s;
498 1.2 christos
499 1.2 christos n = n->child;
500 1.2 christos
501 1.2 christos if (n == NULL || n->type != ROFFT_TEXT)
502 1.2 christos p = unix_versions[0];
503 1.2 christos else {
504 1.2 christos s = n->string;
505 1.2 christos if (0 == strcmp(s, "3"))
506 1.2 christos p = unix_versions[0];
507 1.2 christos else if (0 == strcmp(s, "4"))
508 1.2 christos p = unix_versions[1];
509 1.2 christos else if (0 == strcmp(s, "5")) {
510 1.2 christos nn = n->next;
511 1.2 christos if (nn != NULL &&
512 1.2 christos nn->type == ROFFT_TEXT &&
513 1.2 christos nn->string[0] != '\0')
514 1.2 christos p = unix_versions[3];
515 1.2 christos else
516 1.2 christos p = unix_versions[2];
517 1.2 christos } else
518 1.2 christos p = unix_versions[0];
519 1.2 christos }
520 1.1 joerg
521 1.2 christos free(man->meta.os);
522 1.2 christos man->meta.os = mandoc_strdup(p);
523 1.1 joerg }
524 1.1 joerg
525 1.2 christos static void
526 1.2 christos post_in(CHKARGS)
527 1.2 christos {
528 1.2 christos char *s;
529 1.2 christos
530 1.2 christos if (n->parent->tok != MAN_TP ||
531 1.2 christos n->parent->type != ROFFT_HEAD ||
532 1.2 christos n->child == NULL ||
533 1.2 christos *n->child->string == '+' ||
534 1.2 christos *n->child->string == '-')
535 1.2 christos return;
536 1.2 christos mandoc_asprintf(&s, "+%s", n->child->string);
537 1.2 christos free(n->child->string);
538 1.2 christos n->child->string = s;
539 1.2 christos }
540