diff --git a/contrib/mandoc/mandocdb.c b/contrib/mandoc/mandocdb.c index c26a38a5f233..e8455588d225 100644 --- a/contrib/mandoc/mandocdb.c +++ b/contrib/mandoc/mandocdb.c @@ -1,2447 +1,2468 @@ -/* $Id: mandocdb.c,v 1.274 2024/05/14 21:19:12 schwarze Exp $ */ +/* $Id: mandocdb.c,v 1.275 2025/06/05 12:33:41 schwarze Exp $ */ /* - * Copyright (c) 2011-2021, 2024 Ingo Schwarze + * Copyright (c) 2011-2021, 2024, 2025 Ingo Schwarze * Copyright (c) 2011, 2012 Kristaps Dzonsons * Copyright (c) 2016 Ed Maste * * 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. * * Implementation of the makewhatis(8) program. */ #include "config.h" #include #include #include #include #include #if HAVE_ERR #include #endif #include #include #if HAVE_FTS #include #else #include "compat_fts.h" #endif #include #if HAVE_SANDBOX_INIT #include #endif #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc_ohash.h" #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "man.h" #include "mandoc_parse.h" #include "manconf.h" #include "mansearch.h" #include "dba_array.h" #include "dba.h" extern const char *const mansearch_keynames[]; enum op { OP_DEFAULT = 0, /* new dbs from dir list or default config */ OP_CONFFILE, /* new databases from custom config file */ OP_UPDATE, /* delete/add entries in existing database */ OP_DELETE, /* delete entries from existing database */ OP_TEST /* change no databases, report potential problems */ }; struct str { const struct mpage *mpage; /* if set, the owning parse */ uint64_t mask; /* bitmask in sequence */ char key[]; /* rendered text */ }; struct inodev { ino_t st_ino; dev_t st_dev; }; struct mpage { struct inodev inodev; /* used for hashing routine */ struct dba_array *dba; char *sec; /* section from file content */ char *arch; /* architecture from file content */ char *title; /* title from file content */ char *desc; /* description from file content */ struct mpage *next; /* singly linked list */ struct mlink *mlinks; /* singly linked list */ int name_head_done; enum form form; /* format from file content */ }; struct mlink { char file[PATH_MAX]; /* filename rel. to manpath */ char *dsec; /* section from directory */ char *arch; /* architecture from directory */ char *name; /* name from file name (not empty) */ char *fsec; /* section from file name suffix */ struct mlink *next; /* singly linked list */ struct mpage *mpage; /* parent */ int gzip; /* filename has a .gz suffix */ enum form dform; /* format from directory */ enum form fform; /* format from file name suffix */ }; typedef int (*mdoc_fp)(struct mpage *, const struct roff_meta *, const struct roff_node *); struct mdoc_handler { mdoc_fp fp; /* optional handler */ uint64_t mask; /* set unless handler returns 0 */ int taboo; /* node flags that must not be set */ }; int mandocdb(int, char *[]); static void dbadd(struct dba *, struct mpage *); static void dbadd_mlink(const struct mlink *); static void dbprune(struct dba *); static void dbwrite(struct dba *); static void filescan(const char *); #if HAVE_FTS_COMPARE_CONST static int fts_compare(const FTSENT *const *, const FTSENT *const *); #else static int fts_compare(const FTSENT **, const FTSENT **); #endif static void mlink_add(struct mlink *, const struct stat *); static void mlink_check(struct mpage *, struct mlink *); static void mlink_free(struct mlink *); static void mlinks_undupe(struct mpage *); static void mpages_free(void); static void mpages_merge(struct dba *, struct mparse *); static void parse_cat(struct mpage *, int); static void parse_man(struct mpage *, const struct roff_meta *, const struct roff_node *); static void parse_mdoc(struct mpage *, const struct roff_meta *, const struct roff_node *); static int parse_mdoc_head(struct mpage *, const struct roff_meta *, const struct roff_node *); static int parse_mdoc_Fa(struct mpage *, const struct roff_meta *, const struct roff_node *); static int parse_mdoc_Fd(struct mpage *, const struct roff_meta *, const struct roff_node *); static void parse_mdoc_fname(struct mpage *, const struct roff_node *); static int parse_mdoc_Fn(struct mpage *, const struct roff_meta *, const struct roff_node *); static int parse_mdoc_Fo(struct mpage *, const struct roff_meta *, const struct roff_node *); +static int parse_mdoc_Lb(struct mpage *, const struct roff_meta *, + const struct roff_node *); static int parse_mdoc_Nd(struct mpage *, const struct roff_meta *, const struct roff_node *); static int parse_mdoc_Nm(struct mpage *, const struct roff_meta *, const struct roff_node *); static int parse_mdoc_Sh(struct mpage *, const struct roff_meta *, const struct roff_node *); static int parse_mdoc_Va(struct mpage *, const struct roff_meta *, const struct roff_node *); static int parse_mdoc_Xr(struct mpage *, const struct roff_meta *, const struct roff_node *); static void putkey(const struct mpage *, char *, uint64_t); static void putkeys(const struct mpage *, char *, size_t, uint64_t); static void putmdockey(const struct mpage *, const struct roff_node *, uint64_t, int); #ifdef READ_ALLOWED_PATH static int read_allowed(const char *); #endif static int render_string(char **, size_t *); static void say(const char *, const char *, ...) __attribute__((__format__ (__printf__, 2, 3))); static int set_basedir(const char *, int); static int treescan(void); static size_t utf8(unsigned int, char[5]); static int nodb; /* no database changes */ static int mparse_options; /* abort the parse early */ static int use_all; /* use all found files */ static int debug; /* print what we're doing */ static int warnings; /* warn about crap */ static int write_utf8; /* write UTF-8 output; else ASCII */ static int exitcode; /* to be returned by main */ static enum op op; /* operational mode */ static char basedir[PATH_MAX]; /* current base directory */ static size_t basedir_len; /* strlen(basedir) */ static struct mpage *mpage_head; /* list of distinct manual pages */ static struct ohash mpages; /* table of distinct manual pages */ static struct ohash mlinks; /* table of directory entries */ static struct ohash names; /* table of all names */ static struct ohash strings; /* table of all strings */ static uint64_t name_mask; static const struct mdoc_handler mdoc_handlers[MDOC_MAX - MDOC_Dd] = { { NULL, 0, NODE_NOPRT }, /* Dd */ { NULL, 0, NODE_NOPRT }, /* Dt */ { NULL, 0, NODE_NOPRT }, /* Os */ { parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */ { parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */ { NULL, 0, 0 }, /* Pp */ { NULL, 0, 0 }, /* D1 */ { NULL, 0, 0 }, /* Dl */ { NULL, 0, 0 }, /* Bd */ { NULL, 0, 0 }, /* Ed */ { NULL, 0, 0 }, /* Bl */ { NULL, 0, 0 }, /* El */ { NULL, 0, 0 }, /* It */ { NULL, 0, 0 }, /* Ad */ { NULL, TYPE_An, 0 }, /* An */ { NULL, 0, 0 }, /* Ap */ { NULL, TYPE_Ar, 0 }, /* Ar */ { NULL, TYPE_Cd, 0 }, /* Cd */ { NULL, TYPE_Cm, 0 }, /* Cm */ { NULL, TYPE_Dv, 0 }, /* Dv */ { NULL, TYPE_Er, 0 }, /* Er */ { NULL, TYPE_Ev, 0 }, /* Ev */ { NULL, 0, 0 }, /* Ex */ { parse_mdoc_Fa, 0, 0 }, /* Fa */ { parse_mdoc_Fd, 0, 0 }, /* Fd */ { NULL, TYPE_Fl, 0 }, /* Fl */ { parse_mdoc_Fn, 0, 0 }, /* Fn */ { NULL, TYPE_Ft | TYPE_Vt, 0 }, /* Ft */ { NULL, TYPE_Ic, 0 }, /* Ic */ { NULL, TYPE_In, 0 }, /* In */ { NULL, TYPE_Li, 0 }, /* Li */ { parse_mdoc_Nd, 0, 0 }, /* Nd */ { parse_mdoc_Nm, 0, 0 }, /* Nm */ { NULL, 0, 0 }, /* Op */ { NULL, 0, 0 }, /* Ot */ { NULL, TYPE_Pa, NODE_NOSRC }, /* Pa */ { NULL, 0, 0 }, /* Rv */ { NULL, TYPE_St, 0 }, /* St */ { parse_mdoc_Va, TYPE_Va, 0 }, /* Va */ { parse_mdoc_Va, TYPE_Vt, 0 }, /* Vt */ { parse_mdoc_Xr, 0, 0 }, /* Xr */ { NULL, 0, 0 }, /* %A */ { NULL, 0, 0 }, /* %B */ { NULL, 0, 0 }, /* %D */ { NULL, 0, 0 }, /* %I */ { NULL, 0, 0 }, /* %J */ { NULL, 0, 0 }, /* %N */ { NULL, 0, 0 }, /* %O */ { NULL, 0, 0 }, /* %P */ { NULL, 0, 0 }, /* %R */ { NULL, 0, 0 }, /* %T */ { NULL, 0, 0 }, /* %V */ { NULL, 0, 0 }, /* Ac */ { NULL, 0, 0 }, /* Ao */ { NULL, 0, 0 }, /* Aq */ { NULL, TYPE_At, 0 }, /* At */ { NULL, 0, 0 }, /* Bc */ { NULL, 0, 0 }, /* Bf */ { NULL, 0, 0 }, /* Bo */ { NULL, 0, 0 }, /* Bq */ { NULL, TYPE_Bsx, NODE_NOSRC }, /* Bsx */ { NULL, TYPE_Bx, NODE_NOSRC }, /* Bx */ { NULL, 0, 0 }, /* Db */ { NULL, 0, 0 }, /* Dc */ { NULL, 0, 0 }, /* Do */ { NULL, 0, 0 }, /* Dq */ { NULL, 0, 0 }, /* Ec */ { NULL, 0, 0 }, /* Ef */ { NULL, TYPE_Em, 0 }, /* Em */ { NULL, 0, 0 }, /* Eo */ { NULL, TYPE_Fx, NODE_NOSRC }, /* Fx */ { NULL, TYPE_Ms, 0 }, /* Ms */ { NULL, 0, 0 }, /* No */ { NULL, 0, 0 }, /* Ns */ { NULL, TYPE_Nx, NODE_NOSRC }, /* Nx */ { NULL, TYPE_Ox, NODE_NOSRC }, /* Ox */ { NULL, 0, 0 }, /* Pc */ { NULL, 0, 0 }, /* Pf */ { NULL, 0, 0 }, /* Po */ { NULL, 0, 0 }, /* Pq */ { NULL, 0, 0 }, /* Qc */ { NULL, 0, 0 }, /* Ql */ { NULL, 0, 0 }, /* Qo */ { NULL, 0, 0 }, /* Qq */ { NULL, 0, 0 }, /* Re */ { NULL, 0, 0 }, /* Rs */ { NULL, 0, 0 }, /* Sc */ { NULL, 0, 0 }, /* So */ { NULL, 0, 0 }, /* Sq */ { NULL, 0, 0 }, /* Sm */ { NULL, 0, 0 }, /* Sx */ { NULL, TYPE_Sy, 0 }, /* Sy */ { NULL, TYPE_Tn, 0 }, /* Tn */ { NULL, 0, NODE_NOSRC }, /* Ux */ { NULL, 0, 0 }, /* Xc */ { NULL, 0, 0 }, /* Xo */ { parse_mdoc_Fo, 0, 0 }, /* Fo */ { NULL, 0, 0 }, /* Fc */ { NULL, 0, 0 }, /* Oo */ { NULL, 0, 0 }, /* Oc */ { NULL, 0, 0 }, /* Bk */ { NULL, 0, 0 }, /* Ek */ { NULL, 0, 0 }, /* Bt */ { NULL, 0, 0 }, /* Hf */ { NULL, 0, 0 }, /* Fr */ { NULL, 0, 0 }, /* Ud */ - { NULL, TYPE_Lb, NODE_NOSRC }, /* Lb */ + { parse_mdoc_Lb, 0, 0 }, /* Lb */ { NULL, 0, 0 }, /* Lp */ { NULL, TYPE_Lk, 0 }, /* Lk */ { NULL, TYPE_Mt, NODE_NOSRC }, /* Mt */ { NULL, 0, 0 }, /* Brq */ { NULL, 0, 0 }, /* Bro */ { NULL, 0, 0 }, /* Brc */ { NULL, 0, 0 }, /* %C */ { NULL, 0, 0 }, /* Es */ { NULL, 0, 0 }, /* En */ { NULL, TYPE_Dx, NODE_NOSRC }, /* Dx */ { NULL, 0, 0 }, /* %Q */ { NULL, 0, 0 }, /* %U */ { NULL, 0, 0 }, /* Ta */ }; int mandocdb(int argc, char *argv[]) { struct manconf conf; struct mparse *mp; struct dba *dba; const char *path_arg, *progname; size_t j, sz; int ch, i; #if HAVE_PLEDGE if (pledge("stdio rpath wpath cpath", NULL) == -1) { warn("pledge"); return (int)MANDOCLEVEL_SYSERR; } #endif #if HAVE_SANDBOX_INIT if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) { warnx("sandbox_init"); return (int)MANDOCLEVEL_SYSERR; } #endif memset(&conf, 0, sizeof(conf)); /* * We accept a few different invocations. * The CHECKOP macro makes sure that invocation styles don't * clobber each other. */ #define CHECKOP(_op, _ch) do \ if ((_op) != OP_DEFAULT) { \ warnx("-%c: Conflicting option", (_ch)); \ goto usage; \ } while (/*CONSTCOND*/0) mparse_options = MPARSE_UTF8 | MPARSE_LATIN1 | MPARSE_VALIDATE; path_arg = NULL; op = OP_DEFAULT; while ((ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")) != -1) switch (ch) { case 'a': use_all = 1; break; case 'C': CHECKOP(op, ch); path_arg = optarg; op = OP_CONFFILE; break; case 'D': debug++; break; case 'd': CHECKOP(op, ch); path_arg = optarg; op = OP_UPDATE; break; case 'n': nodb = 1; break; case 'p': warnings = 1; break; case 'Q': mparse_options |= MPARSE_QUICK; break; case 'T': if (strcmp(optarg, "utf8") != 0) { warnx("-T%s: Unsupported output format", optarg); goto usage; } write_utf8 = 1; break; case 't': CHECKOP(op, ch); dup2(STDOUT_FILENO, STDERR_FILENO); op = OP_TEST; nodb = warnings = 1; break; case 'u': CHECKOP(op, ch); path_arg = optarg; op = OP_DELETE; break; case 'v': /* Compatibility with espie@'s makewhatis. */ break; default: goto usage; } argc -= optind; argv += optind; #if HAVE_PLEDGE if (nodb) { if (pledge("stdio rpath", NULL) == -1) { warn("pledge"); return (int)MANDOCLEVEL_SYSERR; } } #endif if (op == OP_CONFFILE && argc > 0) { warnx("-C: Too many arguments"); goto usage; } exitcode = (int)MANDOCLEVEL_OK; mchars_alloc(); mp = mparse_alloc(mparse_options, MANDOC_OS_OTHER, NULL); mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev)); mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file)); if (op == OP_UPDATE || op == OP_DELETE || op == OP_TEST) { /* * Most of these deal with a specific directory. * Jump into that directory first. */ if (op != OP_TEST && set_basedir(path_arg, 1) == 0) goto out; dba = nodb ? dba_new(128) : dba_read(MANDOC_DB); if (dba != NULL) { /* * The existing database is usable. Process * all files specified on the command-line. */ use_all = 1; for (i = 0; i < argc; i++) filescan(argv[i]); if (nodb == 0) dbprune(dba); } else { /* Database missing or corrupt. */ if (op != OP_UPDATE || errno != ENOENT) say(MANDOC_DB, "%s: Automatically recreating" " from scratch", strerror(errno)); exitcode = (int)MANDOCLEVEL_OK; op = OP_DEFAULT; if (treescan() == 0) goto out; dba = dba_new(128); } if (op != OP_DELETE) mpages_merge(dba, mp); if (nodb == 0) dbwrite(dba); dba_free(dba); } else { /* * If we have arguments, use them as our manpaths. * If we don't, use man.conf(5). */ if (argc > 0) { conf.manpath.paths = mandoc_reallocarray(NULL, argc, sizeof(char *)); conf.manpath.sz = (size_t)argc; for (i = 0; i < argc; i++) conf.manpath.paths[i] = mandoc_strdup(argv[i]); } else manconf_parse(&conf, path_arg, NULL, NULL); if (conf.manpath.sz == 0) { exitcode = (int)MANDOCLEVEL_BADARG; say("", "Empty manpath"); } /* * First scan the tree rooted at a base directory, then * build a new database and finally move it into place. * Ignore zero-length directories and strip trailing * slashes. */ for (j = 0; j < conf.manpath.sz; j++) { sz = strlen(conf.manpath.paths[j]); if (sz && conf.manpath.paths[j][sz - 1] == '/') conf.manpath.paths[j][--sz] = '\0'; if (sz == 0) continue; if (j) { mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev)); mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file)); } if (set_basedir(conf.manpath.paths[j], argc > 0) == 0) continue; if (treescan() == 0) continue; dba = dba_new(128); mpages_merge(dba, mp); if (nodb == 0) dbwrite(dba); dba_free(dba); if (j + 1 < conf.manpath.sz) { mpages_free(); ohash_delete(&mpages); ohash_delete(&mlinks); } } } out: manconf_free(&conf); mparse_free(mp); mchars_free(); mpages_free(); ohash_delete(&mpages); ohash_delete(&mlinks); #if DEBUG_MEMORY mandoc_dbg_finish(); #endif return exitcode; usage: progname = getprogname(); fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n" " %s [-aDnpQ] [-Tutf8] dir ...\n" " %s [-DnpQ] [-Tutf8] -d dir [file ...]\n" " %s [-Dnp] -u dir [file ...]\n" " %s [-Q] -t file ...\n", progname, progname, progname, progname, progname); return (int)MANDOCLEVEL_BADARG; } /* * To get a singly linked list in alpha order while inserting entries * at the beginning, process directory entries in reverse alpha order. */ static int #if HAVE_FTS_COMPARE_CONST fts_compare(const FTSENT *const *a, const FTSENT *const *b) #else fts_compare(const FTSENT **a, const FTSENT **b) #endif { return -strcmp((*a)->fts_name, (*b)->fts_name); } /* * Scan a directory tree rooted at "basedir" for manpages. * We use fts(), scanning directory parts along the way for clues to our * section and architecture. * * If use_all has been specified, grok all files. * If not, sanitise paths to the following: * * [./]man*[/]/.
* or * [./]cat
[/]/.0 * * TODO: accommodate for multi-language directories. */ static int treescan(void) { char buf[PATH_MAX]; FTS *f; FTSENT *ff; struct mlink *mlink; int gzip; enum form dform; char *dsec, *arch, *fsec, *cp; const char *path; const char *argv[2]; argv[0] = "."; argv[1] = NULL; f = fts_open((char * const *)argv, FTS_PHYSICAL | FTS_NOCHDIR, fts_compare); if (f == NULL) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&fts_open"); return 0; } dsec = arch = NULL; dform = FORM_NONE; while ((ff = fts_read(f)) != NULL) { path = ff->fts_path + 2; switch (ff->fts_info) { /* * Symbolic links require various sanity checks, * then get handled just like regular files. */ case FTS_SL: if (realpath(path, buf) == NULL) { if (warnings) say(path, "&realpath"); continue; } if (strncmp(buf, basedir, basedir_len) != 0 #ifdef READ_ALLOWED_PATH && !read_allowed(buf) #endif ) { if (warnings) say("", "%s: outside base directory", buf); continue; } /* Use logical inode to avoid mpages dupe. */ if (stat(path, ff->fts_statp) == -1) { if (warnings) say(path, "&stat"); continue; } if ((ff->fts_statp->st_mode & S_IFMT) != S_IFREG) continue; /* FALLTHROUGH */ /* * If we're a regular file, add an mlink by using the * stored directory data and handling the filename. */ case FTS_F: if ( ! strcmp(path, MANDOC_DB)) continue; if ( ! use_all && ff->fts_level < 2) { if (warnings) say(path, "Extraneous file"); continue; } gzip = 0; fsec = NULL; while (fsec == NULL) { fsec = strrchr(ff->fts_name, '.'); if (fsec == NULL || strcmp(fsec+1, "gz")) break; gzip = 1; *fsec = '\0'; fsec = NULL; } if (fsec == NULL) { if ( ! use_all) { if (warnings) say(path, "No filename suffix"); continue; } } else if ( ! strcmp(++fsec, "html")) { if (warnings) say(path, "Skip html"); continue; } else if ( ! strcmp(fsec, "ps")) { if (warnings) say(path, "Skip ps"); continue; } else if ( ! strcmp(fsec, "pdf")) { if (warnings) say(path, "Skip pdf"); continue; } else if ( ! use_all && ((dform == FORM_SRC && strncmp(fsec, dsec, strlen(dsec))) || (dform == FORM_CAT && strcmp(fsec, "0")))) { if (warnings) say(path, "Wrong filename suffix"); continue; } else fsec[-1] = '\0'; mlink = mandoc_calloc(1, sizeof(struct mlink)); if (strlcpy(mlink->file, path, sizeof(mlink->file)) >= sizeof(mlink->file)) { say(path, "Filename too long"); free(mlink); continue; } mlink->dform = dform; mlink->dsec = dsec; mlink->arch = arch; mlink->name = ff->fts_name; mlink->fsec = fsec; mlink->gzip = gzip; mlink_add(mlink, ff->fts_statp); continue; case FTS_D: case FTS_DP: break; default: if (warnings) say(path, "Not a regular file"); continue; } switch (ff->fts_level) { case 0: /* Ignore the root directory. */ break; case 1: /* * This might contain manX/ or catX/. * Try to infer this from the name. * If we're not in use_all, enforce it. */ cp = ff->fts_name; if (ff->fts_info == FTS_DP) { dform = FORM_NONE; dsec = NULL; break; } if ( ! strncmp(cp, "man", 3)) { dform = FORM_SRC; dsec = cp + 3; } else if ( ! strncmp(cp, "cat", 3)) { dform = FORM_CAT; dsec = cp + 3; } else { dform = FORM_NONE; dsec = NULL; } if (dsec != NULL || use_all) break; if (warnings) say(path, "Unknown directory part"); fts_set(f, ff, FTS_SKIP); break; case 2: /* * Possibly our architecture. * If we're descending, keep tabs on it. */ if (ff->fts_info != FTS_DP && dsec != NULL) arch = ff->fts_name; else arch = NULL; break; default: if (ff->fts_info == FTS_DP || use_all) break; if (warnings) say(path, "Extraneous directory part"); fts_set(f, ff, FTS_SKIP); break; } } fts_close(f); return 1; } /* * Add a file to the mlinks table. * Do not verify that it's a "valid" looking manpage (we'll do that * later). * * Try to infer the manual section, architecture, and page name from the * path, assuming it looks like * * [./]man*[/]/.
* or * [./]cat
[/]/.0 * * See treescan() for the fts(3) version of this. */ static void filescan(const char *infile) { struct stat st; struct mlink *mlink; char *linkfile, *p, *realdir, *start, *usefile; size_t realdir_len; assert(use_all); if (strncmp(infile, "./", 2) == 0) infile += 2; /* * We have to do lstat(2) before realpath(3) loses * the information whether this is a symbolic link. * We need to know that because for symbolic links, * we want to use the original file name, while for * regular files, we want to use the real path. */ if (lstat(infile, &st) == -1) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "&lstat"); return; } else if (S_ISREG(st.st_mode) == 0 && S_ISLNK(st.st_mode) == 0) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "Not a regular file"); return; } /* * We have to resolve the file name to the real path * in any case for the base directory check. */ if ((usefile = realpath(infile, NULL)) == NULL) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "&realpath"); return; } if (op == OP_TEST) start = usefile; else if (strncmp(usefile, basedir, basedir_len) == 0) start = usefile + basedir_len; #ifdef READ_ALLOWED_PATH else if (read_allowed(usefile)) start = usefile; #endif else { exitcode = (int)MANDOCLEVEL_BADARG; say("", "%s: outside base directory", infile); free(usefile); return; } /* * Now we are sure the file is inside our tree. * If it is a symbolic link, ignore the real path * and use the original name. */ do { if (S_ISLNK(st.st_mode) == 0) break; /* * Some implementations of realpath(3) may succeed * even if the target of the link does not exist, * so check again for extra safety. */ if (stat(usefile, &st) == -1) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "&stat"); free(usefile); return; } linkfile = mandoc_strdup(infile); if (op == OP_TEST) { free(usefile); start = usefile = linkfile; break; } if (strncmp(infile, basedir, basedir_len) == 0) { free(usefile); usefile = linkfile; start = usefile + basedir_len; break; } /* * This symbolic link points into the basedir * from the outside. Let's see whether any of * the parent directories resolve to the basedir. */ p = strchr(linkfile, '\0'); do { while (*--p != '/') continue; *p = '\0'; if ((realdir = realpath(linkfile, NULL)) == NULL) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "&realpath"); free(linkfile); free(usefile); return; } realdir_len = strlen(realdir) + 1; free(realdir); *p = '/'; } while (realdir_len > basedir_len); /* * If one of the directories resolves to the basedir, * use the rest of the original name. * Otherwise, the best we can do * is to use the filename pointed to. */ if (realdir_len == basedir_len) { free(usefile); usefile = linkfile; start = p + 1; } else { free(linkfile); start = usefile + basedir_len; } } while (/* CONSTCOND */ 0); mlink = mandoc_calloc(1, sizeof(struct mlink)); mlink->dform = FORM_NONE; if (strlcpy(mlink->file, start, sizeof(mlink->file)) >= sizeof(mlink->file)) { say(start, "Filename too long"); free(mlink); free(usefile); return; } /* * In test mode or when the original name is absolute * but outside our tree, guess the base directory. */ if (op == OP_TEST || (start == usefile && *start == '/')) { if (strncmp(usefile, "man/", 4) == 0) start = usefile + 4; else if ((start = strstr(usefile, "/man/")) != NULL) start += 5; else start = usefile; } /* * First try to guess our directory structure. * If we find a separator, try to look for man* or cat*. * If we find one of these and what's underneath is a directory, * assume it's an architecture. */ if ((p = strchr(start, '/')) != NULL) { *p++ = '\0'; if (strncmp(start, "man", 3) == 0) { mlink->dform = FORM_SRC; mlink->dsec = start + 3; } else if (strncmp(start, "cat", 3) == 0) { mlink->dform = FORM_CAT; mlink->dsec = start + 3; } start = p; if (mlink->dsec != NULL && (p = strchr(start, '/')) != NULL) { *p++ = '\0'; mlink->arch = start; start = p; } } /* * Now check the file suffix. * Suffix of `.0' indicates a catpage, `.1-9' is a manpage. */ p = strrchr(start, '\0'); while (p-- > start && *p != '/' && *p != '.') continue; if (*p == '.') { *p++ = '\0'; mlink->fsec = p; } /* * Now try to parse the name. * Use the filename portion of the path. */ mlink->name = start; if ((p = strrchr(start, '/')) != NULL) { mlink->name = p + 1; *p = '\0'; } mlink_add(mlink, &st); free(usefile); } static void mlink_add(struct mlink *mlink, const struct stat *st) { struct inodev inodev; struct mpage *mpage; unsigned int slot; assert(NULL != mlink->file); mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : ""); mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : ""); mlink->name = mandoc_strdup(mlink->name ? mlink->name : ""); mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : ""); if ('0' == *mlink->fsec) { free(mlink->fsec); mlink->fsec = mandoc_strdup(mlink->dsec); mlink->fform = FORM_CAT; } else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec) mlink->fform = FORM_SRC; else mlink->fform = FORM_NONE; slot = ohash_qlookup(&mlinks, mlink->file); assert(NULL == ohash_find(&mlinks, slot)); ohash_insert(&mlinks, slot, mlink); memset(&inodev, 0, sizeof(inodev)); /* Clear padding. */ inodev.st_ino = st->st_ino; inodev.st_dev = st->st_dev; slot = ohash_lookup_memory(&mpages, (char *)&inodev, sizeof(struct inodev), inodev.st_ino); mpage = ohash_find(&mpages, slot); if (NULL == mpage) { mpage = mandoc_calloc(1, sizeof(struct mpage)); mpage->inodev.st_ino = inodev.st_ino; mpage->inodev.st_dev = inodev.st_dev; mpage->form = FORM_NONE; mpage->next = mpage_head; mpage_head = mpage; ohash_insert(&mpages, slot, mpage); } else mlink->next = mpage->mlinks; mpage->mlinks = mlink; mlink->mpage = mpage; } static void mlink_free(struct mlink *mlink) { free(mlink->dsec); free(mlink->arch); free(mlink->name); free(mlink->fsec); free(mlink); } static void mpages_free(void) { struct mpage *mpage; struct mlink *mlink; while ((mpage = mpage_head) != NULL) { while ((mlink = mpage->mlinks) != NULL) { mpage->mlinks = mlink->next; mlink_free(mlink); } mpage_head = mpage->next; free(mpage->sec); free(mpage->arch); free(mpage->title); free(mpage->desc); free(mpage); } } /* * For each mlink to the mpage, check whether the path looks like * it is formatted, and if it does, check whether a source manual * exists by the same name, ignoring the suffix. * If both conditions hold, drop the mlink. */ static void mlinks_undupe(struct mpage *mpage) { char buf[PATH_MAX]; struct mlink **prev; struct mlink *mlink; char *bufp; mpage->form = FORM_CAT; prev = &mpage->mlinks; while (NULL != (mlink = *prev)) { if (FORM_CAT != mlink->dform) { mpage->form = FORM_NONE; goto nextlink; } (void)strlcpy(buf, mlink->file, sizeof(buf)); bufp = strstr(buf, "cat"); assert(NULL != bufp); memcpy(bufp, "man", 3); if (NULL != (bufp = strrchr(buf, '.'))) *++bufp = '\0'; (void)strlcat(buf, mlink->dsec, sizeof(buf)); if (NULL == ohash_find(&mlinks, ohash_qlookup(&mlinks, buf))) goto nextlink; if (warnings) say(mlink->file, "Man source exists: %s", buf); if (use_all) goto nextlink; *prev = mlink->next; mlink_free(mlink); continue; nextlink: prev = &(*prev)->next; } } static void mlink_check(struct mpage *mpage, struct mlink *mlink) { struct str *str; unsigned int slot; /* * Check whether the manual section given in a file * agrees with the directory where the file is located. * Some manuals have suffixes like (3p) on their * section number either inside the file or in the * directory name, some are linked into more than one * section, like encrypt(1) = makekey(8). */ if (FORM_SRC == mpage->form && strcasecmp(mpage->sec, mlink->dsec)) say(mlink->file, "Section \"%s\" manual in %s directory", mpage->sec, mlink->dsec); /* * Manual page directories exist for each kernel * architecture as returned by machine(1). * However, many manuals only depend on the * application architecture as returned by arch(1). * For example, some (2/ARM) manuals are shared * across the "armish" and "zaurus" kernel * architectures. * A few manuals are even shared across completely * different architectures, for example fdformat(1) * on amd64, i386, and sparc64. */ if (strcasecmp(mpage->arch, mlink->arch)) say(mlink->file, "Architecture \"%s\" manual in " "\"%s\" directory", mpage->arch, mlink->arch); /* * XXX * parse_cat() doesn't set NAME_TITLE yet. */ if (FORM_CAT == mpage->form) return; /* * Check whether this mlink * appears as a name in the NAME section. */ slot = ohash_qlookup(&names, mlink->name); str = ohash_find(&names, slot); assert(NULL != str); if ( ! (NAME_TITLE & str->mask)) say(mlink->file, "Name missing in NAME section"); } /* * Run through the files in the global vector "mpages" * and add them to the database specified in "basedir". * * This handles the parsing scheme itself, using the cues of directory * and filename to determine whether the file is parsable or not. */ static void mpages_merge(struct dba *dba, struct mparse *mp) { struct mpage *mpage, *mpage_dest; struct mlink *mlink, *mlink_dest; struct roff_meta *meta; char *cp; int fd; for (mpage = mpage_head; mpage != NULL; mpage = mpage->next) { mlinks_undupe(mpage); if ((mlink = mpage->mlinks) == NULL) continue; name_mask = NAME_MASK; mandoc_ohash_init(&names, 4, offsetof(struct str, key)); mandoc_ohash_init(&strings, 6, offsetof(struct str, key)); mparse_reset(mp); meta = NULL; if ((fd = mparse_open(mp, mlink->file)) == -1) { say(mlink->file, "&open"); goto nextpage; } /* * Interpret the file as mdoc(7) or man(7) source * code, unless it is known to be formatted. */ if (mlink->dform != FORM_CAT || mlink->fform != FORM_CAT) { mparse_readfd(mp, fd, mlink->file); close(fd); fd = -1; meta = mparse_result(mp); } if (meta != NULL && meta->sodest != NULL) { mlink_dest = ohash_find(&mlinks, ohash_qlookup(&mlinks, meta->sodest)); if (mlink_dest == NULL) { mandoc_asprintf(&cp, "%s.gz", meta->sodest); mlink_dest = ohash_find(&mlinks, ohash_qlookup(&mlinks, cp)); free(cp); } if (mlink_dest != NULL) { /* The .so target exists. */ mpage_dest = mlink_dest->mpage; while (1) { mlink->mpage = mpage_dest; /* * If the target was already * processed, add the links * to the database now. * Otherwise, this will * happen when we come * to the target. */ if (mpage_dest->dba != NULL) dbadd_mlink(mlink); if (mlink->next == NULL) break; mlink = mlink->next; } /* Move all links to the target. */ mlink->next = mlink_dest->next; mlink_dest->next = mpage->mlinks; mpage->mlinks = NULL; goto nextpage; } meta->macroset = MACROSET_NONE; } if (meta != NULL && meta->macroset == MACROSET_MDOC) { mpage->form = FORM_SRC; mpage->sec = meta->msec; mpage->sec = mandoc_strdup( mpage->sec == NULL ? "" : mpage->sec); mpage->arch = meta->arch; mpage->arch = mandoc_strdup( mpage->arch == NULL ? "" : mpage->arch); mpage->title = mandoc_strdup(meta->title); } else if (meta != NULL && meta->macroset == MACROSET_MAN) { if (*meta->msec != '\0' || *meta->title != '\0') { mpage->form = FORM_SRC; mpage->sec = mandoc_strdup(meta->msec); mpage->arch = mandoc_strdup(mlink->arch); mpage->title = mandoc_strdup(meta->title); } else meta = NULL; } assert(mpage->desc == NULL); if (meta == NULL || meta->sodest != NULL) { mpage->sec = mandoc_strdup(mlink->dsec); mpage->arch = mandoc_strdup(mlink->arch); mpage->title = mandoc_strdup(mlink->name); if (meta == NULL) { mpage->form = FORM_CAT; parse_cat(mpage, fd); } else mpage->form = FORM_SRC; } else if (meta->macroset == MACROSET_MDOC) parse_mdoc(mpage, meta, meta->first); else parse_man(mpage, meta, meta->first); if (mpage->desc == NULL) { mpage->desc = mandoc_strdup(mlink->name); if (warnings) say(mlink->file, "No one-line description, " "using filename \"%s\"", mlink->name); } for (mlink = mpage->mlinks; mlink != NULL; mlink = mlink->next) { putkey(mpage, mlink->name, NAME_FILE); if (warnings && !use_all) mlink_check(mpage, mlink); } dbadd(dba, mpage); nextpage: ohash_delete(&strings); ohash_delete(&names); } } static void parse_cat(struct mpage *mpage, int fd) { FILE *stream; struct mlink *mlink; char *line, *p, *title, *sec; size_t linesz, plen, titlesz; ssize_t len; int offs; mlink = mpage->mlinks; stream = fd == -1 ? fopen(mlink->file, "r") : fdopen(fd, "r"); if (stream == NULL) { if (fd != -1) close(fd); if (warnings) say(mlink->file, "&fopen"); return; } line = NULL; linesz = 0; /* Parse the section number from the header line. */ while (getline(&line, &linesz, stream) != -1) { if (*line == '\n') continue; if ((sec = strchr(line, '(')) == NULL) break; if ((p = strchr(++sec, ')')) == NULL) break; free(mpage->sec); mpage->sec = mandoc_strndup(sec, p - sec); if (warnings && *mlink->dsec != '\0' && strcasecmp(mpage->sec, mlink->dsec)) say(mlink->file, "Section \"%s\" manual in %s directory", mpage->sec, mlink->dsec); break; } /* Skip to first blank line. */ while (line == NULL || *line != '\n') if (getline(&line, &linesz, stream) == -1) break; /* * Assume the first line that is not indented * is the first section header. Skip to it. */ while (getline(&line, &linesz, stream) != -1) if (*line != '\n' && *line != ' ') break; /* * Read up until the next section into a buffer. * Strip the leading and trailing newline from each read line, * appending a trailing space. * Ignore empty (whitespace-only) lines. */ titlesz = 0; title = NULL; while ((len = getline(&line, &linesz, stream)) != -1) { if (*line != ' ') break; offs = 0; while (isspace((unsigned char)line[offs])) offs++; if (line[offs] == '\0') continue; title = mandoc_realloc(title, titlesz + len - offs); memcpy(title + titlesz, line + offs, len - offs); titlesz += len - offs; title[titlesz - 1] = ' '; } free(line); /* * If no page content can be found, or the input line * is already the next section header, or there is no * trailing newline, reuse the page title as the page * description. */ if (NULL == title || '\0' == *title) { if (warnings) say(mlink->file, "Cannot find NAME section"); fclose(stream); free(title); return; } title[titlesz - 1] = '\0'; /* * Skip to the first dash. * Use the remaining line as the description (no more than 70 * bytes). */ if (NULL != (p = strstr(title, "- "))) { for (p += 2; ' ' == *p || '\b' == *p; p++) /* Skip to next word. */ ; } else { if (warnings) say(mlink->file, "No dash in title line, " "reusing \"%s\" as one-line description", title); p = title; } plen = strlen(p); /* Strip backspace-encoding from line. */ while (NULL != (line = memchr(p, '\b', plen))) { len = line - p; if (0 == len) { memmove(line, line + 1, plen--); continue; } memmove(line - 1, line + 1, plen - len); plen -= 2; } /* * Cut off excessive one-line descriptions. * Bad pages are not worth better heuristics. */ mpage->desc = mandoc_strndup(p, 150); fclose(stream); free(title); } /* * Put a type/word pair into the word database for this particular file. */ static void putkey(const struct mpage *mpage, char *value, uint64_t type) { putkeys(mpage, value, strlen(value), type); } /* * Grok all nodes at or below a certain mdoc node into putkey(). */ static void putmdockey(const struct mpage *mpage, const struct roff_node *n, uint64_t m, int taboo) { for ( ; NULL != n; n = n->next) { if (n->flags & taboo) continue; if (NULL != n->child) putmdockey(mpage, n->child, m, taboo); if (n->type == ROFFT_TEXT) putkey(mpage, n->string, m); } } static void parse_man(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { const struct roff_node *head, *body; char *start, *title; char byte; size_t sz; if (n == NULL) return; /* * We're only searching for one thing: the first text child in * the BODY of a NAME section. Since we don't keep track of * sections in -man, run some hoops to find out whether we're in * the correct section or not. */ if (n->type == ROFFT_BODY && n->tok == MAN_SH) { body = n; if ((head = body->parent->head) != NULL && (head = head->child) != NULL && head->next == NULL && head->type == ROFFT_TEXT && strcmp(head->string, "NAME") == 0 && body->child != NULL) { /* * Suck the entire NAME section into memory. * Yes, we might run away. * But too many manuals have big, spread-out * NAME sections over many lines. */ title = NULL; deroff(&title, body); if (NULL == title) return; /* * Go through a special heuristic dance here. * Conventionally, one or more manual names are * comma-specified prior to a whitespace, then a * dash, then a description. Try to puzzle out * the name parts here. */ start = title; for ( ;; ) { sz = strcspn(start, " ,"); if ('\0' == start[sz]) break; byte = start[sz]; start[sz] = '\0'; /* * Assume a stray trailing comma in the * name list if a name begins with a dash. */ if ('-' == start[0] || ('\\' == start[0] && '-' == start[1])) break; putkey(mpage, start, NAME_TITLE); if ( ! (mpage->name_head_done || strcasecmp(start, meta->title))) { putkey(mpage, start, NAME_HEAD); mpage->name_head_done = 1; } if (' ' == byte) { start += sz + 1; break; } assert(',' == byte); start += sz + 1; while (' ' == *start) start++; } if (start == title) { putkey(mpage, start, NAME_TITLE); if ( ! (mpage->name_head_done || strcasecmp(start, meta->title))) { putkey(mpage, start, NAME_HEAD); mpage->name_head_done = 1; } free(title); return; } while (isspace((unsigned char)*start)) start++; if (0 == strncmp(start, "-", 1)) start += 1; else if (0 == strncmp(start, "\\-\\-", 4)) start += 4; else if (0 == strncmp(start, "\\-", 2)) start += 2; else if (0 == strncmp(start, "\\(en", 4)) start += 4; else if (0 == strncmp(start, "\\(em", 4)) start += 4; while (' ' == *start) start++; /* * Cut off excessive one-line descriptions. * Bad pages are not worth better heuristics. */ mpage->desc = mandoc_strndup(start, 150); free(title); return; } } for (n = n->child; n; n = n->next) { if (NULL != mpage->desc) break; parse_man(mpage, meta, n); } } static void parse_mdoc(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { const struct mdoc_handler *handler; for (n = n->child; n != NULL; n = n->next) { if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX) continue; assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); handler = mdoc_handlers + (n->tok - MDOC_Dd); if (n->flags & handler->taboo) continue; switch (n->type) { case ROFFT_ELEM: case ROFFT_BLOCK: case ROFFT_HEAD: case ROFFT_BODY: case ROFFT_TAIL: if (handler->fp != NULL && (*handler->fp)(mpage, meta, n) == 0) break; if (handler->mask) putmdockey(mpage, n->child, handler->mask, handler->taboo); break; default: continue; } if (NULL != n->child) parse_mdoc(mpage, meta, n); } } static int parse_mdoc_Fa(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { uint64_t mask; mask = TYPE_Fa; if (n->sec == SEC_SYNOPSIS) mask |= TYPE_Vt; putmdockey(mpage, n->child, mask, 0); return 0; } static int parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { char *start, *end; size_t sz; if (SEC_SYNOPSIS != n->sec || NULL == (n = n->child) || n->type != ROFFT_TEXT) return 0; /* * Only consider those `Fd' macro fields that begin with an * "inclusion" token (versus, e.g., #define). */ if (strcmp("#include", n->string)) return 0; if ((n = n->next) == NULL || n->type != ROFFT_TEXT) return 0; /* * Strip away the enclosing angle brackets and make sure we're * not zero-length. */ start = n->string; if ('<' == *start || '"' == *start) start++; if (0 == (sz = strlen(start))) return 0; end = &start[(int)sz - 1]; if ('>' == *end || '"' == *end) end--; if (end > start) putkeys(mpage, start, end - start + 1, TYPE_In); return 0; } static void parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n) { char *cp; size_t sz; if (n->type != ROFFT_TEXT) return; /* Skip function pointer punctuation. */ cp = n->string; while (*cp == '(' || *cp == '*') cp++; sz = strcspn(cp, "()"); putkeys(mpage, cp, sz, TYPE_Fn); if (n->sec == SEC_SYNOPSIS) putkeys(mpage, cp, sz, NAME_SYN); } static int parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { uint64_t mask; if (n->child == NULL) return 0; parse_mdoc_fname(mpage, n->child); n = n->child->next; if (n != NULL && n->type == ROFFT_TEXT) { mask = TYPE_Fa; if (n->sec == SEC_SYNOPSIS) mask |= TYPE_Vt; putmdockey(mpage, n, mask, 0); } return 0; } static int parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { if (n->type != ROFFT_HEAD) return 1; if (n->child != NULL) parse_mdoc_fname(mpage, n->child); return 0; } +static int +parse_mdoc_Lb(struct mpage *mpage, const struct roff_meta *meta, + const struct roff_node *n) +{ + char *cp; + + for (n = n->child; n != NULL; n = n->next) { + if (n->flags & NODE_NOSRC) + continue; + cp = n->string; + if (n->sec == SEC_SYNOPSIS) + mandoc_asprintf(&cp, "lib%s", cp); + putkey(mpage, cp, TYPE_Lb); + if (n->sec == SEC_SYNOPSIS) + free(cp); + } + return 0; +} + static int parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { char *cp; if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY) return 0; if (n->child != NULL && n->child->next == NULL && n->child->type == ROFFT_TEXT) return 1; cp = NULL; deroff(&cp, n); if (cp != NULL) { putkey(mpage, cp, TYPE_Vt | (n->tok == MDOC_Va || n->type == ROFFT_BODY ? TYPE_Va : 0)); free(cp); } return 0; } static int parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { char *cp; if (NULL == (n = n->child)) return 0; if (NULL == n->next) { putkey(mpage, n->string, TYPE_Xr); return 0; } mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string); putkey(mpage, cp, TYPE_Xr); free(cp); return 0; } static int parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { if (n->type == ROFFT_BODY) deroff(&mpage->desc, n); return 0; } static int parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { if (SEC_NAME == n->sec) putmdockey(mpage, n->child, NAME_TITLE, 0); else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) { if (n->child == NULL) putkey(mpage, meta->name, NAME_SYN); else putmdockey(mpage, n->child, NAME_SYN, 0); } if ( ! (mpage->name_head_done || n->child == NULL || n->child->string == NULL || strcasecmp(n->child->string, meta->title))) { putkey(mpage, n->child->string, NAME_HEAD); mpage->name_head_done = 1; } return 0; } static int parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD; } static int parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { return n->type == ROFFT_HEAD; } /* * Add a string to the hash table for the current manual. * Each string has a bitmask telling which macros it belongs to. * When we finish the manual, we'll dump the table. */ static void putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v) { struct ohash *htab; struct str *s; const char *end; unsigned int slot; int i, mustfree; if (0 == sz) return; mustfree = render_string(&cp, &sz); if (TYPE_Nm & v) { htab = &names; v &= name_mask; if (v & NAME_FIRST) name_mask &= ~NAME_FIRST; if (debug > 1) say(mpage->mlinks->file, "Adding name %*s, bits=0x%llx", (int)sz, cp, (unsigned long long)v); } else { htab = &strings; if (debug > 1) for (i = 0; i < KEY_MAX; i++) if ((uint64_t)1 << i & v) say(mpage->mlinks->file, "Adding key %s=%*s", mansearch_keynames[i], (int)sz, cp); } end = cp + sz; slot = ohash_qlookupi(htab, cp, &end); s = ohash_find(htab, slot); if (NULL != s && mpage == s->mpage) { s->mask |= v; return; } else if (NULL == s) { s = mandoc_calloc(1, sizeof(struct str) + sz + 1); memcpy(s->key, cp, sz); ohash_insert(htab, slot, s); } s->mpage = mpage; s->mask = v; if (mustfree) free(cp); } /* * Take a Unicode codepoint and produce its UTF-8 encoding. * This isn't the best way to do this, but it works. * The magic numbers are from the UTF-8 packaging. * Read the UTF-8 spec or the utf8(7) manual page for details. */ static size_t utf8(unsigned int cp, char out[5]) { size_t rc; if (cp <= 0x7f) { rc = 1; out[0] = (char)cp; } else if (cp <= 0x7ff) { rc = 2; out[0] = (cp >> 6 & 31) | 192; out[1] = (cp & 63) | 128; } else if (cp >= 0xd800 && cp <= 0xdfff) { rc = 0; /* reject UTF-16 surrogate */ } else if (cp <= 0xffff) { rc = 3; out[0] = (cp >> 12 & 15) | 224; out[1] = (cp >> 6 & 63) | 128; out[2] = (cp & 63) | 128; } else if (cp <= 0x10ffff) { rc = 4; out[0] = (cp >> 18 & 7) | 240; out[1] = (cp >> 12 & 63) | 128; out[2] = (cp >> 6 & 63) | 128; out[3] = (cp & 63) | 128; } else rc = 0; out[rc] = '\0'; return rc; } /* * If the string contains escape sequences, * replace it with an allocated rendering and return 1, * such that the caller can free it after use. * Otherwise, do nothing and return 0. */ static int render_string(char **public, size_t *psz) { const char *src, *scp, *addcp, *seq; char *dst; size_t ssz, dsz, addsz; char utfbuf[7], res[6]; int seqlen, unicode; res[0] = '\\'; res[1] = '\t'; res[2] = ASCII_NBRSP; res[3] = ASCII_HYPH; res[4] = ASCII_BREAK; res[5] = '\0'; src = scp = *public; ssz = *psz; dst = NULL; dsz = 0; while (scp < src + *psz) { /* Leave normal characters unchanged. */ if (strchr(res, *scp) == NULL) { if (dst != NULL) dst[dsz++] = *scp; scp++; continue; } /* * Found something that requires replacing, * make sure we have a destination buffer. */ if (dst == NULL) { dst = mandoc_malloc(ssz + 1); dsz = scp - src; memcpy(dst, src, dsz); } /* Handle single-char special characters. */ switch (*scp) { case '\\': break; case '\t': case ASCII_NBRSP: dst[dsz++] = ' '; scp++; continue; case ASCII_HYPH: dst[dsz++] = '-'; /* FALLTHROUGH */ case ASCII_BREAK: scp++; continue; default: abort(); } /* * Found an escape sequence. * Read past the slash, then parse it. * Ignore everything except characters. */ scp++; switch (mandoc_escape(&scp, &seq, &seqlen)) { case ESCAPE_UNICODE: unicode = mchars_num2uc(seq + 1, seqlen - 1); break; case ESCAPE_NUMBERED: unicode = mchars_num2char(seq, seqlen); break; case ESCAPE_SPECIAL: unicode = mchars_spec2cp(seq, seqlen); break; default: unicode = -1; break; } if (unicode <= 0) continue; /* * Render the special character * as either UTF-8 or ASCII. */ if (write_utf8) { addsz = utf8(unicode, utfbuf); if (addsz == 0) continue; addcp = utfbuf; } else { addcp = mchars_uc2str(unicode); if (addcp == NULL) continue; if (*addcp == ASCII_NBRSP) addcp = " "; addsz = strlen(addcp); } /* Copy the rendered glyph into the stream. */ ssz += addsz; dst = mandoc_realloc(dst, ssz + 1); memcpy(dst + dsz, addcp, addsz); dsz += addsz; } if (dst != NULL) { *public = dst; *psz = dsz; } /* Trim trailing whitespace and NUL-terminate. */ while (*psz > 0 && (*public)[*psz - 1] == ' ') --*psz; if (dst != NULL) { (*public)[*psz] = '\0'; return 1; } else return 0; } static void dbadd_mlink(const struct mlink *mlink) { dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE); dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec); dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->fsec); dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch); dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file); } /* * Flush the current page's terms (and their bits) into the database. * Also, handle escape sequences at the last possible moment. */ static void dbadd(struct dba *dba, struct mpage *mpage) { struct mlink *mlink; struct str *key; char *cp; uint64_t mask; size_t i; unsigned int slot; int mustfree; mlink = mpage->mlinks; if (nodb) { for (key = ohash_first(&names, &slot); NULL != key; key = ohash_next(&names, &slot)) free(key); for (key = ohash_first(&strings, &slot); NULL != key; key = ohash_next(&strings, &slot)) free(key); if (0 == debug) return; while (NULL != mlink) { fputs(mlink->name, stdout); if (NULL == mlink->next || strcmp(mlink->dsec, mlink->next->dsec) || strcmp(mlink->fsec, mlink->next->fsec) || strcmp(mlink->arch, mlink->next->arch)) { putchar('('); if ('\0' == *mlink->dsec) fputs(mlink->fsec, stdout); else fputs(mlink->dsec, stdout); if ('\0' != *mlink->arch) printf("/%s", mlink->arch); putchar(')'); } mlink = mlink->next; if (NULL != mlink) fputs(", ", stdout); } printf(" - %s\n", mpage->desc); return; } if (debug) say(mlink->file, "Adding to database"); cp = mpage->desc; i = strlen(cp); mustfree = render_string(&cp, &i); mpage->dba = dba_page_new(dba->pages, *mpage->arch == '\0' ? mlink->arch : mpage->arch, cp, mlink->file, mpage->form); if (mustfree) free(cp); dba_page_add(mpage->dba, DBP_SECT, mpage->sec); while (mlink != NULL) { dbadd_mlink(mlink); mlink = mlink->next; } for (key = ohash_first(&names, &slot); NULL != key; key = ohash_next(&names, &slot)) { assert(key->mpage == mpage); dba_page_alias(mpage->dba, key->key, key->mask); free(key); } for (key = ohash_first(&strings, &slot); NULL != key; key = ohash_next(&strings, &slot)) { assert(key->mpage == mpage); i = 0; for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) { if (key->mask & mask) dba_macro_add(dba->macros, i, key->key, mpage->dba); i++; } free(key); } } static void dbprune(struct dba *dba) { struct dba_array *page, *files; char *file; dba_array_FOREACH(dba->pages, page) { files = dba_array_get(page, DBP_FILE); dba_array_FOREACH(files, file) { if (*file < ' ') file++; if (ohash_find(&mlinks, ohash_qlookup(&mlinks, file)) != NULL) { if (debug) say(file, "Deleting from database"); dba_array_del(dba->pages); break; } } } } /* * Write the database from memory to disk. */ static void dbwrite(struct dba *dba) { struct stat sb1, sb2; char tfn[33], *cp1, *cp2; off_t i; int fd1, fd2; /* * Do not write empty databases, and delete existing ones * when makewhatis -u causes them to become empty. */ dba_array_start(dba->pages); if (dba_array_next(dba->pages) == NULL) { if (unlink(MANDOC_DB) == -1 && errno != ENOENT) say(MANDOC_DB, "&unlink"); return; } /* * Build the database in a temporary file, * then atomically move it into place. */ if (dba_write(MANDOC_DB "~", dba) != -1) { if (rename(MANDOC_DB "~", MANDOC_DB) == -1) { exitcode = (int)MANDOCLEVEL_SYSERR; say(MANDOC_DB, "&rename"); unlink(MANDOC_DB "~"); } return; } /* * We lack write permission and cannot replace the database * file, but let's at least check whether the data changed. */ (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn)); if (mkdtemp(tfn) == NULL) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&%s", tfn); return; } cp1 = cp2 = MAP_FAILED; fd1 = fd2 = -1; (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn)); if (dba_write(tfn, dba) == -1) { say(tfn, "&dba_write"); goto err; } if ((fd1 = open(MANDOC_DB, O_RDONLY)) == -1) { say(MANDOC_DB, "&open"); goto err; } if ((fd2 = open(tfn, O_RDONLY)) == -1) { say(tfn, "&open"); goto err; } if (fstat(fd1, &sb1) == -1) { say(MANDOC_DB, "&fstat"); goto err; } if (fstat(fd2, &sb2) == -1) { say(tfn, "&fstat"); goto err; } if (sb1.st_size != sb2.st_size) goto err; if ((cp1 = mmap(NULL, sb1.st_size, PROT_READ, MAP_PRIVATE, fd1, 0)) == MAP_FAILED) { say(MANDOC_DB, "&mmap"); goto err; } if ((cp2 = mmap(NULL, sb2.st_size, PROT_READ, MAP_PRIVATE, fd2, 0)) == MAP_FAILED) { say(tfn, "&mmap"); goto err; } for (i = 0; i < sb1.st_size; i++) if (cp1[i] != cp2[i]) goto err; goto out; err: exitcode = (int)MANDOCLEVEL_SYSERR; say(MANDOC_DB, "Data changed, but cannot replace database"); out: if (cp1 != MAP_FAILED) munmap(cp1, sb1.st_size); if (cp2 != MAP_FAILED) munmap(cp2, sb2.st_size); if (fd1 != -1) close(fd1); if (fd2 != -1) close(fd2); unlink(tfn); *strrchr(tfn, '/') = '\0'; rmdir(tfn); } static int set_basedir(const char *targetdir, int report_baddir) { static char startdir[PATH_MAX]; static int getcwd_status; /* 1 = ok, 2 = failure */ static int chdir_status; /* 1 = changed directory */ /* * Remember the original working directory, if possible. * This will be needed if the second or a later directory * on the command line is given as a relative path. * Do not error out if the current directory is not * searchable: Maybe it won't be needed after all. */ if (getcwd_status == 0) { if (getcwd(startdir, sizeof(startdir)) == NULL) { getcwd_status = 2; (void)strlcpy(startdir, strerror(errno), sizeof(startdir)); } else getcwd_status = 1; } /* * We are leaving the old base directory. * Do not use it any longer, not even for messages. */ *basedir = '\0'; basedir_len = 0; /* * If and only if the directory was changed earlier and * the next directory to process is given as a relative path, * first go back, or bail out if that is impossible. */ if (chdir_status && *targetdir != '/') { if (getcwd_status == 2) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "getcwd: %s", startdir); return 0; } if (chdir(startdir) == -1) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&chdir %s", startdir); return 0; } } /* * Always resolve basedir to the canonicalized absolute * pathname and append a trailing slash, such that * we can reliably check whether files are inside. */ if (realpath(targetdir, basedir) == NULL) { if (report_baddir || errno != ENOENT) { exitcode = (int)MANDOCLEVEL_BADARG; say("", "&%s: realpath", targetdir); } *basedir = '\0'; return 0; } else if (chdir(basedir) == -1) { if (report_baddir || errno != ENOENT) { exitcode = (int)MANDOCLEVEL_BADARG; say("", "&chdir"); } *basedir = '\0'; return 0; } chdir_status = 1; basedir_len = strlen(basedir); if (basedir[basedir_len - 1] != '/') { if (basedir_len >= PATH_MAX - 1) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "Filename too long"); *basedir = '\0'; basedir_len = 0; return 0; } basedir[basedir_len++] = '/'; basedir[basedir_len] = '\0'; } return 1; } #ifdef READ_ALLOWED_PATH static int read_allowed(const char *candidate) { const char *cp; size_t len; for (cp = READ_ALLOWED_PATH;; cp += len) { while (*cp == ':') cp++; if (*cp == '\0') return 0; len = strcspn(cp, ":"); if (strncmp(candidate, cp, len) == 0) return 1; } } #endif static void say(const char *file, const char *format, ...) { va_list ap; int use_errno; if (*basedir != '\0') fprintf(stderr, "%s", basedir); if (*basedir != '\0' && *file != '\0') fputc('/', stderr); if (*file != '\0') fprintf(stderr, "%s", file); use_errno = 1; if (format != NULL) { switch (*format) { case '&': format++; break; case '\0': format = NULL; break; default: use_errno = 0; break; } } if (format != NULL) { if (*basedir != '\0' || *file != '\0') fputs(": ", stderr); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } if (use_errno) { if (*basedir != '\0' || *file != '\0' || format != NULL) fputs(": ", stderr); perror(NULL); } else fputc('\n', stderr); } diff --git a/contrib/mandoc/mdoc.7 b/contrib/mandoc/mdoc.7 index edd74eafa328..55cc7fae688d 100644 --- a/contrib/mandoc/mdoc.7 +++ b/contrib/mandoc/mdoc.7 @@ -1,3264 +1,3277 @@ -.\" $Id: mdoc.7,v 1.296 2025/01/27 03:17:33 schwarze Exp $ +.\" $Id: mdoc.7,v 1.299 2025/06/13 16:18:28 schwarze Exp $ .\" +.\" Copyright (c) 2010-2021, 2024, 2025 Ingo Schwarze .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons -.\" Copyright (c) 2010, 2011, 2013-2020 Ingo Schwarze .\" .\" 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 AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. .\" -.Dd $Mdocdate: January 27 2025 $ +.Dd $Mdocdate: June 13 2025 $ .Dt MDOC 7 .Os .Sh NAME .Nm mdoc .Nd semantic markup language for formatting manual pages .Sh DESCRIPTION The .Nm mdoc language supports authoring of manual pages for the .Xr man 1 utility by allowing semantic annotations of words, phrases, page sections and complete manual pages. Such annotations are used by formatting tools to achieve a uniform presentation across all manuals written in .Nm , and to support hyperlinking if supported by the output medium. .Pp This reference document describes the structure of manual pages and the syntax and usage of the .Nm language. The reference implementation of a parsing and formatting tool is .Xr mandoc 1 ; the .Sx COMPATIBILITY section describes compatibility with other implementations. .Pp In an .Nm document, lines beginning with the control character .Sq \&. are called .Dq macro lines . The first word is the macro name. It consists of two or three letters. Most macro names begin with a capital letter. For a list of available macros, see .Sx MACRO OVERVIEW . The words following the macro name are arguments to the macro, optionally including the names of other, callable macros; see .Sx MACRO SYNTAX for details. .Pp Lines not beginning with the control character are called .Dq text lines . They provide free-form text to be printed; the formatting of the text depends on the respective processing context: .Bd -literal -offset indent \&.Sh Macro lines change control state. Text lines are interpreted within the current state. .Ed .Pp Many aspects of the basic syntax of the .Nm language are based on the .Xr roff 7 language; see the .Em LANGUAGE SYNTAX and .Em MACRO SYNTAX sections in the .Xr roff 7 manual for details, in particular regarding comments, escape sequences, whitespace, and quoting. However, using .Xr roff 7 requests in .Nm documents is discouraged; .Xr mandoc 1 supports some of them merely for backward compatibility. .Sh MANUAL STRUCTURE A well-formed .Nm document consists of a document prologue followed by one or more sections. .Pp The prologue, which consists of the .Ic \&Dd , .Ic \&Dt , and .Ic \&Os macros in that order, is required for every document. .Pp The first section (sections are denoted by .Ic \&Sh ) must be the NAME section, consisting of at least one .Ic \&Nm followed by .Ic \&Nd . .Pp Following that, convention dictates specifying at least the .Em SYNOPSIS and .Em DESCRIPTION sections, although this varies between manual sections. .Pp The following is a well-formed skeleton .Nm file for a utility .Qq progname : .Bd -literal -offset indent \&.Dd $\&Mdocdate$ \&.Dt PROGNAME section \&.Os \&.Sh NAME \&.Nm progname \&.Nd one line about what it does \&.\e\(dq .Sh LIBRARY \&.\e\(dq For sections 2, 3, and 9 only. \&.\e\(dq Not used in OpenBSD. \&.Sh SYNOPSIS \&.Nm progname \&.Op Fl options \&.Ar \&.Sh DESCRIPTION The \&.Nm utility processes files ... \&.\e\(dq .Sh CONTEXT \&.\e\(dq For section 9 functions only. \&.\e\(dq .Sh HARDWARE \&.\e\(dq For section 4 only. \&.\e\(dq Not used in OpenBSD. \&.\e\(dq .Sh IMPLEMENTATION NOTES \&.\e\(dq Not used in OpenBSD. \&.\e\(dq .Sh RETURN VALUES \&.\e\(dq For sections 2, 3, and 9 function return values only. \&.\e\(dq .Sh ENVIRONMENT \&.\e\(dq For sections 1, 6, 7, and 8 only. \&.\e\(dq .Sh FILES \&.\e\(dq .Sh EXIT STATUS \&.\e\(dq For sections 1, 6, and 8 only. \&.\e\(dq .Sh EXAMPLES \&.\e\(dq .Sh DIAGNOSTICS \&.\e\(dq For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only. \&.\e\(dq .Sh ERRORS \&.\e\(dq For sections 2, 3, 4, and 9 errno settings only. \&.\e\(dq .Sh SEE ALSO \&.\e\(dq .Xr foobar 1 \&.\e\(dq .Sh STANDARDS \&.\e\(dq .Sh HISTORY \&.\e\(dq .Sh AUTHORS \&.\e\(dq .Sh CAVEATS \&.\e\(dq .Sh BUGS \&.\e\(dq .Sh SECURITY CONSIDERATIONS \&.\e\(dq Not used in OpenBSD. .Ed .Pp The sections in an .Nm document are conventionally ordered as they appear above. Sections should be composed as follows: .Bl -ohang -offset Ds .It Em NAME The name(s) and a one line description of the documented material. The syntax for this as follows: .Bd -literal -offset indent \&.Nm name0 , \&.Nm name1 , \&.Nm name2 \&.Nd a one line description .Ed .Pp Multiple .Sq \&Nm names should be separated by commas. .Pp The .Ic \&Nm macro(s) must precede the .Ic \&Nd macro. .Pp See .Ic \&Nm and .Ic \&Nd . .It Em LIBRARY -The name of the library containing the documented material, which is -assumed to be a function in a section 2, 3, or 9 manual. -The syntax for this is as follows: -.Bd -literal -offset indent -\&.Lb libarm -.Ed -.Pp -See -.Ic \&Lb . +The name of the library containing the documented functions. +Using this section is no longer recommended. +If any +.Ic \&Lb +macro is needed, put it at the beginning of the +.Em SYNOPSIS +section instead. .It Em SYNOPSIS Documents the utility invocation syntax, function call syntax, or device configuration. .Pp For the first, utilities (sections 1, 6, and 8), this is generally structured as follows: .Bd -literal -offset indent \&.Nm bar \&.Op Fl v \&.Op Fl o Ar file \&.Op Ar \&.Nm foo \&.Op Fl v \&.Op Fl o Ar file \&.Op Ar .Ed .Pp Commands should be ordered alphabetically. .Pp For the second, function calls (sections 2, 3, 9): .Bd -literal -offset indent +\&.Lb libname \e" unless the functions are in libc \&.In header.h \&.Vt extern const char *global; -\&.Ft "char *" +\&.Ft char * \&.Fn foo "const char *src" -\&.Ft "char *" +\&.Ft char * \&.Fn bar "const char *src" .Ed .Pp Ordering of .Ic \&In , .Ic \&Vt , .Ic \&Fn , and .Ic \&Fo macros should follow C header-file conventions. .Pp And for the third, configurations (section 4): .Bd -literal -offset indent \&.Cd \(dqit* at isa? port 0x2e\(dq \&.Cd \(dqit* at isa? port 0x4e\(dq .Ed .Pp Manuals not in these sections generally don't need a .Em SYNOPSIS . .Pp Some macros are displayed differently in the .Em SYNOPSIS section, particularly .Ic \&Nm , .Ic \&Cd , .Ic \&Fd , .Ic \&Fn , .Ic \&Fo , .Ic \&In , .Ic \&Vt , and .Ic \&Ft . All of these macros are output on their own line. If two such dissimilar macros are pairwise invoked (except for .Ic \&Ft before .Ic \&Fo or .Ic \&Fn ) , they are separated by a vertical space, unless in the case of .Ic \&Fo , .Ic \&Fn , and .Ic \&Ft , which are always separated by vertical space. .Pp When text and macros following an .Ic \&Nm macro starting an input line span multiple output lines, all output lines but the first will be indented to align with the text immediately following the .Ic \&Nm macro, up to the next .Ic \&Nm , .Ic \&Sh , or .Ic \&Ss macro or the end of an enclosing block, whichever comes first. .It Em DESCRIPTION This begins with an expansion of the brief, one line description in .Em NAME : .Bd -literal -offset indent The \&.Nm utility does this, that, and the other. .Ed .Pp It usually follows with a breakdown of the options (if documenting a command), such as: .Bd -literal -offset indent The options are as follows: \&.Bl \-tag \-width Ds \&.It Fl v Print verbose information. \&.El .Ed .Pp List the options in alphabetical order, uppercase before lowercase for each letter and with no regard to whether an option takes an argument. Put digits in ascending order before all letter options. .Pp Manuals not documenting a command won't include the above fragment. .Pp Since the .Em DESCRIPTION section usually contains most of the text of a manual, longer manuals often use the .Ic \&Ss macro to form subsections. In very long manuals, the .Em DESCRIPTION may be split into multiple sections, each started by an .Ic \&Sh macro followed by a non-standard section name, and each having several subsections, like in the present .Nm manual. .It Em CONTEXT This section lists the contexts in which functions can be called in section 9. The contexts are autoconf, process, or interrupt. .It Em HARDWARE This section lists the hardware support provided by kernel modules in section 4. FreeBSD Hardware Compatibility Notes are generated from this section. .It Em IMPLEMENTATION NOTES Implementation-specific notes should be kept here. This is useful when implementing standard functions that may have side effects or notable algorithmic implications. .It Em RETURN VALUES This section documents the return values of functions in sections 2, 3, and 9. .Pp See .Ic \&Rv . .It Em ENVIRONMENT Lists the environment variables used by the utility, and explains the syntax and semantics of their values. The .Xr environ 7 manual provides examples of typical content and formatting. .Pp See .Ic \&Ev . .It Em FILES Documents files used. It's helpful to document both the file name and a short description of how the file is used (created, modified, etc.). .Pp See .Ic \&Pa . .It Em EXIT STATUS This section documents the command exit status for section 1, 6, and 8 utilities. Historically, this information was described in .Em DIAGNOSTICS , a practise that is now discouraged. .Pp See .Ic \&Ex . .It Em EXAMPLES Example usages. This often contains snippets of well-formed, well-tested invocations. Make sure that examples work properly! .It Em DIAGNOSTICS Documents error messages. In section 4 and 9 manuals, these are usually messages printed by the kernel to the console and to the kernel log. In section 1, 6, 7, and 8, these are usually messages printed by userland programs to the standard error output. .Pp Historically, this section was used in place of .Em EXIT STATUS for manuals in sections 1, 6, and 8; however, this practise is discouraged. .Pp See .Ic \&Bl .Fl diag . .It Em ERRORS Documents .Xr errno 2 settings in sections 2, 3, 4, and 9. .Pp See .Ic \&Er . .It Em SEE ALSO References other manuals with related topics. This section should exist for most manuals. Cross-references should conventionally be ordered first by section, then alphabetically (ignoring case). .Pp References to other documentation concerning the topic of the manual page, for example authoritative books or journal articles, may also be provided in this section. .Pp See .Ic \&Rs and .Ic \&Xr . .It Em STANDARDS References any standards implemented or used. If not adhering to any standards, the .Em HISTORY section should be used instead. .Pp See .Ic \&St . .It Em HISTORY A brief history of the subject, including where it was first implemented, and when it was ported to or reimplemented for the operating system at hand. .It Em AUTHORS Credits to the person or persons who wrote the code and/or documentation. Authors should generally be noted by both name and email address. .Pp See .Ic \&An . .It Em CAVEATS Common misuses and misunderstandings should be explained in this section. .It Em BUGS Known bugs, limitations, and work-arounds should be described in this section. .It Em SECURITY CONSIDERATIONS Documents any security precautions that operators should consider. .El .Sh MACRO OVERVIEW This overview is sorted such that macros of similar purpose are listed together, to help find the best macro for any given purpose. Deprecated macros are not included in the overview, but can be found below in the alphabetical .Sx MACRO REFERENCE . .Ss Document preamble and NAME section macros .Bl -column "Brq, Bro, Brc" description .It Ic \&Dd Ta document date: Cm $\&Mdocdate$ | Ar month day , year .It Ic \&Dt Ta document title: Ar TITLE section Op Ar arch .It Ic \&Os Ta operating system footer: Op Ar footer text .It Ic \&Nm Ta document name (one argument) .It Ic \&Nd Ta document description (one line) .El .Ss Sections and cross references .Bl -column "Brq, Bro, Brc" description .It Ic \&Sh Ta section header (one line) .It Ic \&Ss Ta subsection header (one line) .It Ic \&Sx Ta internal cross reference to a section or subsection .It Ic \&Xr Ta cross reference to another manual page: Ar name section .It Ic \&Tg Ta tag the definition of a Ar term Pq <= 1 arguments .It Ic \&Pp Ta start a text paragraph (no arguments) .El .Ss Displays and lists .Bl -column "Brq, Bro, Brc" description .It Ic \&Bd , \&Ed Ta display block: .Fl Ar type .Op Fl offset Ar width .Op Fl compact .It Ic \&D1 Ta indented display (one line) .It Ic \&Dl Ta indented literal display (one line) -.It Ic \&Ql Ta in-line literal display: Ql text +.It Ic \&Ql Ta normal in-line literal display: Ql text +.It Ic \&Li Ta unquoted in-line literal display: Li text .It Ic \&Bl , \&El Ta list block: .Fl Ar type .Op Fl width Ar val .Op Fl offset Ar val .Op Fl compact .It Ic \&It Ta list item (syntax depends on Fl Ar type ) .It Ic \&Ta Ta table cell separator in Ic \&Bl Fl column No lists .It Ic \&Rs , \&%* , \&Re Ta bibliographic block (references) .El .Ss Spacing control .Bl -column "Brq, Bro, Brc" description .It Ic \&Pf Ta prefix, no following horizontal space (one argument) .It Ic \&Ns Ta roman font, no preceding horizontal space (no arguments) .It Ic \&Ap Ta apostrophe without surrounding whitespace (no arguments) .It Ic \&Sm Ta switch horizontal spacing mode: Op Cm on | off .It Ic \&Bk , \&Ek Ta keep block: Fl words .El .Ss Semantic markup for command line utilities .Bl -column "Brq, Bro, Brc" description .It Ic \&Nm Ta start a SYNOPSIS block with the name of a utility .It Ic \&Fl Ta command line options (flags) (>=0 arguments) .It Ic \&Cm Ta command modifier (>0 arguments) .It Ic \&Ar Ta command arguments (>=0 arguments) .It Ic \&Op , \&Oo , \&Oc Ta optional syntax elements (enclosure) .It Ic \&Ic Ta internal or interactive command (>0 arguments) .It Ic \&Ev Ta environmental variable (>0 arguments) .It Ic \&Pa Ta file system path (>=0 arguments) .El .Ss Semantic markup for function libraries .Bl -column "Brq, Bro, Brc" description -.It Ic \&Lb Ta function library (one argument) +.It Ic \&Lb Ta function library (>0 arguments) .It Ic \&In Ta include file (one argument) .It Ic \&Fd Ta other preprocessor directive (>0 arguments) .It Ic \&Ft Ta function type (>0 arguments) .It Ic \&Fo , \&Fc Ta function block: Ar funcname .It Ic \&Fn Ta function name: Ar funcname Op Ar argument ... .It Ic \&Fa Ta function argument (>0 arguments) .It Ic \&Vt Ta variable type (>0 arguments) .It Ic \&Va Ta variable name (>0 arguments) .It Ic \&Dv Ta defined variable or preprocessor constant (>0 arguments) .It Ic \&Er Ta error constant (>0 arguments) .It Ic \&Ev Ta environmental variable (>0 arguments) .El .Ss Various semantic markup .Bl -column "Brq, Bro, Brc" description .It Ic \&An Ta author name (>0 arguments) .It Ic \&Lk Ta hyperlink: Ar uri Op Ar display_name .It Ic \&Mt Ta Do mailto Dc hyperlink: Ar localpart Ns @ Ns Ar domain .It Ic \&Cd Ta kernel configuration declaration (>0 arguments) .It Ic \&Ad Ta memory address (>0 arguments) .It Ic \&Ms Ta mathematical symbol (>0 arguments) .El .Ss Physical markup .Bl -column "Brq, Bro, Brc" description .It Ic \&Em Ta italic font or underline (emphasis) (>0 arguments) .It Ic \&Sy Ta boldface font (symbolic) (>0 arguments) .It Ic \&No Ta return to roman font (normal) (>0 arguments) .It Ic \&Bf , \&Ef Ta font block: Fl Ar type | Cm \&Em | \&Li | \&Sy .El .Ss Physical enclosures .Bl -column "Brq, Bro, Brc" description .It Ic \&Dq , \&Do , \&Dc Ta enclose in typographic double quotes: Dq text .It Ic \&Qq , \&Qo , \&Qc Ta enclose in typewriter double quotes: Qq text .It Ic \&Sq , \&So , \&Sc Ta enclose in single quotes: Sq text .It Ic \&Pq , \&Po , \&Pc Ta enclose in parentheses: Pq text .It Ic \&Bq , \&Bo , \&Bc Ta enclose in square brackets: Bq text .It Ic \&Brq , \&Bro , \&Brc Ta enclose in curly braces: Brq text .It Ic \&Aq , \&Ao , \&Ac Ta enclose in angle brackets: Aq text .It Ic \&Eo , \&Ec Ta generic enclosure .El .Ss Text production .Bl -column "Brq, Bro, Brc" description .It Ic \&Ex Fl std Ta standard command exit values: Op Ar utility ... .It Ic \&Rv Fl std Ta standard function return values: Op Ar function ... .It Ic \&St Ta reference to a standards document (one argument) .It Ic \&At Ta At .It Ic \&Bx Ta Bx .It Ic \&Bsx Ta Bsx .It Ic \&Nx Ta Nx .It Ic \&Fx Ta Fx .It Ic \&Ox Ta Ox .It Ic \&Dx Ta Dx .El .Sh MACRO REFERENCE This section is a canonical reference of all macros, arranged alphabetically. For the scoping of individual macros, see .Sx MACRO SYNTAX . .Bl -tag -width 3n .It Ic \&%A Ar first_name ... last_name Author name of an .Ic \&Rs block. Multiple authors should each be accorded their own .Ic \%%A line. Author names should be ordered with full or abbreviated forename(s) first, then full surname. .It Ic \&%B Ar title Book title of an .Ic \&Rs block. This macro may also be used in a non-bibliographic context when referring to book titles. .It Ic \&%C Ar location Publication city or location of an .Ic \&Rs block. .It Ic \&%D Oo Ar month day , Oc Ar year Publication date of an .Ic \&Rs block. Provide the full English name of the .Ar month and all four digits of the .Ar year . .It Ic \&%I Ar name Publisher or issuer name of an .Ic \&Rs block. .It Ic \&%J Ar name Journal name of an .Ic \&Rs block. .It Ic \&%N Ar number Issue number (usually for journals) of an .Ic \&Rs block. .It Ic \&%O Ar line Optional information of an .Ic \&Rs block. .It Ic \&%P Ar number Book or journal page number of an .Ic \&Rs block. Conventionally, the argument starts with .Ql p.\& for a single page or .Ql pp.\& for a range of pages, for example: .Pp .Dl .%P pp. 42\e(en47 .It Ic \&%Q Ar name Institutional author (school, government, etc.) of an .Ic \&Rs block. Multiple institutional authors should each be accorded their own .Ic \&%Q line. .It Ic \&%R Ar name Technical report name of an .Ic \&Rs block. .It Ic \&%T Ar title Article title of an .Ic \&Rs block. This macro may also be used in a non-bibliographical context when referring to article titles. .It Ic \&%U Ar protocol Ns :// Ns Ar path URI of reference document. .It Ic \&%V Ar number Volume number of an .Ic \&Rs block. .It Ic \&Ac Close an .Ic \&Ao block. Does not have any tail arguments. .Tg Ad .It Ic \&Ad Ar address Memory address. Do not use this for postal addresses. .Pp Examples: .Dl \&.Ad [0,$] .Dl \&.Ad 0x00000000 .Tg An .It Ic \&An Fl split | nosplit | Ar first_name ... last_name Author name. Can be used both for the authors of the program, function, or driver documented in the manual, or for the authors of the manual itself. Requires either the name of an author or one of the following arguments: .Pp .Bl -tag -width "-nosplitX" -offset indent -compact .It Fl split Start a new output line before each subsequent invocation of .Ic \&An . .It Fl nosplit The opposite of .Fl split . .El .Pp The default is .Fl nosplit . The effect of selecting either of the .Fl split modes ends at the beginning of the .Em AUTHORS section. In the .Em AUTHORS section, the default is .Fl nosplit for the first author listing and .Fl split for all other author listings. .Pp Examples: .Dl \&.An -nosplit .Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv .It Ic \&Ao Ar block Begin a block enclosed by angle brackets. Does not have any head arguments. This macro is almost never useful. See .Ic \&Aq for more details. .Tg Ap .It Ic \&Ap Inserts an apostrophe without any surrounding whitespace. This is generally used as a grammatical device when referring to the verb form of a function. .Pp Examples: .Dl \&.Fn execve \&Ap d .Tg Aq .It Ic \&Aq Ar line Enclose the rest of the input line in angle brackets. The only important use case is for email addresses. See .Ic \&Mt for an example. .Pp Occasionally, it is used for names of characters and keys, for example: .Bd -literal -offset indent Press the \&.Aq escape key to ... .Ed .Pp For URIs, use .Ic \&Lk instead, and .Ic \&In for .Dq #include directives. Never wrap .Ic \&Ar in .Ic \&Aq . .Pp Since .Ic \&Aq usually renders with non-ASCII characters in non-ASCII output modes, do not use it where the ASCII characters .Sq < and .Sq > are required as syntax elements. Instead, use these characters directly in such cases, combining them with the macros .Ic \&Pf , .Ic \&Ns , or .Ic \&Eo as needed. .Pp See also .Ic \&Ao . .Tg Ar .It Ic \&Ar Op Ar placeholder ... Command arguments. If an argument is not provided, the string .Dq file ...\& is used as a default. .Pp Examples: .Dl ".Fl o Ar file" .Dl ".Ar" .Dl ".Ar arg1 , arg2 ." .Pp The arguments to the .Ic \&Ar macro are names and placeholders for command arguments; for fixed strings to be passed verbatim as arguments, use .Ic \&Fl or .Ic \&Cm . .Tg At .It Ic \&At Op Ar version Formats an .At version. Accepts one optional argument: .Pp .Bl -tag -width "v[1-7] | 32vX" -offset indent -compact .It Cm v[1-7] | 32v A version of .At . .It Cm III .At III . .It Cm V | V.[1-4] A version of .At V . .El .Pp Note that these arguments do not begin with a hyphen. .Pp Examples: .Dl \&.At .Dl \&.At III .Dl \&.At V.1 .Pp See also .Ic \&Bsx , .Ic \&Bx , .Ic \&Dx , .Ic \&Fx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Bc Close a .Ic \&Bo block. Does not have any tail arguments. .Tg Bd .It Ic \&Bd Fl Ns Ar type Oo Fl offset Ar width Oc Op Fl compact Begin a display block. Display blocks are used to select a different indentation and justification than the one used by the surrounding text. They may contain both macro lines and text lines. By default, a display block is preceded by a vertical space. .Pp The .Ar type must be one of the following: .Bl -tag -width 13n -offset indent .It Fl centered Produce one output line from each input line, and center-justify each line. Using this display type is not recommended; many .Nm implementations render it poorly. .It Fl filled Change the positions of line breaks to fill each line, and left- and right-justify the resulting block. .It Fl literal Produce one output line from each input line, and do not justify the block at all. Preserve white space as it appears in the input. Always use a constant-width font. Use this for displaying source code. .It Fl ragged Change the positions of line breaks to fill each line, and left-justify the resulting block. .It Fl unfilled The same as .Fl literal , but using the same font as for normal text, which is a variable width font if supported by the output device. .El .Pp The .Ar type must be provided first. Additional arguments may follow: .Bl -tag -width 13n -offset indent .It Fl offset Ar width Indent the display by the .Ar width , which may be one of the following: .Bl -item .It One of the pre-defined strings .Cm indent , the width of a standard indentation (six constant width characters); .Cm indent-two , twice .Cm indent ; .Cm left , which has no effect; .Cm right , which justifies to the right margin; or .Cm center , which aligns around an imagined center axis. .It A macro invocation, which selects a predefined width associated with that macro. The most popular is the imaginary macro .Ar \&Ds , which resolves to .Sy 6n . .It A scaling width as described in .Xr roff 7 . .It An arbitrary string, which indents by the length of this string. .El .Pp When the argument is missing, .Fl offset is ignored. .It Fl compact Do not assert vertical space before the display. .El .Pp Examples: .Bd -literal -offset indent \&.Bd \-literal \-offset indent \-compact Hello world. \&.Ed .Ed .Pp See also .Ic \&D1 and .Ic \&Dl . .Tg Bf .It Ic \&Bf Fl emphasis | literal | symbolic | Cm \&Em | \&Li | \&Sy Change the font mode for a scoped block of text. The .Fl emphasis and .Cm \&Em argument are equivalent, as are .Fl symbolic and .Cm \&Sy , and .Fl literal and .Cm \&Li . Without an argument, this macro does nothing. The font mode continues until broken by a new font mode in a nested scope or .Ic \&Ef is encountered. .Pp See also .Ic \&Li , .Ic \&Ef , .Ic \&Em , and .Ic \&Sy . .Tg Bk .It Ic \&Bk Fl words For each macro, keep its output together on the same output line, until the end of the macro or the end of the input line is reached, whichever comes first. Line breaks in text lines are unaffected. .Pp The .Fl words argument is required; additional arguments are ignored. .Pp The following example will not break within each .Ic \&Op macro line: .Bd -literal -offset indent \&.Bk \-words \&.Op Fl f Ar flags \&.Op Fl o Ar output \&.Ek .Ed .Pp Be careful in using over-long lines within a keep block! Doing so will clobber the right margin. .Tg Bl .It Xo .Ic \&Bl .Fl Ns Ar type .Op Fl width Ar val .Op Fl offset Ar val .Op Fl compact .Op Ar col ... .Xc Begin a list. Lists consist of items specified using the .Ic \&It macro, containing a head or a body or both. .Pp The list .Ar type is mandatory and must be specified first. The .Fl width and .Fl offset arguments accept macro names as described for .Ic \&Bd .Fl offset , scaling widths as described in .Xr roff 7 , or use the length of the given string. The .Fl offset is a global indentation for the whole list, affecting both item heads and bodies. For those list types supporting it, the .Fl width argument requests an additional indentation of item bodies, to be added to the .Fl offset . Unless the .Fl compact argument is specified, list entries are separated by vertical space. .Pp A list must specify one of the following list types: .Bl -tag -width 12n -offset indent .It Fl bullet No item heads can be specified, but a bullet will be printed at the head of each item. Item bodies start on the same output line as the bullet and are indented according to the .Fl width argument. .It Fl column A columnated list. The .Fl width argument has no effect; instead, the string length of each argument specifies the width of one column. If the first line of the body of a .Fl column list is not an .Ic \&It macro line, .Ic \&It contexts spanning one input line each are implied until an .Ic \&It macro line is encountered, at which point items start being interpreted as described in the .Ic \&It documentation. .It Fl dash Like .Fl bullet , except that dashes are used in place of bullets. .It Fl diag Like .Fl inset , except that item heads are not parsed for macro invocations. Most often used in the .Em DIAGNOSTICS section with error constants in the item heads. .It Fl enum A numbered list. No item heads can be specified. Formatted like .Fl bullet , except that ordinal numbers are used in place of bullets, starting at 1. .It Fl hang Like .Fl tag , except that the first lines of item bodies are not indented, but follow the item heads like in .Fl inset lists. .It Fl hyphen Synonym for .Fl dash . .It Fl inset Item bodies follow items heads on the same line, using normal inter-word spacing. Bodies are not indented, and the .Fl width argument is ignored. .It Fl item No item heads can be specified, and none are printed. Bodies are not indented, and the .Fl width argument is ignored. .It Fl ohang Item bodies start on the line following item heads and are not indented. The .Fl width argument is ignored. .It Fl tag Item bodies are indented according to the .Fl width argument. When an item head fits inside the indentation, the item body follows this head on the same output line. Otherwise, the body starts on the output line following the head. .El .Pp Lists may be nested within lists and displays. Nesting of .Fl column and .Fl enum lists may not be portable. .Pp See also .Ic \&El and .Ic \&It . .It Ic \&Bo Ar block Begin a block enclosed by square brackets. Does not have any head arguments. .Pp Examples: .Bd -literal -offset indent -compact \&.Bo 1 , \&.Dv BUFSIZ \&Bc .Ed .Pp See also .Ic \&Bq . .Tg Bq .It Ic \&Bq Ar line Encloses its arguments in square brackets. .Pp Examples: .Dl \&.Bq 1 , \&Dv BUFSIZ .Pp .Em Remarks : this macro is sometimes abused to emulate optional arguments for commands; the correct macros to use for this purpose are .Ic \&Op , .Ic \&Oo , and .Ic \&Oc . .Pp See also .Ic \&Bo . .It Ic \&Brc Close a .Ic \&Bro block. Does not have any tail arguments. .It Ic \&Bro Ar block Begin a block enclosed by curly braces. Does not have any head arguments. .Pp Examples: .Bd -literal -offset indent -compact \&.Bro 1 , ... , \&.Va n \&Brc .Ed .Pp See also .Ic \&Brq . .Tg Brq .It Ic \&Brq Ar line Encloses its arguments in curly braces. .Pp Examples: .Dl \&.Brq 1 , ... , \&Va n .Pp See also .Ic \&Bro . .Tg Bsx .It Ic \&Bsx Op Ar version Format the .Bsx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Bsx 1.0 .Dl \&.Bsx .Pp See also .Ic \&At , .Ic \&Bx , .Ic \&Dx , .Ic \&Fx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Bt Supported only for compatibility, do not use this in new manuals. Prints .Dq is currently in beta test. .Tg Bx .It Ic \&Bx Op Ar version Op Ar variant Format the .Bx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Bx 4.3 Tahoe .Dl \&.Bx 4.4 .Dl \&.Bx .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Dx , .Ic \&Fx , .Ic \&Nx , and .Ic \&Ox . .Tg Cd .It Ic \&Cd Ar line Kernel configuration declaration. This denotes strings accepted by .Xr config 8 . It is most often used in section 4 manual pages. .Pp Examples: .Dl \&.Cd device le0 at scode? .Pp .Em Remarks : this macro is commonly abused by using quoted literals to retain whitespace and align consecutive .Ic \&Cd declarations. This practise is discouraged. .Tg Cm .It Ic \&Cm Ar keyword ... Command modifiers. Typically used for fixed strings passed as arguments to interactive commands, to commands in interpreted scripts, or to configuration file directives, unless .Ic \&Fl is more appropriate. .Pp Examples: .Dl ".Nm mt Fl f Ar device Cm rewind" .Dl ".Nm ps Fl o Cm pid , Ns Cm command" .Dl ".Nm dd Cm if= Ns Ar file1 Cm of= Ns Ar file2" .Dl ".Ic set Fl o Cm vi" .Dl ".Ic lookup Cm file bind" .Dl ".Ic permit Ar identity Op Cm as Ar target" .Tg D1 .It Ic \&D1 Ar line One-line indented display. This is formatted by the default rules and is useful for simple indented statements. It is followed by a newline. .Pp Examples: .Dl \&.D1 \&Fl abcdefgh .Pp See also .Ic \&Bd and .Ic \&Dl . .It Ic \&Db This macro is obsolete. No replacement is needed. It is ignored by .Xr mandoc 1 and groff including its arguments. It was formerly used to toggle a debugging mode. .It Ic \&Dc Close a .Ic \&Do block. Does not have any tail arguments. .Tg Dd .It Ic \&Dd Cm $\&Mdocdate$ | Ar month day , year Document date for display in the page footer, by convention the date of the last change. This is the mandatory first macro of any .Nm manual. .Pp The .Ar month is the full English month name, the .Ar day is an integer number, and the .Ar year is the full four-digit year. .Pp Other arguments are not portable; the .Xr mandoc 1 utility handles them as follows: .Bl -dash -offset 3n -compact .It To have the date automatically filled in by the .Ox version of .Xr cvs 1 , the special string .Dq $\&Mdocdate$ can be given as an argument. .It The traditional, purely numeric .Xr man 7 format .Ar year Ns \(en Ns Ar month Ns \(en Ns Ar day is accepted, too. .It If a date string cannot be parsed, it is used verbatim. .It If no date string is given, the current date is used. .El .Pp Examples: .Dl \&.Dd $\&Mdocdate$ .Dl \&.Dd $\&Mdocdate: July 2 2018$ .Dl \&.Dd July 2, 2018 .Pp See also .Ic \&Dt and .Ic \&Os . .Tg Dl .It Ic \&Dl Ar line -One-line indented display. -This is formatted as literal text and is useful for commands and -invocations. +One-line indented literal display. +This is formatted using a constant-width font +and is useful for commands and invocations. It is followed by a newline. .Pp Examples: .Dl \&.Dl % mandoc mdoc.7 \e(ba less .Pp See also .Ic \&Ql , .Ic \&Bd Fl literal , and .Ic \&D1 . .It Ic \&Do Ar block Begin a block enclosed by double quotes. Does not have any head arguments. .Pp Examples: .Bd -literal -offset indent -compact \&.Do April is the cruellest month \&.Dc \e(em T.S. Eliot .Ed .Pp See also .Ic \&Dq . .Tg Dq .It Ic \&Dq Ar line Encloses its arguments in .Dq typographic double-quotes. .Pp Examples: .Bd -literal -offset indent -compact \&.Dq April is the cruellest month \e(em T.S. Eliot .Ed .Pp See also .Ic \&Qq , .Ic \&Sq , and .Ic \&Do . .Tg Dt .It Ic \&Dt Ar TITLE section Op Ar arch Document title for display in the page header. This is the mandatory second macro of any .Nm file. .Pp Its arguments are as follows: .Bl -tag -width section -offset 2n .It Ar TITLE The document's title (name), defaulting to .Dq UNTITLED if unspecified. To achieve a uniform appearance of page header lines, it should by convention be all caps. .It Ar section The manual section. This may be one of .Cm 1 .Pq General Commands , .Cm 2 .Pq System Calls , .Cm 3 .Pq Library Functions , .Cm 3p .Pq Perl Library , .Cm 4 .Pq Device Drivers , .Cm 5 .Pq File Formats , .Cm 6 .Pq Games , .Cm 7 .Pq Miscellaneous Information , .Cm 8 .Pq System Manager's Manual , or .Cm 9 .Pq Kernel Developer's Manual . It should correspond to the manual's filename suffix and defaults to the empty string if unspecified. .It Ar arch This specifies the machine architecture a manual page applies to, where relevant, for example .Cm alpha , .Cm amd64 , .Cm i386 , or .Cm sparc64 . The list of valid architectures varies by operating system. .El .Pp Examples: .Dl \&.Dt FOO 1 .Dl \&.Dt FOO 9 i386 .Pp See also .Ic \&Dd and .Ic \&Os . .Tg Dv .It Ic \&Dv Ar identifier ... Defined variables such as preprocessor constants, constant symbols, enumeration values, and so on. .Pp Examples: .Dl \&.Dv NULL .Dl \&.Dv BUFSIZ .Dl \&.Dv STDOUT_FILENO .Pp See also .Ic \&Er and .Ic \&Ev for special-purpose constants, .Ic \&Va for variable symbols, and .Ic \&Fd for listing preprocessor variable definitions in the .Em SYNOPSIS . .Tg Dx .It Ic \&Dx Op Ar version Format the .Dx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Dx 2.4.1 .Dl \&.Dx .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Bx , .Ic \&Fx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Ec Op Ar closing_delimiter Close a scope started by .Ic \&Eo . .Pp The .Ar closing_delimiter argument is used as the enclosure tail, for example, specifying \e(rq will emulate .Ic \&Dc . .It Ic \&Ed End a display context started by .Ic \&Bd . .It Ic \&Ef End a font mode context started by .Ic \&Bf . .It Ic \&Ek End a keep context started by .Ic \&Bk . .It Ic \&El End a list context started by .Ic \&Bl . See also .Ic \&It . .Tg Em .It Ic \&Em Ar word ... Request an italic font. If the output device does not provide that, underline. .Pp This is most often used for stress emphasis (not to be confused with importance, see .Ic \&Sy ) . In the rare cases where none of the semantic markup macros fit, it can also be used for technical terms and placeholders, except that for syntax elements, .Ic \&Sy and .Ic \&Ar are preferred, respectively. .Pp Examples: .Bd -literal -compact -offset indent Selected lines are those \&.Em not matching any of the specified patterns. Some of the functions use a \&.Em hold space to save the pattern space for subsequent retrieval. .Ed .Pp See also .Ic \&No , .Ic \&Ql , and .Ic \&Sy . .It Ic \&En Ar word ... This macro is obsolete. Use .Ic \&Eo or any of the other enclosure macros. .Pp It encloses its argument in the delimiters specified by the last .Ic \&Es macro. .Tg Eo .It Ic \&Eo Op Ar opening_delimiter An arbitrary enclosure. The .Ar opening_delimiter argument is used as the enclosure head, for example, specifying \e(lq will emulate .Ic \&Do . .Tg Er .It Ic \&Er Ar identifier ... Error constants for definitions of the .Va errno libc global variable. This is most often used in section 2 and 3 manual pages. .Pp Examples: .Dl \&.Er EPERM .Dl \&.Er ENOENT .Pp See also .Ic \&Dv for general constants. .It Ic \&Es Ar opening_delimiter closing_delimiter This macro is obsolete. Use .Ic \&Eo or any of the other enclosure macros. .Pp It takes two arguments, defining the delimiters to be used by subsequent .Ic \&En macros. .Tg Ev .It Ic \&Ev Ar identifier ... Environmental variables such as those specified in .Xr environ 7 . .Pp Examples: .Dl \&.Ev DISPLAY .Dl \&.Ev PATH .Pp See also .Ic \&Dv for general constants. .Tg Ex .It Ic \&Ex Fl std Op Ar utility ... Insert a standard sentence regarding command exit values of 0 on success and >0 on failure. This is most often used in section 1, 6, and 8 manual pages. .Pp If .Ar utility is not specified, the document's name set by .Ic \&Nm is used. Multiple .Ar utility arguments are treated as separate utilities. .Pp See also .Ic \&Rv . .Tg Fa .It Ic \&Fa Ar argument ... Function argument or parameter. Each argument may be a name and a type (recommended for the .Em SYNOPSIS section), a name alone (for function invocations), or a type alone (for function prototypes). If both a type and a name are given or if the type consists of multiple words, all words belonging to the same function argument have to be given in a single argument to the .Ic \&Fa macro. .Pp This macro is also used to specify the field name of a structure. .Pp Most often, the .Ic \&Fa macro is used in the .Em SYNOPSIS within .Ic \&Fo blocks when documenting multi-line function prototypes. If invoked with multiple arguments, the arguments are separated by a comma. Furthermore, if the following macro is another .Ic \&Fa , the last argument will also have a trailing comma. .Pp Examples: .Dl \&.Fa \(dqconst char *p\(dq .Dl \&.Fa \(dqint a\(dq \(dqint b\(dq \(dqint c\(dq .Dl \&.Fa \(dqchar *\(dq size_t .Pp See also .Ic \&Fo . .It Ic \&Fc End a function context started by .Ic \&Fo . .Tg Fd .It Ic \&Fd Pf # Ar directive Op Ar argument ... Preprocessor directive, in particular for listing it in the .Em SYNOPSIS . Historically, it was also used to document include files. The latter usage has been deprecated in favour of .Ic \&In . .Pp Examples: .Dl \&.Fd #define sa_handler __sigaction_u.__sa_handler .Dl \&.Fd #define SIO_MAXNFDS .Dl \&.Fd #ifdef FS_DEBUG .Dl \&.Ft void .Dl \&.Fn dbg_open \(dqconst char *\(dq .Dl \&.Fd #endif .Pp See also .Sx MANUAL STRUCTURE , .Ic \&In , and .Ic \&Dv . .Tg Fl .It Ic \&Fl Op Ar word ... Command-line flag or option. Used when listing arguments to command-line utilities. For each argument, prints an ASCII hyphen-minus character .Sq \- , immediately followed by the argument. If no arguments are provided, a hyphen-minus is printed followed by a space. If the argument is a macro, a hyphen-minus is prefixed to the subsequent macro output. .Pp Examples: .Dl ".Nm du Op Fl H | L | P" .Dl ".Nm ls Op Fl 1AaCcdFfgHhikLlmnopqRrSsTtux" .Dl ".Nm route Cm add Fl inet Ar destination gateway" .Dl ".Nm locate.updatedb Op Fl \e-fcodes Ns = Ns Ar dbfile" .Dl ".Nm aucat Fl o Fl" .Dl ".Nm kill Fl Ar signal_number" .Pp For GNU-style long options, escaping the additional hyphen-minus is not strictly required, but may be safer with future versions of GNU troff; see .Xr mandoc_char 7 for details. .Pp See also .Ic \&Cm . .Tg Fn .It Ic \&Fn Ar funcname Op Ar argument ... A function name. .Pp Function arguments are surrounded in parenthesis and are delimited by commas. If no arguments are specified, blank parenthesis are output. In the .Em SYNOPSIS section, this macro starts a new output line, and a blank line is automatically inserted between function definitions. .Pp Examples: .Dl \&.Fn \(dqint funcname\(dq \(dqint arg0\(dq \(dqint arg1\(dq .Dl \&.Fn funcname \(dqint arg0\(dq .Dl \&.Fn funcname arg0 .Bd -literal -offset indent \&.Ft functype \&.Fn funcname .Ed .Pp When referring to a function documented in another manual page, use .Ic \&Xr instead. See also .Sx MANUAL STRUCTURE , .Ic \&Fo , and .Ic \&Ft . .Tg Fo .It Ic \&Fo Ar funcname Begin a function block. This is a multi-line version of .Ic \&Fn . .Pp Invocations usually occur in the following context: .Bd -ragged -offset indent .Pf \. Ic \&Ft Ar functype .br .Pf \. Ic \&Fo Ar funcname .br .Pf \. Ic \&Fa Qq Ar argtype Ar argname .br \&.\.\. .br .Pf \. Ic \&Fc .Ed .Pp A .Ic \&Fo scope is closed by .Ic \&Fc . .Pp See also .Sx MANUAL STRUCTURE , .Ic \&Fa , .Ic \&Fc , and .Ic \&Ft . .It Ic \&Fr Ar number This macro is obsolete. No replacement markup is needed. .Pp It was used to show numerical function return values in an italic font. .Tg Ft .It Ic \&Ft Ar functype A function type. .Pp In the .Em SYNOPSIS section, a new output line is started after this macro. .Pp Examples: .Dl \&.Ft int .Bd -literal -offset indent -compact \&.Ft functype \&.Fn funcname .Ed .Pp See also .Sx MANUAL STRUCTURE , .Ic \&Fn , and .Ic \&Fo . .Tg Fx .It Ic \&Fx Op Ar version Format the .Fx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Fx 7.1 .Dl \&.Fx .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Bx , .Ic \&Dx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Hf Ar filename This macro is not implemented in .Xr mandoc 1 . It was used to include the contents of a (header) file literally. .Tg Ic .It Ic \&Ic Ar keyword ... Internal or interactive command, or configuration instruction in a configuration file. See also .Ic \&Cm . .Pp Examples: .Dl \&.Ic :wq .Dl \&.Ic hash .Dl \&.Ic alias .Pp Note that using .Ic \&Ql , .Ic \&Dl , or .Ic \&Bd Fl literal is preferred for displaying code samples; the .Ic \&Ic macro is used when referring to an individual command name. .Tg In .It Ic \&In Ar filename The name of an include file. This macro is most often used in section 2, 3, and 9 manual pages. .Pp When invoked as the first macro on an input line in the .Em SYNOPSIS section, the argument is displayed in angle brackets and preceded by .Qq #include , and a blank line is inserted in front if there is a preceding function declaration. In other sections, it only encloses its argument in angle brackets and causes no line break. .Pp Examples: .Dl \&.In sys/types.h .Pp See also .Sx MANUAL STRUCTURE . .Tg It .It Ic \&It Op Ar head A list item. The syntax of this macro depends on the list type. .Pp Lists of type .Fl hang , .Fl ohang , .Fl inset , and .Fl diag have the following syntax: .Pp .D1 Pf \. Ic \&It Ar args .Pp Lists of type .Fl bullet , .Fl dash , .Fl enum , .Fl hyphen and .Fl item have the following syntax: .Pp .D1 Pf \. Ic \&It .Pp with subsequent lines interpreted within the scope of the .Ic \&It until either a closing .Ic \&El or another .Ic \&It . .Pp The .Fl tag list has the following syntax: .Pp .D1 Pf \. Ic \&It Op Cm args .Pp Subsequent lines are interpreted as with .Fl bullet and family. The line arguments correspond to the list's left-hand side; body arguments correspond to the list's contents. .Pp The .Fl column list is the most complicated. Its syntax is as follows: .Pp .D1 Pf \. Ic \&It Ar cell Op Ic \&Ta Ar cell ... .D1 Pf \. Ic \&It Ar cell Op Ar cell ... .Pp The arguments consist of one or more lines of text and macros representing a complete table line. Cells within the line are delimited by the special .Ic \&Ta block macro or by literal tab characters. .Pp Using literal tabs is strongly discouraged because they are very hard to use correctly and .Nm code using them is very hard to read. In particular, a blank character is syntactically significant before and after the literal tab character. If a word precedes or follows the tab without an intervening blank, that word is never interpreted as a macro call, but always output literally. .Pp The tab cell delimiter may only be used within the .Ic \&It line itself; on following lines, only the .Ic \&Ta macro can be used to delimit cells, and portability requires that .Ic \&Ta is called by other macros: some parsers do not recognize it when it appears as the first macro on a line. .Pp Note that quoted strings may span tab-delimited cells on an .Ic \&It line. For example, .Pp .Dl .It \(dqcol1 ,\& col2 ,\(dq \&; .Pp will preserve the whitespace before both commas, but not the whitespace before the semicolon. .Pp See also .Ic \&Bl . .Tg Lb -.It Ic \&Lb Cm lib Ns Ar name -Specify a library. -.Pp -The -.Ar name -parameter may be a system library, such as -.Cm z -or -.Cm pam , -in which case a small library description is printed next to the linker -invocation; or a custom library, in which case the library name is -printed in quotes. -This is most commonly used in the +.It Ic \&Lb Cm lib Ns Ar name Op Cm lib Ns Ar name ... +Specify one or more libraries to link against. +Putting this macro at the beginning of the .Em SYNOPSIS -section as described in -.Sx MANUAL STRUCTURE . +section is recommended, in which case it prints this comment: +.D1 /* Fl l Ns Ar name Oo Fl l Ns Ar name ... Oc */ .Pp -Examples: -.Dl \&.Lb libz -.Dl \&.Lb libmandoc +If used outside the +.Em SYNOPSIS , +this macro prints +.D1 library Dq Cm lib Ns Ar name +instead. +For system libraries, some operating systems +print a short library description. +.Pp +Example: +.Bd -literal -offset indent -compact +\&.Sh SYNOPSIS +\&.Lb libtls libssl libcrypto +\&.In tls.h +\&.Ft int +\&.Fn tls_init void +.Ed .Tg Li .It Ic \&Li Ar word ... -Request a typewriter (literal) font. -Deprecated because on terminal output devices, this is usually -indistinguishable from normal text. -For literal displays, use -.Ic \&Ql Pq in-line , -.Ic \&Dl Pq single line , +Unquoted in-line literal display, always set in a constant-width font. +In most cases, use +.Ic \&Ql +instead because on terminal output devices, +.Ic \&Li +is usually indistinguishable from normal text. +This macro is only useful when enclosing the argument in quotes +is explicitly not desired, for example because it already stands out +due to being wrapped in another macro, e.g. in an +.Ic \&It +head. +.Pp +For longer literal displays, use +.Ic \&Dl Pq single line or .Ic \&Bd Fl literal Pq multi-line instead. .Tg Lk .It Ic \&Lk Ar uri Op Ar display_name Format a hyperlink. .Pp Examples: .Dl \&.Lk https://bsd.lv \(dqThe BSD.lv Project\(dq .Dl \&.Lk https://bsd.lv .Pp See also .Ic \&Mt . .It Ic \&Lp Deprecated synonym for .Ic \&Pp . .Tg Ms .It Ic \&Ms Ar name Display a mathematical symbol. .Pp Examples: .Dl \&.Ms sigma .Dl \&.Ms aleph .Tg Mt .It Ic \&Mt Ar localpart Ns @ Ns Ar domain Format a .Dq mailto: hyperlink. .Pp Examples: .Dl \&.Mt discuss@manpages.bsd.lv .Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv .Tg Nd .It Ic \&Nd Ar line A one line description of the manual's content. This is the mandatory last macro of the .Em NAME section and not appropriate for other sections. .Pp Examples: .Dl Pf . Ic \&Nd mdoc language reference .Dl Pf . Ic \&Nd format and display UNIX manuals .Pp The .Ic \&Nd macro technically accepts child macros and terminates with a subsequent .Ic \&Sh invocation. Do not assume this behaviour: some .Xr whatis 1 database generators are not smart enough to parse more than the line arguments and will display macros verbatim. .Pp See also .Ic \&Nm . .Tg Nm .It Ic \&Nm Op Ar name The name of the manual page, or \(em in particular in section 1, 6, and 8 pages \(em of an additional command or feature documented in the manual page. When first invoked, the .Ic \&Nm macro expects a single argument, the name of the manual page. Usually, the first invocation happens in the .Em NAME section of the page. The specified name will be remembered and used whenever the macro is called again without arguments later in the page. The .Ic \&Nm macro uses .Sx Block full-implicit semantics when invoked as the first macro on an input line in the .Em SYNOPSIS section; otherwise, it uses ordinary .Sx In-line semantics. .Pp Examples: .Bd -literal -offset indent \&.Sh SYNOPSIS \&.Nm cat \&.Op Fl benstuv \&.Op Ar .Ed .Pp In the .Em SYNOPSIS of section 2, 3 and 9 manual pages, use the .Ic \&Fn macro rather than .Ic \&Nm to mark up the name of the manual page. .Tg No .It Ic \&No Ar word ... Normal text. Closes the scope of any preceding in-line macro. When used after physical formatting macros like .Ic \&Em or .Ic \&Sy , switches back to the standard font face and weight. Can also be used to embed plain text strings in macro lines using semantic annotation macros. .Pp Examples: .Dl ".Em italic , Sy bold , No and roman" .Bd -literal -offset indent \&.Sm off \&.Cm :C No / Ar pattern No / Ar replacement No / \&.Sm on .Ed .Pp See also .Ic \&Em , .Ic \&Ql , and .Ic \&Sy . .Tg Ns .It Ic \&Ns Suppress a space between the output of the preceding macro and the following text or macro. Following invocation, input is interpreted as normal text just like after an .Ic \&No macro. .Pp This has no effect when invoked at the start of a macro line. .Pp Examples: .Dl ".Ar name Ns = Ns Ar value" .Dl ".Cm :M Ns Ar pattern" .Dl ".Fl o Ns Ar output" .Pp See also .Ic \&No and .Ic \&Sm . .Tg Nx .It Ic \&Nx Op Ar version Format the .Nx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Nx 5.01 .Dl \&.Nx .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Bx , .Ic \&Dx , .Ic \&Fx , and .Ic \&Ox . .It Ic \&Oc Close multi-line .Ic \&Oo context. .It Ic \&Oo Ar block Multi-line version of .Ic \&Op . .Pp Examples: .Bd -literal -offset indent -compact \&.Oo \&.Op Fl flag Ns Ar value \&.Oc .Ed .Tg Op .It Ic \&Op Ar line Optional part of a command line. Prints the argument(s) in brackets. This is most often used in the .Em SYNOPSIS section of section 1 and 8 manual pages. .Pp Examples: .Dl \&.Op \&Fl a \&Ar b .Dl \&.Op \&Ar a | b .Pp See also .Ic \&Oo . .Tg Os .It Ic \&Os Op Ar footer text The mandatory third macro of every .Nm file. Usually, do not specify any arguments, in particular not the operating system name and/or version. .Pp If no argument is given, .Xr mandoc 1 prints its .Fl Ios argument in the page footer, or .Fa sysname and .Fa release as returned by .Xr uname 3 by default. .Pp Manual pages that are part of a portable software project can override the default by giving the project name and version number as arguments, but leaving it blank is never a bad choice. .Pp See also .Ic \&Dd and .Ic \&Dt . .It Ic \&Ot Ar functype This macro is obsolete. Use .Ic \&Ft instead; with .Xr mandoc 1 , both have the same effect. .Pp Historical .Nm packages described it as .Dq "old function type (FORTRAN)" . .Tg Ox .It Ic \&Ox Op Ar version Format the .Ox version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Ox 4.5 .Dl \&.Ox .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Bx , .Ic \&Dx , .Ic \&Fx , and .Ic \&Nx . .Tg Pa .It Ic \&Pa Ar name ... An absolute or relative file system path, or a file or directory name. If an argument is not provided, the character .Sq \(ti is used as a default. .Pp Examples: .Dl \&.Pa /usr/bin/mandoc .Dl \&.Pa /usr/share/man/man7/mdoc.7 .Pp See also .Ic \&Lk . .It Ic \&Pc Close parenthesised context opened by .Ic \&Po . .Tg Pf .It Ic \&Pf Ar prefix macro Op Ar argument ... Removes the space between its argument and the following macro. It is equivalent to: .Pp .D1 Ic \&No Pf \e& Ar prefix Ic \&Ns Ar macro Op Ar argument ... .Pp The .Ar prefix argument is not parsed for macro names or delimiters, but used verbatim as if it were escaped. .Pp Examples: .Dl ".Pf $ Ar variable_name" .Dl ".Pf . Ar macro_name" .Dl ".Pf 0x Ar hex_digits" .Pp See also .Ic \&Ns and .Ic \&Sm . .It Ic \&Po Ar block Multi-line version of .Ic \&Pq . .Tg Pp .It Ic \&Pp Break a paragraph. This will assert vertical space between prior and subsequent macros and/or text. .Pp Paragraph breaks are not needed before or after .Ic \&Sh or .Ic \&Ss macros or before displays .Pq Ic \&Bd Ar line or lists .Pq Ic \&Bl unless the .Fl compact flag is given. .Tg Pq .It Ic \&Pq Ar line Parenthesised enclosure. .Pp See also .Ic \&Po . .It Ic \&Qc Close quoted context opened by .Ic \&Qo . .Tg Ql .It Ic \&Ql Ar line -In-line literal display. +Normal in-line literal display, always set in constant-width font and +additionally enclosed in quotes by many formatters in many cases. This can be used for complete command invocations and for multi-word code examples when an indented display is not desired. .Pp See also -.Ic \&Dl -and +.Ic \&Dl , .Ic \&Bd -.Fl literal . +.Fl literal , +and +.Ic \&Li . .It Ic \&Qo Ar block Multi-line version of .Ic \&Qq . .Tg Qq .It Ic \&Qq Ar line Encloses its arguments in .Qq typewriter double-quotes. Consider using .Ic \&Dq . .Pp See also .Ic \&Dq , .Ic \&Sq , and .Ic \&Qo . .It Ic \&Re Close an .Ic \&Rs block. Does not have any tail arguments. .Tg Rs .It Ic \&Rs Begin a bibliographic .Pq Dq reference block. Does not have any head arguments. The block macro may only contain .Ic \&%A , .Ic \&%B , .Ic \&%C , .Ic \&%D , .Ic \&%I , .Ic \&%J , .Ic \&%N , .Ic \&%O , .Ic \&%P , .Ic \&%Q , .Ic \&%R , .Ic \&%T , .Ic \&%U , and .Ic \&%V child macros (at least one must be specified). .Pp Examples: .Bd -literal -offset indent -compact \&.Rs \&.%A J. E. Hopcroft \&.%A J. D. Ullman \&.%B Introduction to Automata Theory, Languages, and Computation \&.%I Addison-Wesley \&.%C Reading, Massachusetts \&.%D 1979 \&.Re .Ed .Pp If an .Ic \&Rs block is used within a SEE ALSO section, a vertical space is asserted before the rendered output, else the block continues on the current line. .Tg Rv .It Ic \&Rv Fl std Op Ar function ... Insert a standard sentence regarding a function call's return value of 0 on success and \-1 on error, with the .Va errno libc global variable set on error. .Pp If .Ar function is not specified, the document's name set by .Ic \&Nm is used. Multiple .Ar function arguments are treated as separate functions. .Pp See also .Ic \&Ex . .It Ic \&Sc Close single-quoted context opened by .Ic \&So . .Tg Sh .It Ic \&Sh Ar TITLE LINE Begin a new section. For a list of conventional manual sections, see .Sx MANUAL STRUCTURE . Use the conventional sections where applicable. For unusually long and complicated manual pages, adding custom sections is occasionally useful. .Pp Avoid using macros inside the .Ar TITLE LINE and keep that line unique within the manual page, such that it can be pointed to with .Ic \&Sx . .Pp See also .Ic \&Pp , .Ic \&Ss , and .Ic \&Sx . .Tg Sm .It Ic \&Sm Op Cm on | off Switches the spacing mode for output generated from macros. .Pp By default, spacing is .Cm on . When switched .Cm off , no white space is inserted between macro arguments and between the output generated from adjacent macros, but text lines still get normal spacing between words and sentences. .Pp When called without an argument, the .Ic \&Sm macro toggles the spacing mode. Using this is not recommended because it makes the code harder to read. .It Ic \&So Ar block Multi-line version of .Ic \&Sq . .Tg Sq .It Ic \&Sq Ar line Encloses its arguments in .Sq typewriter single-quotes. .Pp See also .Ic \&Dq , .Ic \&Qq , and .Ic \&So . .Tg Ss .It Ic \&Ss Ar Title line Begin a new subsection. Unlike with .Ic \&Sh , there is no convention for the naming of subsections. Except .Em DESCRIPTION , the conventional sections described in .Sx MANUAL STRUCTURE rarely have subsections. .Pp Avoid using macros inside the .Ar Title line and keep that line unique within the manual page, such that it can be pointed to with .Ic \&Sx . .Pp See also .Ic \&Pp , .Ic \&Sh , and .Ic \&Sx . .Tg St .It Ic \&St Fl Ns Ar abbreviation Replace an abbreviation for a standard with the full form. The following standards are recognised. Where multiple lines are given without a blank line in between, they all refer to the same standard, and using the first form is recommended. .Bl -tag -width 1n .It C language standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-ansiC .St -ansiC .It \-ansiC-89 .St -ansiC-89 .It \-isoC .St -isoC .It \-isoC-90 .St -isoC-90 .br The original C standard. .Pp .It \-isoC-amd1 .St -isoC-amd1 .Pp .It \-isoC-tcor1 .St -isoC-tcor1 .Pp .It \-isoC-tcor2 .St -isoC-tcor2 .Pp .It \-isoC-99 .St -isoC-99 .br Edition 2 of the C language standard. .Pp .It \-isoC-2011 .St -isoC-2011 .br Edition 3 of the C language standard. .Pp .It \-isoC-2023 .St -isoC-2023 .br Edition 5 of the C language standard. .El .It POSIX.1 before XPG4.2 .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-p1003.1-88 .St -p1003.1-88 .It \-p1003.1 .St -p1003.1 .br The original POSIX standard, based on ANSI C. .Pp .It \-p1003.1-90 .St -p1003.1-90 .It \-iso9945-1-90 .St -iso9945-1-90 .br The first update of POSIX.1. .Pp .It \-p1003.1b-93 .St -p1003.1b-93 .It \-p1003.1b .St -p1003.1b .br Real-time extensions. .Pp .It \-p1003.1c-95 .St -p1003.1c-95 .br POSIX thread interfaces. .Pp .It \-p1003.1i-95 .St -p1003.1i-95 .br Technical Corrigendum. .Pp .It \-p1003.1-96 .St -p1003.1-96 .It \-iso9945-1-96 .St -iso9945-1-96 .br Includes POSIX.1-1990, 1b, 1c, and 1i. .El .It X/Open Portability Guide before XPG4.2 .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-xpg3 .St -xpg3 .br An XPG4 precursor, published in 1989. .Pp .It \-p1003.2 .St -p1003.2 .It \-p1003.2-92 .St -p1003.2-92 .It \-iso9945-2-93 .St -iso9945-2-93 .br An XCU4 precursor. .Pp .It \-p1003.2a-92 .St -p1003.2a-92 .br Updates to POSIX.2. .Pp .It \-xpg4 .St -xpg4 .br Based on POSIX.1 and POSIX.2, published in 1992. .El .It X/Open Portability Guide Issue 4 Version 2 and related standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-susv1 .St -susv1 .It \-xpg4.2 .St -xpg4.2 .br This standard was published in 1994. It was used as the basis for UNIX 95 certification. The following two refer to parts of it. .Pp .It \-xcurses4.2 .St -xcurses4.2 .Pp .It \-p1003.1g-2000 .St -p1003.1g-2000 .br Networking APIs, including sockets. .Pp .It \-svid4 .St -svid4 , .br Published in 1995. .El .It X/Open Portability Guide Issue 5 and related standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-susv2 .St -susv2 .br This Standard was published in 1997 and is also called X/Open Portability Guide Issue 5. It was used as the basis for UNIX 98 certification. The following refer to parts of it. .Pp .It \-xbd5 .St -xbd5 .Pp .It \-xsh5 .St -xsh5 .Pp .It \-xcu5 .St -xcu5 .Pp .It \-xns5 .St -xns5 .It \-xns5.2 .St -xns5.2 .El .It POSIX Issue 6 .Pp .Bl -tag -width "-p1003.1-2001" -compact .It \-p1003.1-2001 .St -p1003.1-2001 .It \-susv3 .St -susv3 .br This standard is based on C99, SUSv2, POSIX.1-1996, 1d, and 1j. It is also called X/Open Portability Guide Issue 6. It is used as the basis for UNIX 03 certification. .Pp .It \-p1003.1-2004 .St -p1003.1-2004 .br The second and last Technical Corrigendum. .El .It POSIX Issues 7 and 8 .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-p1003.1-2008 .St -p1003.1-2008 .It \-susv4 .St -susv4 .br This standard is based on C99. It is also called the Open Group Standard Base Specifications, Issue 7. .El .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-p1003.1-2024 .St -p1003.1-2024 .br This standard is based on C17. It is also called the Open Group Standard Base Specifications, Issue 8. .El .It Other standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-ieee754 .St -ieee754 .br Floating-point arithmetic. .Pp .It \-iso8601 .St -iso8601 .br Representation of dates and times, published in 1988. .Pp .It \-iso8802-3 .St -iso8802-3 .br Ethernet local area networks. .Pp .It \-ieee1275-94 .St -ieee1275-94 .El .El .Tg Sx .It Ic \&Sx Ar Title line Reference a section or subsection in the same manual page. The referenced section or subsection name must be identical to the enclosed argument, including whitespace. .Pp Examples: .Dl \&.Sx MANUAL STRUCTURE .Pp See also .Ic \&Sh and .Ic \&Ss . .Tg Sy .It Ic \&Sy Ar word ... Request a boldface font. .Pp This is most often used to indicate importance or seriousness (not to be confused with stress emphasis, see .Ic \&Em ) . When none of the semantic macros fit, it is also adequate for syntax elements that have to be given or that appear verbatim. .Pp Examples: .Bd -literal -compact -offset indent \&.Sy Warning : If \&.Sy s appears in the owner permissions, set-user-ID mode is set. This utility replaces the former \&.Sy dumpdir program. .Ed .Pp See also .Ic \&Em , .Ic \&No , and .Ic \&Ql . .Tg Ta .It Ic \&Ta Table cell separator in .Ic \&Bl Fl column lists; can only be used below .Ic \&It . .Tg Tg .It Ic \&Tg Op Ar term Announce that the next input line starts a definition of the .Ar term . This macro must appear alone on its own input line. The argument defaults to the first argument of the first macro on the next line. The argument may not contain whitespace characters, not even when it is quoted. This macro is a .Xr mandoc 1 extension and is typically ignored by other formatters. .Pp When viewing terminal output with .Xr less 1 , the interactive .Ic :t command can be used to go to the definition of the .Ar term as described for the .Ev MANPAGER variable in .Xr man 1 ; when producing HTML output, a fragment identifier .Pq Ic id No attribute is generated, to be used for deep linking to this place of the document. .Pp In most cases, adding a .Ic \&Tg macro would be redundant because .Xr mandoc 1 is able to automatically tag most definitions. This macro is intended for cases where automatic tagging of a .Ar term is unsatisfactory, for example if a definition is not tagged automatically (false negative) or if places are tagged that do not define the .Ar term (false positives). When there is at least one .Ic \&Tg macro for a .Ar term , no other places are automatically marked as definitions of that .Ar term . .It Ic \&Tn Ar word ... Supported only for compatibility, do not use this in new manuals. Even though the macro name .Pq Dq tradename suggests a semantic function, historic usage is inconsistent, mostly using it as a presentation-level macro to request a small caps font. .It Ic \&Ud Supported only for compatibility, do not use this in new manuals. Prints out .Dq currently under development. .It Ic \&Ux Supported only for compatibility, do not use this in new manuals. Prints out .Dq Ux . .Tg Va .It Ic \&Va Oo Ar type Oc Ar identifier ... A variable name. .Pp Examples: .Dl \&.Va foo .Dl \&.Va const char *bar ; .Pp For function arguments and parameters, use .Ic \&Fa instead. For declarations of global variables in the .Em SYNOPSIS section, use .Ic \&Vt . .Tg Vt .It Ic \&Vt Ar type Op Ar identifier A variable type. .Pp This is also used for indicating global variables in the .Em SYNOPSIS section, in which case a variable name is also specified. Note that it accepts .Sx Block partial-implicit syntax when invoked as the first macro on an input line in the .Em SYNOPSIS section, else it accepts ordinary .Sx In-line syntax. In the former case, this macro starts a new output line, and a blank line is inserted in front if there is a preceding function definition or include directive. .Pp Examples: .Dl \&.Vt unsigned char .Dl \&.Vt extern const char * const sys_signame[] \&; .Pp For parameters in function prototypes, use .Ic \&Fa instead, for function return types .Ic \&Ft , and for variable names outside the .Em SYNOPSIS section .Ic \&Va , even when including a type with the name. See also .Sx MANUAL STRUCTURE . .It Ic \&Xc Close a scope opened by .Ic \&Xo . .It Ic \&Xo Ar block Extend the header of an .Ic \&It macro or the body of a partial-implicit block macro beyond the end of the input line. This macro originally existed to work around the 9-argument limit of historic .Xr roff 7 . .Tg Xr .It Ic \&Xr Ar name section Link to another manual .Pq Qq cross-reference . .Pp Cross reference the .Ar name and .Ar section number of another man page. .Pp Examples: .Dl \&.Xr mandoc 1 .Dl \&.Xr mandoc 1 \&; .Dl \&.Xr mandoc 1 \&Ns s behaviour .El .Sh MACRO SYNTAX The syntax of a macro depends on its classification. In this section, .Sq \-arg refers to macro arguments, which may be followed by zero or more .Sq parm parameters; .Sq \&Yo opens the scope of a macro; and if specified, .Sq \&Yc closes it out. .Pp The .Em Callable column indicates that the macro may also be called by passing its name as an argument to another macro. For example, .Sq \&.Op \&Fl O \&Ar file produces .Sq Op Fl O Ar file . To prevent a macro call and render the macro name literally, escape it by prepending a zero-width space, .Sq \e& . For example, .Sq \&Op \e&Fl O produces .Sq Op \&Fl O . If a macro is not callable but its name appears as an argument to another macro, it is interpreted as opaque text. For example, .Sq \&.Fl \&Sh produces .Sq Fl \&Sh . .Pp The .Em Parsed column indicates whether the macro may call other macros by receiving their names as arguments. If a macro is not parsed but the name of another macro appears as an argument, it is interpreted as opaque text. .Pp The .Em Scope column, if applicable, describes closure rules. .Ss Block full-explicit Multi-line scope closed by an explicit closing macro. All macros contains bodies; only .Ic \&Bf and .Pq optionally .Ic \&Bl contain a head. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \(lBbody...\(rB \&.Yc .Ed .Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope .It Ic \&Bd Ta \&No Ta \&No Ta closed by Ic \&Ed .It Ic \&Bf Ta \&No Ta \&No Ta closed by Ic \&Ef .It Ic \&Bk Ta \&No Ta \&No Ta closed by Ic \&Ek .It Ic \&Bl Ta \&No Ta \&No Ta closed by Ic \&El .It Ic \&Ed Ta \&No Ta \&No Ta opened by Ic \&Bd .It Ic \&Ef Ta \&No Ta \&No Ta opened by Ic \&Bf .It Ic \&Ek Ta \&No Ta \&No Ta opened by Ic \&Bk .It Ic \&El Ta \&No Ta \&No Ta opened by Ic \&Bl .El .Ss Block full-implicit Multi-line scope closed by end-of-file or implicitly by another macro. All macros have bodies; some .Po .Ic \&It Fl bullet , .Fl hyphen , .Fl dash , .Fl enum , .Fl item .Pc don't have heads; only one .Po .Ic \&It in .Ic \&Bl Fl column .Pc has multiple heads. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead... \(lBTa head...\(rB\(rB \(lBbody...\(rB .Ed .Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXXXXXXXXX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope .It Ic \&It Ta \&No Ta Yes Ta closed by Ic \&It , Ic \&El .It Ic \&Nd Ta \&No Ta \&No Ta closed by Ic \&Sh .It Ic \&Nm Ta \&No Ta Yes Ta closed by Ic \&Nm , Ic \&Sh , Ic \&Ss .It Ic \&Sh Ta \&No Ta Yes Ta closed by Ic \&Sh .It Ic \&Ss Ta \&No Ta Yes Ta closed by Ic \&Sh , Ic \&Ss .El .Pp Note that the .Ic \&Nm macro is a .Sx Block full-implicit macro only when invoked as the first macro in a .Em SYNOPSIS section line, else it is .Sx In-line . .Ss Block partial-explicit Like block full-explicit, but also with single-line scope. Each has at least a body and, in limited circumstances, a head .Po .Ic \&Fo , .Ic \&Eo .Pc and/or tail .Pq Ic \&Ec . .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \(lBbody...\(rB \&.Yc \(lBtail...\(rB \&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \ \(lBbody...\(rB \&Yc \(lBtail...\(rB .Ed .Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope .It Ic \&Ac Ta Yes Ta Yes Ta opened by Ic \&Ao .It Ic \&Ao Ta Yes Ta Yes Ta closed by Ic \&Ac .It Ic \&Bc Ta Yes Ta Yes Ta closed by Ic \&Bo .It Ic \&Bo Ta Yes Ta Yes Ta opened by Ic \&Bc .It Ic \&Brc Ta Yes Ta Yes Ta opened by Ic \&Bro .It Ic \&Bro Ta Yes Ta Yes Ta closed by Ic \&Brc .It Ic \&Dc Ta Yes Ta Yes Ta opened by Ic \&Do .It Ic \&Do Ta Yes Ta Yes Ta closed by Ic \&Dc .It Ic \&Ec Ta Yes Ta Yes Ta opened by Ic \&Eo .It Ic \&Eo Ta Yes Ta Yes Ta closed by Ic \&Ec .It Ic \&Fc Ta Yes Ta Yes Ta opened by Ic \&Fo .It Ic \&Fo Ta \&No Ta \&No Ta closed by Ic \&Fc .It Ic \&Oc Ta Yes Ta Yes Ta closed by Ic \&Oo .It Ic \&Oo Ta Yes Ta Yes Ta opened by Ic \&Oc .It Ic \&Pc Ta Yes Ta Yes Ta closed by Ic \&Po .It Ic \&Po Ta Yes Ta Yes Ta opened by Ic \&Pc .It Ic \&Qc Ta Yes Ta Yes Ta opened by Ic \&Oo .It Ic \&Qo Ta Yes Ta Yes Ta closed by Ic \&Oc .It Ic \&Re Ta \&No Ta \&No Ta opened by Ic \&Rs .It Ic \&Rs Ta \&No Ta \&No Ta closed by Ic \&Re .It Ic \&Sc Ta Yes Ta Yes Ta opened by Ic \&So .It Ic \&So Ta Yes Ta Yes Ta closed by Ic \&Sc .It Ic \&Xc Ta Yes Ta Yes Ta opened by Ic \&Xo .It Ic \&Xo Ta Yes Ta Yes Ta closed by Ic \&Xc .El .Ss Block partial-implicit Like block full-implicit, but with single-line scope closed by the end of the line. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBres...\(rB .Ed .Bl -column "MacroX" "CallableX" "ParsedX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed .It Ic \&Aq Ta Yes Ta Yes .It Ic \&Bq Ta Yes Ta Yes .It Ic \&Brq Ta Yes Ta Yes .It Ic \&D1 Ta \&No Ta \&Yes .It Ic \&Dl Ta \&No Ta Yes .It Ic \&Dq Ta Yes Ta Yes .It Ic \&En Ta Yes Ta Yes .It Ic \&Op Ta Yes Ta Yes .It Ic \&Pq Ta Yes Ta Yes .It Ic \&Ql Ta Yes Ta Yes .It Ic \&Qq Ta Yes Ta Yes .It Ic \&Sq Ta Yes Ta Yes .It Ic \&Vt Ta Yes Ta Yes .El .Pp Note that the .Ic \&Vt macro is a .Sx Block partial-implicit only when invoked as the first macro in a .Em SYNOPSIS section line, else it is .Sx In-line . .Ss Special block macro The .Ic \&Ta macro can only be used below .Ic \&It in .Ic \&Bl Fl column lists. It delimits blocks representing table cells; these blocks have bodies, but no heads. .Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope .It Ic \&Ta Ta Yes Ta Yes Ta closed by Ic \&Ta , Ic \&It .El .Ss In-line Closed by the end of the line, fixed argument lengths, and/or subsequent macros. In-line macros have only text children. If a number (or inequality) of arguments is .Pq n , then the macro accepts an arbitrary number of arguments. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB \(lBres...\(rB \&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB Yc... \&.Yo \(lB\-arg \(lBval...\(rB\(rB arg0 arg1 argN .Ed .Bl -column "MacroX" "CallableX" "ParsedX" "Arguments" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Arguments .It Ic \&%A Ta \&No Ta \&No Ta >0 .It Ic \&%B Ta \&No Ta \&No Ta >0 .It Ic \&%C Ta \&No Ta \&No Ta >0 .It Ic \&%D Ta \&No Ta \&No Ta >0 .It Ic \&%I Ta \&No Ta \&No Ta >0 .It Ic \&%J Ta \&No Ta \&No Ta >0 .It Ic \&%N Ta \&No Ta \&No Ta >0 .It Ic \&%O Ta \&No Ta \&No Ta >0 .It Ic \&%P Ta \&No Ta \&No Ta >0 .It Ic \&%Q Ta \&No Ta \&No Ta >0 .It Ic \&%R Ta \&No Ta \&No Ta >0 .It Ic \&%T Ta \&No Ta \&No Ta >0 .It Ic \&%U Ta \&No Ta \&No Ta >0 .It Ic \&%V Ta \&No Ta \&No Ta >0 .It Ic \&Ad Ta Yes Ta Yes Ta >0 .It Ic \&An Ta Yes Ta Yes Ta >0 .It Ic \&Ap Ta Yes Ta Yes Ta 0 .It Ic \&Ar Ta Yes Ta Yes Ta n .It Ic \&At Ta Yes Ta Yes Ta 1 .It Ic \&Bsx Ta Yes Ta Yes Ta n .It Ic \&Bt Ta \&No Ta \&No Ta 0 .It Ic \&Bx Ta Yes Ta Yes Ta n .It Ic \&Cd Ta Yes Ta Yes Ta >0 .It Ic \&Cm Ta Yes Ta Yes Ta >0 .It Ic \&Db Ta \&No Ta \&No Ta 1 .It Ic \&Dd Ta \&No Ta \&No Ta n .It Ic \&Dt Ta \&No Ta \&No Ta n .It Ic \&Dv Ta Yes Ta Yes Ta >0 .It Ic \&Dx Ta Yes Ta Yes Ta n .It Ic \&Em Ta Yes Ta Yes Ta >0 .It Ic \&Er Ta Yes Ta Yes Ta >0 .It Ic \&Es Ta Yes Ta Yes Ta 2 .It Ic \&Ev Ta Yes Ta Yes Ta >0 .It Ic \&Ex Ta \&No Ta \&No Ta n .It Ic \&Fa Ta Yes Ta Yes Ta >0 .It Ic \&Fd Ta \&No Ta \&No Ta >0 .It Ic \&Fl Ta Yes Ta Yes Ta n .It Ic \&Fn Ta Yes Ta Yes Ta >0 .It Ic \&Fr Ta Yes Ta Yes Ta >0 .It Ic \&Ft Ta Yes Ta Yes Ta >0 .It Ic \&Fx Ta Yes Ta Yes Ta n .It Ic \&Hf Ta \&No Ta \&No Ta n .It Ic \&Ic Ta Yes Ta Yes Ta >0 .It Ic \&In Ta Yes Ta Yes Ta 1 -.It Ic \&Lb Ta \&No Ta \&No Ta 1 +.It Ic \&Lb Ta \&No Ta \&No Ta >0 .It Ic \&Li Ta Yes Ta Yes Ta >0 .It Ic \&Lk Ta Yes Ta Yes Ta >0 .It Ic \&Lp Ta \&No Ta \&No Ta 0 .It Ic \&Ms Ta Yes Ta Yes Ta >0 .It Ic \&Mt Ta Yes Ta Yes Ta >0 .It Ic \&Nm Ta Yes Ta Yes Ta n .It Ic \&No Ta Yes Ta Yes Ta >0 .It Ic \&Ns Ta Yes Ta Yes Ta 0 .It Ic \&Nx Ta Yes Ta Yes Ta n .It Ic \&Os Ta \&No Ta \&No Ta n .It Ic \&Ot Ta Yes Ta Yes Ta >0 .It Ic \&Ox Ta Yes Ta Yes Ta n .It Ic \&Pa Ta Yes Ta Yes Ta n .It Ic \&Pf Ta Yes Ta Yes Ta 1 .It Ic \&Pp Ta \&No Ta \&No Ta 0 .It Ic \&Rv Ta \&No Ta \&No Ta n .It Ic \&Sm Ta \&No Ta \&No Ta <2 .It Ic \&St Ta \&No Ta Yes Ta 1 .It Ic \&Sx Ta Yes Ta Yes Ta >0 .It Ic \&Sy Ta Yes Ta Yes Ta >0 .It Ic \&Tg Ta \&No Ta \&No Ta <2 .It Ic \&Tn Ta Yes Ta Yes Ta >0 .It Ic \&Ud Ta \&No Ta \&No Ta 0 .It Ic \&Ux Ta Yes Ta Yes Ta n .It Ic \&Va Ta Yes Ta Yes Ta n .It Ic \&Vt Ta Yes Ta Yes Ta >0 .It Ic \&Xr Ta Yes Ta Yes Ta 2 .El .Ss Delimiters When a macro argument consists of one single input character considered as a delimiter, the argument gets special handling. This does not apply when delimiters appear in arguments containing more than one character. Consequently, to prevent special handling and just handle it like any other argument, a delimiter can be escaped by prepending a zero-width space .Pq Sq \e& . In text lines, delimiters never need escaping, but may be used as normal punctuation. .Pp For many macros, when the leading arguments are opening delimiters, these delimiters are put before the macro scope, and when the trailing arguments are closing delimiters, these delimiters are put after the macro scope. Spacing is suppressed after opening delimiters and before closing delimiters. For example, .Pp .D1 Pf \. \&Aq "( [ word ] ) ." .Pp renders as: .Pp .D1 Aq ( [ word ] ) . .Pp Opening delimiters are: .Pp .Bl -tag -width Ds -offset indent -compact .It \&( left parenthesis .It \&[ left bracket .El .Pp Closing delimiters are: .Pp .Bl -tag -width Ds -offset indent -compact .It \&. period .It \&, comma .It \&: colon .It \&; semicolon .It \&) right parenthesis .It \&] right bracket .It \&? question mark .It \&! exclamation mark .El .Pp Note that even a period preceded by a backslash .Pq Sq \e.\& gets this special handling; use .Sq \e&.\& to prevent that. .Pp Many in-line macros interrupt their scope when they encounter delimiters, and resume their scope when more arguments follow that are not delimiters. For example, .Pp .D1 Pf \. \&Fl "a ( b | c \e*(Ba d ) e" .Pp renders as: .Pp .D1 Fl a ( b | c \*(Ba d ) e .Pp This applies to both opening and closing delimiters, and also to the middle delimiter, which does not suppress spacing: .Pp .Bl -tag -width Ds -offset indent -compact .It \&| vertical bar .El .Pp As a special case, the predefined string \e*(Ba is handled and rendered in the same way as a plain .Sq \&| character. Using this predefined string is not recommended in new manuals. .Pp Appending a zero-width space .Pq Sq \e& to the end of an input line is also useful to prevent the interpretation of a trailing period, exclamation or question mark as the end of a sentence, for example when an abbreviation happens to occur at the end of a text or macro input line. .Ss Font handling In .Nm documents, usage of semantic markup is recommended in order to have proper fonts automatically selected; only when no fitting semantic markup is available, consider falling back to .Sx Physical markup macros. Whenever any .Nm macro switches the .Xr roff 7 font mode, it will automatically restore the previous font when exiting its scope. Manually switching the font using the .Xr roff 7 .Ql \ef font escape sequences is never required. .Sh COMPATIBILITY This section provides an incomplete list of compatibility issues between mandoc and GNU troff .Pq Qq groff . .Pp The following problematic behaviour is found in groff: .Pp .Bl -dash -compact .It .Ic \&Pa does not format its arguments when used in the FILES section under certain list types. .It .Ic \&Ta can only be called by other macros, but not at the beginning of a line. .It .Sq \ef .Pq font face and .Sq \eF .Pq font family face .Sx Text Decoration escapes behave irregularly when specified within line-macro scopes. .It Negative scaling units return to prior lines. Instead, mandoc truncates them to zero. .El .Pp The following features are unimplemented in mandoc: .Pp .Bl -dash -compact .It .Ic \&Bd Fl file Ar file is unsupported for security reasons. .It .Ic \&Bd .Fl filled does not adjust the right margin, but is an alias for .Ic \&Bd .Fl ragged . .It .Ic \&Bd .Fl literal does not use a literal font, but is an alias for .Ic \&Bd .Fl unfilled . .It .Ic \&Bd .Fl offset Cm center and .Fl offset Cm right don't work. Groff does not implement centered and flush-right rendering either, but produces large indentations. .El .Sh SEE ALSO .Xr man 1 , .Xr mandoc 1 , .Xr eqn 7 , .Xr man 7 , .Xr mandoc_char 7 , .Xr roff 7 , .Xr tbl 7 .Pp The web page .Lk https://mandoc.bsd.lv/mdoc/ "extended documentation for the mdoc language" provides a few tutorial-style pages for beginners, an extensive style guide for advanced authors, and an alphabetic index helping to choose the best macros for various kinds of content. .Pp The manual page .Lk https://man.voidlinux.org/groff_mdoc "groff_mdoc(7)" contained in the .Dq groff package documents exactly the same language in a somewhat different style. .Sh HISTORY The .Nm language first appeared as a troff macro package in .Bx 4.4 . It was later significantly updated by Werner Lemberg and Ruslan Ermilov in groff-1.17. The standalone implementation that is part of the .Xr mandoc 1 utility written by Kristaps Dzonsons appeared in .Ox 4.6 . .Sh AUTHORS The .Nm reference was written by .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv . diff --git a/contrib/mandoc/mdoc_macro.c b/contrib/mandoc/mdoc_macro.c index 889b80a64a68..14b1ba0d228a 100644 --- a/contrib/mandoc/mdoc_macro.c +++ b/contrib/mandoc/mdoc_macro.c @@ -1,1608 +1,1607 @@ -/* $Id: mdoc_macro.c,v 1.235 2022/04/14 16:43:44 schwarze Exp $ */ +/* $Id: mdoc_macro.c,v 1.237 2025/06/13 14:24:56 schwarze Exp $ */ /* * Copyright (c) 2010, 2012-2021 Ingo Schwarze * Copyright (c) 2008-2012 Kristaps Dzonsons * * 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 #include #include #include #include #include #include #if DEBUG_MEMORY #include "mandoc_dbg.h" #endif #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "libmandoc.h" #include "roff_int.h" #include "libmdoc.h" static void blk_full(MACRO_PROT_ARGS); static void blk_exp_close(MACRO_PROT_ARGS); static void blk_part_exp(MACRO_PROT_ARGS); static void blk_part_imp(MACRO_PROT_ARGS); static void ctx_synopsis(MACRO_PROT_ARGS); static void in_line_eoln(MACRO_PROT_ARGS); static void in_line_argn(MACRO_PROT_ARGS); static void in_line(MACRO_PROT_ARGS); static void phrase_ta(MACRO_PROT_ARGS); static void append_delims(struct roff_man *, int, int *, char *); static void dword(struct roff_man *, int, int, const char *, enum mdelim, int); static int find_pending(struct roff_man *, enum roff_tok, int, int, struct roff_node *); static int lookup(struct roff_man *, int, int, int, const char *); static int macro_or_word(MACRO_PROT_ARGS, char *, int); static void break_intermediate(struct roff_node *, struct roff_node *); static int parse_rest(struct roff_man *, enum roff_tok, int, int *, char *); static enum roff_tok rew_alt(enum roff_tok); static void rew_elem(struct roff_man *, enum roff_tok); static void rew_last(struct roff_man *, const struct roff_node *); static void rew_pending(struct roff_man *, const struct roff_node *); static const struct mdoc_macro mdoc_macros[MDOC_MAX - MDOC_Dd] = { { in_line_eoln, MDOC_PROLOGUE | MDOC_JOIN }, /* Dd */ { in_line_eoln, MDOC_PROLOGUE }, /* Dt */ { in_line_eoln, MDOC_PROLOGUE }, /* Os */ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* Sh */ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* Ss */ { in_line_eoln, 0 }, /* Pp */ { blk_part_imp, MDOC_PARSED | MDOC_JOIN }, /* D1 */ { blk_part_imp, MDOC_PARSED | MDOC_JOIN }, /* Dl */ { blk_full, MDOC_EXPLICIT }, /* Bd */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ed */ { blk_full, MDOC_EXPLICIT }, /* Bl */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* El */ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* It */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* An */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM | MDOC_JOIN }, /* Ap */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Cd */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cm */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Dv */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Er */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ev */ { in_line_eoln, 0 }, /* Ex */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fa */ { in_line_eoln, 0 }, /* Fd */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fl */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fn */ - { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ft */ + { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ft */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ic */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* In */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Li */ { blk_full, MDOC_JOIN }, /* Nd */ { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Nm */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Op */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ot */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Pa */ { in_line_eoln, 0 }, /* Rv */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* St */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Va */ { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Vt */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Xr */ { in_line_eoln, MDOC_JOIN }, /* %A */ { in_line_eoln, MDOC_JOIN }, /* %B */ { in_line_eoln, MDOC_JOIN }, /* %D */ { in_line_eoln, MDOC_JOIN }, /* %I */ { in_line_eoln, MDOC_JOIN }, /* %J */ { in_line_eoln, 0 }, /* %N */ { in_line_eoln, MDOC_JOIN }, /* %O */ { in_line_eoln, 0 }, /* %P */ { in_line_eoln, MDOC_JOIN }, /* %R */ { in_line_eoln, MDOC_JOIN }, /* %T */ { in_line_eoln, 0 }, /* %V */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Ac */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Ao */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Aq */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* At */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Bc */ { blk_full, MDOC_EXPLICIT }, /* Bf */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Bo */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Bq */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bsx */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bx */ { in_line_eoln, 0 }, /* Db */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Dc */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Do */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Dq */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Ec */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ef */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Em */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Eo */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Fx */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ms */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* No */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM | MDOC_JOIN }, /* Ns */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Nx */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ox */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Pc */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Pf */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Po */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Pq */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Qc */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ql */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Qo */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Qq */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Re */ { blk_full, MDOC_EXPLICIT }, /* Rs */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Sc */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* So */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sq */ { in_line_argn, 0 }, /* Sm */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sx */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sy */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ux */ { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Xc */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Xo */ { blk_full, MDOC_EXPLICIT | MDOC_CALLABLE }, /* Fo */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Fc */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Oo */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Oc */ { blk_full, MDOC_EXPLICIT }, /* Bk */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ek */ { in_line_eoln, 0 }, /* Bt */ { in_line_eoln, 0 }, /* Hf */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fr */ { in_line_eoln, 0 }, /* Ud */ { in_line, 0 }, /* Lb */ { in_line_eoln, 0 }, /* Lp */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Lk */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Mt */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Brq */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Bro */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Brc */ { in_line_eoln, MDOC_JOIN }, /* %C */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Es */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* En */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Dx */ { in_line_eoln, MDOC_JOIN }, /* %Q */ { in_line_eoln, 0 }, /* %U */ { phrase_ta, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ta */ { in_line_eoln, 0 }, /* Tg */ }; const struct mdoc_macro * mdoc_macro(enum roff_tok tok) { assert(tok >= MDOC_Dd && tok < MDOC_MAX); return mdoc_macros + (tok - MDOC_Dd); } /* * This is called at the end of parsing. It must traverse up the tree, * closing out open [implicit] scopes. Obviously, open explicit scopes * are errors. */ void mdoc_endparse(struct roff_man *mdoc) { struct roff_node *n; /* Scan for open explicit scopes. */ n = mdoc->last->flags & NODE_VALID ? mdoc->last->parent : mdoc->last; for ( ; n; n = n->parent) if (n->type == ROFFT_BLOCK && mdoc_macro(n->tok)->flags & MDOC_EXPLICIT) mandoc_msg(MANDOCERR_BLK_NOEND, n->line, n->pos, "%s", roff_name[n->tok]); /* Rewind to the first. */ rew_last(mdoc, mdoc->meta.first); } /* * Look up the macro at *p called by "from", * or as a line macro if from == TOKEN_NONE. */ static int lookup(struct roff_man *mdoc, int from, int line, int ppos, const char *p) { enum roff_tok res; if (mdoc->flags & MDOC_PHRASEQF) { mdoc->flags &= ~MDOC_PHRASEQF; return TOKEN_NONE; } if (from == TOKEN_NONE || mdoc_macro(from)->flags & MDOC_PARSED) { res = roffhash_find(mdoc->mdocmac, p, 0); if (res != TOKEN_NONE) { if (mdoc_macro(res)->flags & MDOC_CALLABLE) return res; mandoc_msg(MANDOCERR_MACRO_CALL, line, ppos, "%s", p); } } return TOKEN_NONE; } /* * Rewind up to and including a specific node. */ static void rew_last(struct roff_man *mdoc, const struct roff_node *to) { if (to->flags & NODE_VALID) return; while (mdoc->last != to) { mdoc_state(mdoc, mdoc->last); mdoc->last->flags |= NODE_VALID | NODE_ENDED; mdoc->last = mdoc->last->parent; } mdoc_state(mdoc, mdoc->last); mdoc->last->flags |= NODE_VALID | NODE_ENDED; mdoc->next = ROFF_NEXT_SIBLING; } /* * Rewind up to a specific block, including all blocks that broke it. */ static void rew_pending(struct roff_man *mdoc, const struct roff_node *n) { for (;;) { rew_last(mdoc, n); if (mdoc->last == n) { switch (n->type) { case ROFFT_HEAD: roff_body_alloc(mdoc, n->line, n->pos, n->tok); if (n->tok == MDOC_Ss) mdoc->flags &= ~ROFF_NONOFILL; break; case ROFFT_BLOCK: break; default: return; } if ( ! (n->flags & NODE_BROKEN)) return; } else n = mdoc->last; for (;;) { if ((n = n->parent) == NULL) return; if (n->type == ROFFT_BLOCK || n->type == ROFFT_HEAD) { if (n->flags & NODE_ENDED) break; else return; } } } } /* * For a block closing macro, return the corresponding opening one. * Otherwise, return the macro itself. */ static enum roff_tok rew_alt(enum roff_tok tok) { switch (tok) { case MDOC_Ac: return MDOC_Ao; case MDOC_Bc: return MDOC_Bo; case MDOC_Brc: return MDOC_Bro; case MDOC_Dc: return MDOC_Do; case MDOC_Ec: return MDOC_Eo; case MDOC_Ed: return MDOC_Bd; case MDOC_Ef: return MDOC_Bf; case MDOC_Ek: return MDOC_Bk; case MDOC_El: return MDOC_Bl; case MDOC_Fc: return MDOC_Fo; case MDOC_Oc: return MDOC_Oo; case MDOC_Pc: return MDOC_Po; case MDOC_Qc: return MDOC_Qo; case MDOC_Re: return MDOC_Rs; case MDOC_Sc: return MDOC_So; case MDOC_Xc: return MDOC_Xo; default: return tok; } } static void rew_elem(struct roff_man *mdoc, enum roff_tok tok) { struct roff_node *n; n = mdoc->last; if (n->type != ROFFT_ELEM) n = n->parent; assert(n->type == ROFFT_ELEM); assert(tok == n->tok); rew_last(mdoc, n); } static void break_intermediate(struct roff_node *n, struct roff_node *breaker) { if (n != breaker && n->type != ROFFT_BLOCK && n->type != ROFFT_HEAD && (n->type != ROFFT_BODY || n->end != ENDBODY_NOT)) n = n->parent; while (n != breaker) { if ( ! (n->flags & NODE_VALID)) n->flags |= NODE_BROKEN; n = n->parent; } } /* * If there is an open sub-block of the target requiring * explicit close-out, postpone closing out the target until * the rew_pending() call closing out the sub-block. */ static int find_pending(struct roff_man *mdoc, enum roff_tok tok, int line, int ppos, struct roff_node *target) { struct roff_node *n; int irc; if (target->flags & NODE_VALID) return 0; irc = 0; for (n = mdoc->last; n != NULL && n != target; n = n->parent) { if (n->flags & NODE_ENDED) continue; if (n->type == ROFFT_BLOCK && mdoc_macro(n->tok)->flags & MDOC_EXPLICIT) { irc = 1; break_intermediate(mdoc->last, target); if (target->type == ROFFT_HEAD) target->flags |= NODE_ENDED; else if ( ! (target->flags & NODE_ENDED)) { mandoc_msg(MANDOCERR_BLK_NEST, line, ppos, "%s breaks %s", roff_name[tok], roff_name[n->tok]); mdoc_endbody_alloc(mdoc, line, ppos, tok, target); } } } return irc; } /* * Allocate a word and check whether it's punctuation or not. * Punctuation consists of those tokens found in mdoc_isdelim(). */ static void dword(struct roff_man *mdoc, int line, int col, const char *p, enum mdelim d, int may_append) { if (d == DELIM_MAX) d = mdoc_isdelim(p); - if (may_append && - ! (mdoc->flags & (MDOC_SYNOPSIS | MDOC_KEEP | MDOC_SMOFF)) && + if (may_append && ! (mdoc->flags & (MDOC_KEEP | MDOC_SMOFF)) && d == DELIM_NONE && mdoc->last->type == ROFFT_TEXT && mdoc_isdelim(mdoc->last->string) == DELIM_NONE) { roff_word_append(mdoc, p); return; } roff_word_alloc(mdoc, line, col, p); /* * If the word consists of a bare delimiter, * flag the new node accordingly, * unless doing so was vetoed by the invoking macro. * Always clear the veto, it is only valid for one word. */ if (d == DELIM_OPEN) mdoc->last->flags |= NODE_DELIMO; else if (d == DELIM_CLOSE && ! (mdoc->flags & MDOC_NODELIMC) && mdoc->last->parent->tok != MDOC_Fd) mdoc->last->flags |= NODE_DELIMC; mdoc->flags &= ~MDOC_NODELIMC; } static void append_delims(struct roff_man *mdoc, int line, int *pos, char *buf) { char *p; int la; enum margserr ac; if (buf[*pos] == '\0') return; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, TOKEN_NONE, &p); if (ac == ARGS_EOLN) break; dword(mdoc, line, la, p, DELIM_MAX, 1); /* * If we encounter end-of-sentence symbols, then trigger * the double-space. * * XXX: it's easy to allow this to propagate outward to * the last symbol, such that `. )' will cause the * correct double-spacing. However, (1) groff isn't * smart enough to do this and (2) it would require * knowing which symbols break this behaviour, for * example, `. ;' shouldn't propagate the double-space. */ if (mandoc_eos(p, strlen(p))) mdoc->last->flags |= NODE_EOS; if (ac == ARGS_ALLOC) free(p); } } /* * Parse one word. * If it is a macro, call it and return 1. * Otherwise, allocate it and return 0. */ static int macro_or_word(MACRO_PROT_ARGS, char *p, int parsed) { int ntok; ntok = buf[ppos] == '"' || parsed == 0 || mdoc->flags & MDOC_PHRASELIT ? TOKEN_NONE : lookup(mdoc, tok, line, ppos, p); if (ntok == TOKEN_NONE) { dword(mdoc, line, ppos, p, DELIM_MAX, tok == TOKEN_NONE || mdoc_macro(tok)->flags & MDOC_JOIN); return 0; } else { if (tok != TOKEN_NONE && mdoc_macro(tok)->fp == in_line_eoln) rew_elem(mdoc, tok); (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, ppos, pos, buf); if (tok == TOKEN_NONE) append_delims(mdoc, line, pos, buf); return 1; } } /* * Close out block partial/full explicit. */ static void blk_exp_close(MACRO_PROT_ARGS) { struct roff_node *body; /* Our own body. */ struct roff_node *endbody; /* Our own end marker. */ struct roff_node *itblk; /* An It block starting later. */ struct roff_node *later; /* A sub-block starting later. */ struct roff_node *n; /* Search back to our block. */ struct roff_node *target; /* For find_pending(). */ int j, lastarg, maxargs, nl, pending; enum margserr ac; enum roff_tok atok, ntok; char *p; nl = MDOC_NEWLINE & mdoc->flags; switch (tok) { case MDOC_Ec: maxargs = 1; break; case MDOC_Ek: mdoc->flags &= ~MDOC_KEEP; /* FALLTHROUGH */ default: maxargs = 0; break; } /* Search backwards for the beginning of our own body. */ atok = rew_alt(tok); body = NULL; for (n = mdoc->last; n; n = n->parent) { if (n->flags & NODE_ENDED || n->tok != atok || n->type != ROFFT_BODY || n->end != ENDBODY_NOT) continue; body = n; break; } /* * Search backwards for beginnings of blocks, * both of our own and of pending sub-blocks. */ endbody = itblk = later = NULL; for (n = mdoc->last; n; n = n->parent) { if (n->flags & NODE_ENDED) continue; /* * Mismatching end macros can never break anything * and we only care about the breaking of BLOCKs. */ if (body == NULL || n->type != ROFFT_BLOCK) continue; /* * SYNOPSIS name blocks can not be broken themselves, * but they do get broken together with a broken child. */ if (n->tok == MDOC_Nm) { if (later != NULL) n->flags |= NODE_BROKEN | NODE_ENDED; continue; } if (n->tok == MDOC_It) { itblk = n; continue; } if (atok == n->tok) { /* * Found the start of our own block. * When there is no pending sub block, * just proceed to closing out. */ if (later == NULL || (tok == MDOC_El && itblk == NULL)) break; /* * When there is a pending sub block, postpone * closing out the current block until the * rew_pending() closing out the sub-block. * Mark the place where the formatting - but not * the scope - of the current block ends. */ mandoc_msg(MANDOCERR_BLK_NEST, line, ppos, "%s breaks %s", roff_name[atok], roff_name[later->tok]); endbody = mdoc_endbody_alloc(mdoc, line, ppos, atok, body); if (tok == MDOC_El) itblk->flags |= NODE_ENDED | NODE_BROKEN; /* * If a block closing macro taking arguments * breaks another block, put the arguments * into the end marker. */ if (maxargs) mdoc->next = ROFF_NEXT_CHILD; break; } /* * Explicit blocks close out description lines, but * even those can get broken together with a child. */ if (n->tok == MDOC_Nd) { if (later != NULL) n->flags |= NODE_BROKEN | NODE_ENDED; else rew_last(mdoc, n); continue; } /* Breaking an open sub block. */ break_intermediate(mdoc->last, body); n->flags |= NODE_BROKEN; if (later == NULL) later = n; } if (body == NULL) { mandoc_msg(MANDOCERR_BLK_NOTOPEN, line, ppos, "%s", roff_name[tok]); if (maxargs && endbody == NULL) { /* * Stray .Ec without previous .Eo: * Break the output line, keep the arguments. */ roff_elem_alloc(mdoc, line, ppos, ROFF_br); rew_elem(mdoc, ROFF_br); } } else if (endbody == NULL) { rew_last(mdoc, body); if (maxargs) mdoc_tail_alloc(mdoc, line, ppos, atok); } if ((mdoc_macro(tok)->flags & MDOC_PARSED) == 0) { if (buf[*pos] != '\0') mandoc_msg(MANDOCERR_ARG_SKIP, line, ppos, "%s %s", roff_name[tok], buf + *pos); if (endbody == NULL && n != NULL) rew_pending(mdoc, n); /* * Restore the fill mode that was set before the display. * This needs to be done here rather than during validation * such that subsequent nodes get the right flags. */ if (tok == MDOC_Ed && body != NULL) { if (body->flags & NODE_NOFILL) mdoc->flags |= ROFF_NOFILL; else mdoc->flags &= ~ROFF_NOFILL; } return; } if (endbody != NULL) n = endbody; ntok = TOKEN_NONE; for (j = 0; ; j++) { lastarg = *pos; if (j == maxargs && n != NULL) rew_last(mdoc, n); ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_PUNCT || ac == ARGS_EOLN) break; ntok = lookup(mdoc, tok, line, lastarg, p); if (ntok == TOKEN_NONE) { dword(mdoc, line, lastarg, p, DELIM_MAX, mdoc_macro(tok)->flags & MDOC_JOIN); if (ac == ARGS_ALLOC) free(p); continue; } if (ac == ARGS_ALLOC) free(p); if (n != NULL) rew_last(mdoc, n); mdoc->flags &= ~MDOC_NEWLINE; (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, lastarg, pos, buf); break; } if (n != NULL) { pending = 0; if (ntok != TOKEN_NONE && n->flags & NODE_BROKEN) { target = n; do target = target->parent; while ( ! (target->flags & NODE_ENDED)); pending = find_pending(mdoc, ntok, line, ppos, target); } if ( ! pending) rew_pending(mdoc, n); } if (nl) append_delims(mdoc, line, pos, buf); } static void in_line(MACRO_PROT_ARGS) { int la, scope, cnt, firstarg, mayopen, nc, nl; enum roff_tok ntok; enum margserr ac; enum mdelim d; struct mdoc_arg *arg; char *p; nl = MDOC_NEWLINE & mdoc->flags; /* * Whether we allow ignored elements (those without content, * usually because of reserved words) to squeak by. */ switch (tok) { case MDOC_An: case MDOC_Ar: case MDOC_Fl: case MDOC_Mt: case MDOC_Nm: case MDOC_Pa: nc = 1; break; default: nc = 0; break; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); d = DELIM_NONE; firstarg = 1; mayopen = 1; for (cnt = scope = 0;; ) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); /* * At the end of a macro line, * opening delimiters do not suppress spacing. */ if (ac == ARGS_EOLN) { if (d == DELIM_OPEN) mdoc->last->flags &= ~NODE_DELIMO; break; } /* * The rest of the macro line is only punctuation, * to be handled by append_delims(). * If there were no other arguments, * do not allow the first one to suppress spacing, * even if it turns out to be a closing one. */ if (ac == ARGS_PUNCT) { if (cnt == 0 && (nc == 0 || tok == MDOC_An)) mdoc->flags |= MDOC_NODELIMC; break; } ntok = (tok == MDOC_Fn && !cnt) ? TOKEN_NONE : lookup(mdoc, tok, line, la, p); /* * In this case, we've located a submacro and must * execute it. Close out scope, if open. If no * elements have been generated, either create one (nc) * or raise a warning. */ if (ntok != TOKEN_NONE) { if (scope) rew_elem(mdoc, tok); if (nc && ! cnt) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); rew_last(mdoc, mdoc->last); } else if ( ! nc && ! cnt) { mdoc_argv_free(arg); mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); } (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, la, pos, buf); if (nl) append_delims(mdoc, line, pos, buf); if (ac == ARGS_ALLOC) free(p); return; } /* * Handle punctuation. Set up our scope, if a word; * rewind the scope, if a delimiter; then append the word. */ if ((d = mdoc_isdelim(p)) != DELIM_NONE) { /* * If we encounter closing punctuation, no word * has been emitted, no scope is open, and we're * allowed to have an empty element, then start * a new scope. */ if ((d == DELIM_CLOSE || (d == DELIM_MIDDLE && tok == MDOC_Fl)) && !cnt && !scope && nc && mayopen) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); scope = 1; cnt++; if (tok == MDOC_Nm) mayopen = 0; } /* * Close out our scope, if one is open, before * any punctuation. */ if (scope && tok != MDOC_Lk) { rew_elem(mdoc, tok); scope = 0; if (tok == MDOC_Fn) mayopen = 0; } } else if (mayopen && !scope) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); scope = 1; cnt++; } dword(mdoc, line, la, p, d, mdoc_macro(tok)->flags & MDOC_JOIN); if (ac == ARGS_ALLOC) free(p); /* * If the first argument is a closing delimiter, * do not suppress spacing before it. */ if (firstarg && d == DELIM_CLOSE && !nc) mdoc->last->flags &= ~NODE_DELIMC; firstarg = 0; /* * `Fl' macros have their scope re-opened with each new * word so that the `-' can be added to each one without * having to parse out spaces. */ if (scope && tok == MDOC_Fl) { rew_elem(mdoc, tok); scope = 0; } } if (scope && tok != MDOC_Lk) { rew_elem(mdoc, tok); scope = 0; } /* * If no elements have been collected and we're allowed to have * empties (nc), open a scope and close it out. Otherwise, * raise a warning. */ if ( ! cnt) { if (nc) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); rew_last(mdoc, mdoc->last); } else { mdoc_argv_free(arg); mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); } } if (nl) append_delims(mdoc, line, pos, buf); if (scope) rew_elem(mdoc, tok); } static void blk_full(MACRO_PROT_ARGS) { struct mdoc_arg *arg; struct roff_node *blk; /* Our own or a broken block. */ struct roff_node *head; /* Our own head. */ struct roff_node *body; /* Our own body. */ struct roff_node *n; char *p; size_t iarg; int done, la, nl, parsed; enum margserr ac, lac; nl = MDOC_NEWLINE & mdoc->flags; if (buf[*pos] == '\0' && (tok == MDOC_Sh || tok == MDOC_Ss)) { mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); return; } if ((mdoc_macro(tok)->flags & MDOC_EXPLICIT) == 0) { /* Here, tok is one of Sh Ss Nm Nd It. */ blk = NULL; for (n = mdoc->last; n != NULL; n = n->parent) { if (n->flags & NODE_ENDED) { if ( ! (n->flags & NODE_VALID)) n->flags |= NODE_BROKEN; continue; } if (n->type != ROFFT_BLOCK) continue; if (tok == MDOC_It && n->tok == MDOC_Bl) { if (blk != NULL) { mandoc_msg(MANDOCERR_BLK_BROKEN, line, ppos, "It breaks %s", roff_name[blk->tok]); rew_pending(mdoc, blk); } break; } if (mdoc_macro(n->tok)->flags & MDOC_EXPLICIT) { switch (tok) { case MDOC_Sh: case MDOC_Ss: mandoc_msg(MANDOCERR_BLK_BROKEN, line, ppos, "%s breaks %s", roff_name[tok], roff_name[n->tok]); rew_pending(mdoc, n); n = mdoc->last; continue; case MDOC_It: /* Delay in case it's astray. */ blk = n; continue; default: break; } break; } /* Here, n is one of Sh Ss Nm Nd It. */ if (tok != MDOC_Sh && (n->tok == MDOC_Sh || (tok != MDOC_Ss && (n->tok == MDOC_Ss || (tok != MDOC_It && n->tok == MDOC_It))))) break; /* Item breaking an explicit block. */ if (blk != NULL) { mandoc_msg(MANDOCERR_BLK_BROKEN, line, ppos, "It breaks %s", roff_name[blk->tok]); rew_pending(mdoc, blk); blk = NULL; } /* Close out prior implicit scopes. */ rew_pending(mdoc, n); } /* Skip items outside lists. */ if (tok == MDOC_It && (n == NULL || n->tok != MDOC_Bl)) { mandoc_msg(MANDOCERR_IT_STRAY, line, ppos, "It %s", buf + *pos); roff_elem_alloc(mdoc, line, ppos, ROFF_br); rew_elem(mdoc, ROFF_br); return; } } /* * This routine accommodates implicitly- and explicitly-scoped * macro openings. Implicit ones first close out prior scope * (seen above). Delay opening the head until necessary to * allow leading punctuation to print. Special consideration * for `It -column', which has phrase-part syntax instead of * regular child nodes. */ switch (tok) { case MDOC_Sh: mdoc->flags &= ~ROFF_NOFILL; break; case MDOC_Ss: mdoc->flags |= ROFF_NONOFILL; break; default: break; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); blk = mdoc_block_alloc(mdoc, line, ppos, tok, arg); head = body = NULL; /* * Exception: Heads of `It' macros in `-diag' lists are not * parsed, even though `It' macros in general are parsed. */ parsed = tok != MDOC_It || mdoc->last->parent->tok != MDOC_Bl || mdoc->last->parent->norm->Bl.type != LIST_diag; /* * The `Nd' macro has all arguments in its body: it's a hybrid * of block partial-explicit and full-implicit. Stupid. */ if (tok == MDOC_Nd) { head = roff_head_alloc(mdoc, line, ppos, tok); rew_last(mdoc, head); body = roff_body_alloc(mdoc, line, ppos, tok); } if (tok == MDOC_Bk) mdoc->flags |= MDOC_KEEP; ac = ARGS_EOLN; for (;;) { /* * If we are right after a tab character, * do not parse the first word for macros. */ if (mdoc->flags & MDOC_PHRASEQN) { mdoc->flags &= ~MDOC_PHRASEQN; mdoc->flags |= MDOC_PHRASEQF; } la = *pos; lac = ac; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_EOLN) { if (lac != ARGS_PHRASE || ! (mdoc->flags & MDOC_PHRASEQF)) break; /* * This line ends in a tab; start the next * column now, with a leading blank. */ if (body != NULL) rew_last(mdoc, body); body = roff_body_alloc(mdoc, line, ppos, tok); roff_word_alloc(mdoc, line, ppos, "\\&"); break; } if (tok == MDOC_Bd || tok == MDOC_Bk) { mandoc_msg(MANDOCERR_ARG_EXCESS, line, la, "%s ... %s", roff_name[tok], buf + la); if (ac == ARGS_ALLOC) free(p); break; } if (tok == MDOC_Rs) { mandoc_msg(MANDOCERR_ARG_SKIP, line, la, "Rs %s", buf + la); if (ac == ARGS_ALLOC) free(p); break; } if (ac == ARGS_PUNCT) break; /* * Emit leading punctuation (i.e., punctuation before * the ROFFT_HEAD) for non-phrase types. */ if (head == NULL && ac != ARGS_PHRASE && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); if (ac == ARGS_ALLOC) free(p); continue; } /* Open a head if one hasn't been opened. */ if (head == NULL) head = roff_head_alloc(mdoc, line, ppos, tok); if (ac == ARGS_PHRASE) { /* * If we haven't opened a body yet, rewind the * head; if we have, rewind that instead. */ rew_last(mdoc, body == NULL ? head : body); body = roff_body_alloc(mdoc, line, ppos, tok); /* Process to the tab or to the end of the line. */ mdoc->flags |= MDOC_PHRASE; parse_rest(mdoc, TOKEN_NONE, line, &la, buf); mdoc->flags &= ~MDOC_PHRASE; /* There may have been `Ta' macros. */ while (body->next != NULL) body = body->next; continue; } done = macro_or_word(mdoc, tok, line, la, pos, buf, p, parsed); if (ac == ARGS_ALLOC) free(p); if (done) break; } if (blk->flags & NODE_VALID) return; if (head == NULL) head = roff_head_alloc(mdoc, line, ppos, tok); if (nl && tok != MDOC_Bd && tok != MDOC_Bl && tok != MDOC_Rs) append_delims(mdoc, line, pos, buf); if (body != NULL) goto out; if (find_pending(mdoc, tok, line, ppos, head)) return; /* Close out scopes to remain in a consistent state. */ rew_last(mdoc, head); body = roff_body_alloc(mdoc, line, ppos, tok); if (tok == MDOC_Ss) mdoc->flags &= ~ROFF_NONOFILL; /* * Set up fill mode for display blocks. * This needs to be done here up front rather than during * validation such that child nodes get the right flags. */ if (tok == MDOC_Bd && arg != NULL) { for (iarg = 0; iarg < arg->argc; iarg++) { switch (arg->argv[iarg].arg) { case MDOC_Unfilled: case MDOC_Literal: mdoc->flags |= ROFF_NOFILL; break; case MDOC_Filled: case MDOC_Ragged: case MDOC_Centred: mdoc->flags &= ~ROFF_NOFILL; break; default: continue; } break; } } out: if (mdoc->flags & MDOC_FREECOL) { rew_last(mdoc, body); rew_last(mdoc, blk); mdoc->flags &= ~MDOC_FREECOL; } } static void blk_part_imp(MACRO_PROT_ARGS) { int done, la, nl; enum margserr ac; char *p; struct roff_node *blk; /* saved block context */ struct roff_node *body; /* saved body context */ struct roff_node *n; nl = MDOC_NEWLINE & mdoc->flags; /* * A macro that spans to the end of the line. This is generally * (but not necessarily) called as the first macro. The block * has a head as the immediate child, which is always empty, * followed by zero or more opening punctuation nodes, then the * body (which may be empty, depending on the macro), then zero * or more closing punctuation nodes. */ blk = mdoc_block_alloc(mdoc, line, ppos, tok, NULL); rew_last(mdoc, roff_head_alloc(mdoc, line, ppos, tok)); /* * Open the body scope "on-demand", that is, after we've * processed all our the leading delimiters (open parenthesis, * etc.). */ for (body = NULL; ; ) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_EOLN || ac == ARGS_PUNCT) break; if (body == NULL && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); if (ac == ARGS_ALLOC) free(p); continue; } if (body == NULL) body = roff_body_alloc(mdoc, line, ppos, tok); done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1); if (ac == ARGS_ALLOC) free(p); if (done) break; } if (body == NULL) body = roff_body_alloc(mdoc, line, ppos, tok); if (find_pending(mdoc, tok, line, ppos, body)) return; rew_last(mdoc, body); if (nl) append_delims(mdoc, line, pos, buf); rew_pending(mdoc, blk); /* Move trailing .Ns out of scope. */ for (n = body->child; n && n->next; n = n->next) /* Do nothing. */ ; if (n && n->tok == MDOC_Ns) roff_node_relink(mdoc, n); } static void blk_part_exp(MACRO_PROT_ARGS) { int done, la, nl; enum margserr ac; struct roff_node *head; /* keep track of head */ char *p; nl = MDOC_NEWLINE & mdoc->flags; /* * The opening of an explicit macro having zero or more leading * punctuation nodes; a head with optional single element (the * case of `Eo'); and a body that may be empty. */ roff_block_alloc(mdoc, line, ppos, tok); head = NULL; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_PUNCT || ac == ARGS_EOLN) break; /* Flush out leading punctuation. */ if (head == NULL && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); if (ac == ARGS_ALLOC) free(p); continue; } if (head == NULL) { head = roff_head_alloc(mdoc, line, ppos, tok); if (tok == MDOC_Eo) /* Not parsed. */ dword(mdoc, line, la, p, DELIM_MAX, 0); rew_last(mdoc, head); roff_body_alloc(mdoc, line, ppos, tok); if (tok == MDOC_Eo) { if (ac == ARGS_ALLOC) free(p); continue; } } done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1); if (ac == ARGS_ALLOC) free(p); if (done) break; } /* Clean-up to leave in a consistent state. */ if (head == NULL) { rew_last(mdoc, roff_head_alloc(mdoc, line, ppos, tok)); roff_body_alloc(mdoc, line, ppos, tok); } if (nl) append_delims(mdoc, line, pos, buf); } static void in_line_argn(MACRO_PROT_ARGS) { struct mdoc_arg *arg; char *p; enum margserr ac; enum roff_tok ntok; int state; /* arg#; -1: not yet open; -2: closed */ int la, maxargs, nl; nl = mdoc->flags & MDOC_NEWLINE; /* * A line macro that has a fixed number of arguments (maxargs). * Only open the scope once the first non-leading-punctuation is * found (unless MDOC_IGNDELIM is noted, like in `Pf'), then * keep it open until the maximum number of arguments are * exhausted. */ switch (tok) { case MDOC_Ap: case MDOC_Ns: case MDOC_Ux: maxargs = 0; break; case MDOC_Bx: case MDOC_Es: case MDOC_Xr: maxargs = 2; break; default: maxargs = 1; break; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); state = -1; p = NULL; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if ((ac == ARGS_WORD || ac == ARGS_ALLOC) && state == -1 && (mdoc_macro(tok)->flags & MDOC_IGNDELIM) == 0 && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); if (ac == ARGS_ALLOC) free(p); continue; } if (state == -1 && tok != MDOC_In && tok != MDOC_St && tok != MDOC_Xr) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); state = 0; } if (ac == ARGS_PUNCT || ac == ARGS_EOLN) { if (abs(state) < 2 && tok == MDOC_Pf) mandoc_msg(MANDOCERR_PF_SKIP, line, ppos, "Pf %s", p == NULL ? "at eol" : p); break; } if (state == maxargs) { rew_elem(mdoc, tok); state = -2; } ntok = (tok == MDOC_Pf && state == 0) ? TOKEN_NONE : lookup(mdoc, tok, line, la, p); if (ntok != TOKEN_NONE) { if (state >= 0) { rew_elem(mdoc, tok); state = -2; } (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, la, pos, buf); if (ac == ARGS_ALLOC) free(p); break; } if (mdoc_macro(tok)->flags & MDOC_IGNDELIM || mdoc_isdelim(p) == DELIM_NONE) { if (state == -1) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); state = 1; } else if (state >= 0) state++; } else if (state >= 0) { rew_elem(mdoc, tok); state = -2; } dword(mdoc, line, la, p, DELIM_MAX, mdoc_macro(tok)->flags & MDOC_JOIN); if (ac == ARGS_ALLOC) free(p); p = mdoc->last->string; } if (state == -1) { mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); return; } if (state == 0 && tok == MDOC_Pf) append_delims(mdoc, line, pos, buf); if (state >= 0) rew_elem(mdoc, tok); if (nl) append_delims(mdoc, line, pos, buf); } static void in_line_eoln(MACRO_PROT_ARGS) { struct roff_node *n; struct mdoc_arg *arg; if ((tok == MDOC_Pp || tok == MDOC_Lp) && ! (mdoc->flags & MDOC_SYNOPSIS)) { n = mdoc->last; if (mdoc->next == ROFF_NEXT_SIBLING) n = n->parent; if (n->tok == MDOC_Nm) rew_last(mdoc, n->parent); } #if DEBUG_MEMORY if (tok == MDOC_Dt) mandoc_dbg_name(buf); #endif if (buf[*pos] == '\0' && (tok == MDOC_Fd || *roff_name[tok] == '%')) { mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); return; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); mdoc_elem_alloc(mdoc, line, ppos, tok, arg); if (parse_rest(mdoc, tok, line, pos, buf)) return; rew_elem(mdoc, tok); } /* * The simplest argument parser available: Parse the remaining * words until the end of the phrase or line and return 0 * or until the next macro, call that macro, and return 1. */ static int parse_rest(struct roff_man *mdoc, enum roff_tok tok, int line, int *pos, char *buf) { char *p; int done, la; enum margserr ac; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_EOLN) return 0; done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1); if (ac == ARGS_ALLOC) free(p); if (done) return 1; } } static void ctx_synopsis(MACRO_PROT_ARGS) { if (~mdoc->flags & (MDOC_SYNOPSIS | MDOC_NEWLINE)) in_line(mdoc, tok, line, ppos, pos, buf); else if (tok == MDOC_Nm) blk_full(mdoc, tok, line, ppos, pos, buf); else { assert(tok == MDOC_Vt); blk_part_imp(mdoc, tok, line, ppos, pos, buf); } } /* * Phrases occur within `Bl -column' entries, separated by `Ta' or tabs. * They're unusual because they're basically free-form text until a * macro is encountered. */ static void phrase_ta(MACRO_PROT_ARGS) { struct roff_node *body, *n; /* Make sure we are in a column list or ignore this macro. */ body = NULL; for (n = mdoc->last; n != NULL; n = n->parent) { if (n->flags & NODE_ENDED) continue; if (n->tok == MDOC_It && n->type == ROFFT_BODY) body = n; if (n->tok == MDOC_Bl && n->end == ENDBODY_NOT) break; } if (n == NULL || n->norm->Bl.type != LIST_column) { mandoc_msg(MANDOCERR_TA_STRAY, line, ppos, "Ta"); return; } /* Advance to the next column. */ rew_last(mdoc, body); roff_body_alloc(mdoc, line, ppos, MDOC_It); parse_rest(mdoc, TOKEN_NONE, line, pos, buf); } diff --git a/contrib/mandoc/mdoc_validate.c b/contrib/mandoc/mdoc_validate.c index 5271dfb523b9..4ca1253e4b70 100644 --- a/contrib/mandoc/mdoc_validate.c +++ b/contrib/mandoc/mdoc_validate.c @@ -1,3106 +1,3127 @@ -/* $Id: mdoc_validate.c,v 1.391 2022/06/08 16:31:46 schwarze Exp $ */ +/* $Id: mdoc_validate.c,v 1.393 2025/06/05 12:38:26 schwarze Exp $ */ /* - * Copyright (c) 2010-2021 Ingo Schwarze + * Copyright (c) 2010-2022, 2025 Ingo Schwarze * Copyright (c) 2008-2012 Kristaps Dzonsons * Copyright (c) 2010 Joerg Sonnenberger * * 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. * * Validation module for mdoc(7) syntax trees used by mandoc(1). */ #include "config.h" #include #ifndef OSNAME #include #endif #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc.h" #include "mandoc_xr.h" #include "roff.h" #include "mdoc.h" #include "libmandoc.h" #include "roff_int.h" #include "libmdoc.h" #include "tag.h" /* FIXME: .Bl -diag can't have non-text children in HEAD. */ #define POST_ARGS struct roff_man *mdoc enum check_ineq { CHECK_LT, CHECK_GT, CHECK_EQ }; typedef void (*v_post)(POST_ARGS); static int build_list(struct roff_man *, int); static void check_argv(struct roff_man *, struct roff_node *, struct mdoc_argv *); static void check_args(struct roff_man *, struct roff_node *); static void check_text(struct roff_man *, int, int, char *); static void check_text_em(struct roff_man *, int, int, char *); static void check_toptext(struct roff_man *, int, int, const char *); static int child_an(const struct roff_node *); static size_t macro2len(enum roff_tok); static void rewrite_macro2len(struct roff_man *, char **); static int similar(const char *, const char *); static void post_abort(POST_ARGS) __attribute__((__noreturn__)); static void post_an(POST_ARGS); static void post_an_norm(POST_ARGS); static void post_at(POST_ARGS); static void post_bd(POST_ARGS); static void post_bf(POST_ARGS); static void post_bk(POST_ARGS); static void post_bl(POST_ARGS); static void post_bl_block(POST_ARGS); static void post_bl_head(POST_ARGS); static void post_bl_norm(POST_ARGS); static void post_bx(POST_ARGS); static void post_defaults(POST_ARGS); static void post_display(POST_ARGS); static void post_dd(POST_ARGS); static void post_delim(POST_ARGS); static void post_delim_nb(POST_ARGS); static void post_dt(POST_ARGS); static void post_em(POST_ARGS); static void post_en(POST_ARGS); static void post_er(POST_ARGS); static void post_es(POST_ARGS); static void post_eoln(POST_ARGS); static void post_ex(POST_ARGS); static void post_fa(POST_ARGS); static void post_fl(POST_ARGS); static void post_fn(POST_ARGS); static void post_fname(POST_ARGS); static void post_fo(POST_ARGS); static void post_hyph(POST_ARGS); static void post_it(POST_ARGS); static void post_lb(POST_ARGS); static void post_nd(POST_ARGS); static void post_nm(POST_ARGS); static void post_ns(POST_ARGS); static void post_obsolete(POST_ARGS); static void post_os(POST_ARGS); static void post_par(POST_ARGS); static void post_prevpar(POST_ARGS); static void post_root(POST_ARGS); static void post_rs(POST_ARGS); static void post_rv(POST_ARGS); static void post_section(POST_ARGS); static void post_sh(POST_ARGS); static void post_sh_head(POST_ARGS); static void post_sh_name(POST_ARGS); static void post_sh_see_also(POST_ARGS); static void post_sh_authors(POST_ARGS); static void post_sm(POST_ARGS); static void post_st(POST_ARGS); static void post_std(POST_ARGS); static void post_sx(POST_ARGS); static void post_tag(POST_ARGS); static void post_tg(POST_ARGS); static void post_useless(POST_ARGS); static void post_xr(POST_ARGS); static void post_xx(POST_ARGS); static const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = { post_dd, /* Dd */ post_dt, /* Dt */ post_os, /* Os */ post_sh, /* Sh */ post_section, /* Ss */ post_par, /* Pp */ post_display, /* D1 */ post_display, /* Dl */ post_display, /* Bd */ NULL, /* Ed */ post_bl, /* Bl */ NULL, /* El */ post_it, /* It */ post_delim_nb, /* Ad */ post_an, /* An */ NULL, /* Ap */ post_defaults, /* Ar */ NULL, /* Cd */ post_tag, /* Cm */ post_tag, /* Dv */ post_er, /* Er */ post_tag, /* Ev */ post_ex, /* Ex */ post_fa, /* Fa */ NULL, /* Fd */ post_fl, /* Fl */ post_fn, /* Fn */ post_delim_nb, /* Ft */ post_tag, /* Ic */ post_delim_nb, /* In */ post_tag, /* Li */ post_nd, /* Nd */ post_nm, /* Nm */ post_delim_nb, /* Op */ post_abort, /* Ot */ post_defaults, /* Pa */ post_rv, /* Rv */ post_st, /* St */ post_tag, /* Va */ post_delim_nb, /* Vt */ post_xr, /* Xr */ NULL, /* %A */ post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */ NULL, /* %D */ NULL, /* %I */ NULL, /* %J */ post_hyph, /* %N */ post_hyph, /* %O */ NULL, /* %P */ post_hyph, /* %R */ post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */ NULL, /* %V */ NULL, /* Ac */ NULL, /* Ao */ post_delim_nb, /* Aq */ post_at, /* At */ NULL, /* Bc */ post_bf, /* Bf */ NULL, /* Bo */ NULL, /* Bq */ post_xx, /* Bsx */ post_bx, /* Bx */ post_obsolete, /* Db */ NULL, /* Dc */ NULL, /* Do */ NULL, /* Dq */ NULL, /* Ec */ NULL, /* Ef */ post_em, /* Em */ NULL, /* Eo */ post_xx, /* Fx */ post_tag, /* Ms */ post_tag, /* No */ post_ns, /* Ns */ post_xx, /* Nx */ post_xx, /* Ox */ NULL, /* Pc */ NULL, /* Pf */ NULL, /* Po */ post_delim_nb, /* Pq */ NULL, /* Qc */ post_delim_nb, /* Ql */ NULL, /* Qo */ post_delim_nb, /* Qq */ NULL, /* Re */ post_rs, /* Rs */ NULL, /* Sc */ NULL, /* So */ post_delim_nb, /* Sq */ post_sm, /* Sm */ post_sx, /* Sx */ post_em, /* Sy */ post_useless, /* Tn */ post_xx, /* Ux */ NULL, /* Xc */ NULL, /* Xo */ post_fo, /* Fo */ NULL, /* Fc */ NULL, /* Oo */ NULL, /* Oc */ post_bk, /* Bk */ NULL, /* Ek */ post_eoln, /* Bt */ post_obsolete, /* Hf */ post_obsolete, /* Fr */ post_eoln, /* Ud */ post_lb, /* Lb */ post_abort, /* Lp */ post_delim_nb, /* Lk */ post_defaults, /* Mt */ post_delim_nb, /* Brq */ NULL, /* Bro */ NULL, /* Brc */ NULL, /* %C */ post_es, /* Es */ post_en, /* En */ post_xx, /* Dx */ NULL, /* %Q */ NULL, /* %U */ NULL, /* Ta */ post_tg, /* Tg */ }; #define RSORD_MAX 14 /* Number of `Rs' blocks. */ static const enum roff_tok rsord[RSORD_MAX] = { MDOC__A, MDOC__T, MDOC__B, MDOC__I, MDOC__J, MDOC__R, MDOC__N, MDOC__V, MDOC__U, MDOC__P, MDOC__Q, MDOC__C, MDOC__D, MDOC__O }; static const char * const secnames[SEC__MAX] = { NULL, "NAME", "LIBRARY", "SYNOPSIS", "DESCRIPTION", "CONTEXT", "IMPLEMENTATION NOTES", "RETURN VALUES", "ENVIRONMENT", "FILES", "EXIT STATUS", "EXAMPLES", "DIAGNOSTICS", "COMPATIBILITY", "ERRORS", "SEE ALSO", "STANDARDS", "HISTORY", "AUTHORS", "CAVEATS", "BUGS", "SECURITY CONSIDERATIONS", NULL }; static int fn_prio = TAG_STRONG; /* Validate the subtree rooted at mdoc->last. */ void mdoc_validate(struct roff_man *mdoc) { struct roff_node *n, *np; const v_post *p; /* * Translate obsolete macros to modern macros first * such that later code does not need to look * for the obsolete versions. */ n = mdoc->last; switch (n->tok) { case MDOC_Lp: n->tok = MDOC_Pp; break; case MDOC_Ot: post_obsolete(mdoc); n->tok = MDOC_Ft; break; default: break; } /* * Iterate over all children, recursing into each one * in turn, depth-first. */ mdoc->last = mdoc->last->child; while (mdoc->last != NULL) { mdoc_validate(mdoc); if (mdoc->last == n) mdoc->last = mdoc->last->child; else mdoc->last = mdoc->last->next; } /* Finally validate the macro itself. */ mdoc->last = n; mdoc->next = ROFF_NEXT_SIBLING; switch (n->type) { case ROFFT_TEXT: np = n->parent; if (n->sec != SEC_SYNOPSIS || (np->tok != MDOC_Cd && np->tok != MDOC_Fd)) check_text(mdoc, n->line, n->pos, n->string); if ((n->flags & NODE_NOFILL) == 0 && (np->tok != MDOC_It || np->type != ROFFT_HEAD || np->parent->parent->norm->Bl.type != LIST_diag)) check_text_em(mdoc, n->line, n->pos, n->string); if (np->tok == MDOC_It || (np->type == ROFFT_BODY && (np->tok == MDOC_Sh || np->tok == MDOC_Ss))) check_toptext(mdoc, n->line, n->pos, n->string); break; case ROFFT_COMMENT: case ROFFT_EQN: case ROFFT_TBL: break; case ROFFT_ROOT: post_root(mdoc); break; default: check_args(mdoc, mdoc->last); /* * Closing delimiters are not special at the * beginning of a block, opening delimiters * are not special at the end. */ if (n->child != NULL) n->child->flags &= ~NODE_DELIMC; if (n->last != NULL) n->last->flags &= ~NODE_DELIMO; /* Call the macro's postprocessor. */ if (n->tok < ROFF_MAX) { roff_validate(mdoc); break; } assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); p = mdoc_valids + (n->tok - MDOC_Dd); if (*p) (*p)(mdoc); if (mdoc->last == n) mdoc_state(mdoc, n); break; } } static void check_args(struct roff_man *mdoc, struct roff_node *n) { int i; if (NULL == n->args) return; assert(n->args->argc); for (i = 0; i < (int)n->args->argc; i++) check_argv(mdoc, n, &n->args->argv[i]); } static void check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v) { int i; for (i = 0; i < (int)v->sz; i++) check_text(mdoc, v->line, v->pos, v->value[i]); } static void check_text(struct roff_man *mdoc, int ln, int pos, char *p) { char *cp; if (mdoc->last->flags & NODE_NOFILL) return; for (cp = p; NULL != (p = strchr(p, '\t')); p++) mandoc_msg(MANDOCERR_FI_TAB, ln, pos + (int)(p - cp), NULL); } static void check_text_em(struct roff_man *mdoc, int ln, int pos, char *p) { const struct roff_node *np, *nn; char *cp; np = mdoc->last->prev; nn = mdoc->last->next; /* Look for em-dashes wrongly encoded as "--". */ for (cp = p; *cp != '\0'; cp++) { if (cp[0] != '-' || cp[1] != '-') continue; cp++; /* Skip input sequences of more than two '-'. */ if (cp[1] == '-') { while (cp[1] == '-') cp++; continue; } /* Skip "--" directly attached to something else. */ if ((cp - p > 1 && cp[-2] != ' ') || (cp[1] != '\0' && cp[1] != ' ')) continue; /* Require a letter right before or right afterwards. */ if ((cp - p > 2 ? isalpha((unsigned char)cp[-3]) : np != NULL && np->type == ROFFT_TEXT && *np->string != '\0' && isalpha((unsigned char)np->string[ strlen(np->string) - 1])) || (cp[1] != '\0' && cp[2] != '\0' ? isalpha((unsigned char)cp[2]) : nn != NULL && nn->type == ROFFT_TEXT && isalpha((unsigned char)*nn->string))) { mandoc_msg(MANDOCERR_DASHDASH, ln, pos + (int)(cp - p) - 1, NULL); break; } } } static void check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p) { const char *cp, *cpr; if (*p == '\0') return; if ((cp = strstr(p, "OpenBSD")) != NULL) mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Ox"); if ((cp = strstr(p, "NetBSD")) != NULL) mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Nx"); if ((cp = strstr(p, "FreeBSD")) != NULL) mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Fx"); if ((cp = strstr(p, "DragonFly")) != NULL) mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Dx"); cp = p; while ((cp = strstr(cp + 1, "()")) != NULL) { for (cpr = cp - 1; cpr >= p; cpr--) if (*cpr != '_' && !isalnum((unsigned char)*cpr)) break; if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) { cpr++; mandoc_msg(MANDOCERR_FUNC, ln, pos + (int)(cpr - p), "%.*s()", (int)(cp - cpr), cpr); } } } static void post_abort(POST_ARGS) { abort(); } static void post_delim(POST_ARGS) { const struct roff_node *nch; const char *lc; enum mdelim delim; enum roff_tok tok; tok = mdoc->last->tok; nch = mdoc->last->last; if (nch == NULL || nch->type != ROFFT_TEXT) return; lc = strchr(nch->string, '\0') - 1; if (lc < nch->string) return; delim = mdoc_isdelim(lc); if (delim == DELIM_NONE || delim == DELIM_OPEN) return; if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh || tok == MDOC_Ss || tok == MDOC_Fo)) return; mandoc_msg(MANDOCERR_DELIM, nch->line, nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok], nch == mdoc->last->child ? "" : " ...", nch->string); } static void post_delim_nb(POST_ARGS) { const struct roff_node *nch; const char *lc, *cp; int nw; enum mdelim delim; enum roff_tok tok; /* * Find candidates: at least two bytes, * the last one a closing or middle delimiter. */ tok = mdoc->last->tok; nch = mdoc->last->last; if (nch == NULL || nch->type != ROFFT_TEXT) return; lc = strchr(nch->string, '\0') - 1; if (lc <= nch->string) return; delim = mdoc_isdelim(lc); if (delim == DELIM_NONE || delim == DELIM_OPEN) return; /* * Reduce false positives by allowing various cases. */ /* Escaped delimiters. */ if (lc > nch->string + 1 && lc[-2] == '\\' && (lc[-1] == '&' || lc[-1] == 'e')) return; /* Specific byte sequences. */ switch (*lc) { case ')': for (cp = lc; cp >= nch->string; cp--) if (*cp == '(') return; break; case '.': if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.') return; if (lc[-1] == '.') return; break; case ';': if (tok == MDOC_Vt) return; break; case '?': if (lc[-1] == '?') return; break; case ']': for (cp = lc; cp >= nch->string; cp--) if (*cp == '[') return; break; case '|': if (lc == nch->string + 1 && lc[-1] == '|') return; default: break; } /* Exactly two non-alphanumeric bytes. */ if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1])) return; /* At least three alphabetic words with a sentence ending. */ if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em || tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) { nw = 0; for (cp = lc - 1; cp >= nch->string; cp--) { if (*cp == ' ') { nw++; if (cp > nch->string && cp[-1] == ',') cp--; } else if (isalpha((unsigned int)*cp)) { if (nw > 1) return; } else break; } } mandoc_msg(MANDOCERR_DELIM_NB, nch->line, nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok], nch == mdoc->last->child ? "" : " ...", nch->string); } static void post_bl_norm(POST_ARGS) { struct roff_node *n; struct mdoc_argv *argv, *wa; int i; enum mdocargt mdoclt; enum mdoc_list lt; n = mdoc->last->parent; n->norm->Bl.type = LIST__NONE; /* * First figure out which kind of list to use: bind ourselves to * the first mentioned list type and warn about any remaining * ones. If we find no list type, we default to LIST_item. */ wa = (n->args == NULL) ? NULL : n->args->argv; mdoclt = MDOC_ARG_MAX; for (i = 0; n->args && i < (int)n->args->argc; i++) { argv = n->args->argv + i; lt = LIST__NONE; switch (argv->arg) { /* Set list types. */ case MDOC_Bullet: lt = LIST_bullet; break; case MDOC_Dash: lt = LIST_dash; break; case MDOC_Enum: lt = LIST_enum; break; case MDOC_Hyphen: lt = LIST_hyphen; break; case MDOC_Item: lt = LIST_item; break; case MDOC_Tag: lt = LIST_tag; break; case MDOC_Diag: lt = LIST_diag; break; case MDOC_Hang: lt = LIST_hang; break; case MDOC_Ohang: lt = LIST_ohang; break; case MDOC_Inset: lt = LIST_inset; break; case MDOC_Column: lt = LIST_column; break; /* Set list arguments. */ case MDOC_Compact: if (n->norm->Bl.comp) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bl -compact"); n->norm->Bl.comp = 1; break; case MDOC_Width: wa = argv; if (0 == argv->sz) { mandoc_msg(MANDOCERR_ARG_EMPTY, argv->line, argv->pos, "Bl -width"); n->norm->Bl.width = "0n"; break; } if (NULL != n->norm->Bl.width) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bl -width %s", argv->value[0]); rewrite_macro2len(mdoc, argv->value); n->norm->Bl.width = argv->value[0]; break; case MDOC_Offset: if (0 == argv->sz) { mandoc_msg(MANDOCERR_ARG_EMPTY, argv->line, argv->pos, "Bl -offset"); break; } if (NULL != n->norm->Bl.offs) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bl -offset %s", argv->value[0]); rewrite_macro2len(mdoc, argv->value); n->norm->Bl.offs = argv->value[0]; break; default: continue; } if (LIST__NONE == lt) continue; mdoclt = argv->arg; /* Check: multiple list types. */ if (LIST__NONE != n->norm->Bl.type) { mandoc_msg(MANDOCERR_BL_REP, n->line, n->pos, "Bl -%s", mdoc_argnames[argv->arg]); continue; } /* The list type should come first. */ if (n->norm->Bl.width || n->norm->Bl.offs || n->norm->Bl.comp) mandoc_msg(MANDOCERR_BL_LATETYPE, n->line, n->pos, "Bl -%s", mdoc_argnames[n->args->argv[0].arg]); n->norm->Bl.type = lt; if (LIST_column == lt) { n->norm->Bl.ncols = argv->sz; n->norm->Bl.cols = (void *)argv->value; } } /* Allow lists to default to LIST_item. */ if (LIST__NONE == n->norm->Bl.type) { mandoc_msg(MANDOCERR_BL_NOTYPE, n->line, n->pos, "Bl"); n->norm->Bl.type = LIST_item; mdoclt = MDOC_Item; } /* * Validate the width field. Some list types don't need width * types and should be warned about them. Others should have it * and must also be warned. Yet others have a default and need * no warning. */ switch (n->norm->Bl.type) { case LIST_tag: if (n->norm->Bl.width == NULL) mandoc_msg(MANDOCERR_BL_NOWIDTH, n->line, n->pos, "Bl -tag"); break; case LIST_column: case LIST_diag: case LIST_ohang: case LIST_inset: case LIST_item: if (n->norm->Bl.width != NULL) mandoc_msg(MANDOCERR_BL_SKIPW, wa->line, wa->pos, "Bl -%s", mdoc_argnames[mdoclt]); n->norm->Bl.width = NULL; break; case LIST_bullet: case LIST_dash: case LIST_hyphen: if (n->norm->Bl.width == NULL) n->norm->Bl.width = "2n"; break; case LIST_enum: if (n->norm->Bl.width == NULL) n->norm->Bl.width = "3n"; break; default: break; } } static void post_bd(POST_ARGS) { struct roff_node *n; struct mdoc_argv *argv; int i; enum mdoc_disp dt; n = mdoc->last; for (i = 0; n->args && i < (int)n->args->argc; i++) { argv = n->args->argv + i; dt = DISP__NONE; switch (argv->arg) { case MDOC_Centred: dt = DISP_centered; break; case MDOC_Ragged: dt = DISP_ragged; break; case MDOC_Unfilled: dt = DISP_unfilled; break; case MDOC_Filled: dt = DISP_filled; break; case MDOC_Literal: dt = DISP_literal; break; case MDOC_File: mandoc_msg(MANDOCERR_BD_FILE, n->line, n->pos, NULL); break; case MDOC_Offset: if (0 == argv->sz) { mandoc_msg(MANDOCERR_ARG_EMPTY, argv->line, argv->pos, "Bd -offset"); break; } if (NULL != n->norm->Bd.offs) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bd -offset %s", argv->value[0]); rewrite_macro2len(mdoc, argv->value); n->norm->Bd.offs = argv->value[0]; break; case MDOC_Compact: if (n->norm->Bd.comp) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bd -compact"); n->norm->Bd.comp = 1; break; default: abort(); } if (DISP__NONE == dt) continue; if (DISP__NONE == n->norm->Bd.type) n->norm->Bd.type = dt; else mandoc_msg(MANDOCERR_BD_REP, n->line, n->pos, "Bd -%s", mdoc_argnames[argv->arg]); } if (DISP__NONE == n->norm->Bd.type) { mandoc_msg(MANDOCERR_BD_NOTYPE, n->line, n->pos, "Bd"); n->norm->Bd.type = DISP_ragged; } } /* * Stand-alone line macros. */ static void post_an_norm(POST_ARGS) { struct roff_node *n; struct mdoc_argv *argv; size_t i; n = mdoc->last; if (n->args == NULL) return; for (i = 1; i < n->args->argc; i++) { argv = n->args->argv + i; mandoc_msg(MANDOCERR_AN_REP, argv->line, argv->pos, "An -%s", mdoc_argnames[argv->arg]); } argv = n->args->argv; if (argv->arg == MDOC_Split) n->norm->An.auth = AUTH_split; else if (argv->arg == MDOC_Nosplit) n->norm->An.auth = AUTH_nosplit; else abort(); } static void post_eoln(POST_ARGS) { struct roff_node *n; post_useless(mdoc); n = mdoc->last; if (n->child != NULL) mandoc_msg(MANDOCERR_ARG_SKIP, n->line, n->pos, "%s %s", roff_name[n->tok], n->child->string); while (n->child != NULL) roff_node_delete(mdoc, n->child); roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ? "is currently in beta test." : "currently under development."); mdoc->last->flags |= NODE_EOS | NODE_NOSRC; mdoc->last = n; } static int build_list(struct roff_man *mdoc, int tok) { struct roff_node *n; int ic; n = mdoc->last->next; for (ic = 1;; ic++) { roff_elem_alloc(mdoc, n->line, n->pos, tok); mdoc->last->flags |= NODE_NOSRC; roff_node_relink(mdoc, n); n = mdoc->last = mdoc->last->parent; mdoc->next = ROFF_NEXT_SIBLING; if (n->next == NULL) return ic; if (ic > 1 || n->next->next != NULL) { roff_word_alloc(mdoc, n->line, n->pos, ","); mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC; } n = mdoc->last->next; if (n->next == NULL) { roff_word_alloc(mdoc, n->line, n->pos, "and"); mdoc->last->flags |= NODE_NOSRC; } } } static void post_ex(POST_ARGS) { struct roff_node *n; int ic; post_std(mdoc); n = mdoc->last; mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->line, n->pos, "The"); mdoc->last->flags |= NODE_NOSRC; if (mdoc->last->next != NULL) ic = build_list(mdoc, MDOC_Nm); else if (mdoc->meta.name != NULL) { roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); mdoc->last->flags |= NODE_NOSRC; mdoc->last = mdoc->last->parent; mdoc->next = ROFF_NEXT_SIBLING; ic = 1; } else { mandoc_msg(MANDOCERR_EX_NONAME, n->line, n->pos, "Ex"); ic = 0; } roff_word_alloc(mdoc, n->line, n->pos, ic > 1 ? "utilities exit\\~0" : "utility exits\\~0"); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, "on success, and\\~>0 if an error occurs."); mdoc->last->flags |= NODE_EOS | NODE_NOSRC; mdoc->last = n; } static void post_lb(POST_ARGS) { - struct roff_node *n; - const char *p; + struct roff_node *n, *nch; + const char *ccp; + char *cp; post_delim_nb(mdoc); n = mdoc->last; - assert(n->child->type == ROFFT_TEXT); + nch = n->child; + assert(nch->type == ROFFT_TEXT); mdoc->next = ROFF_NEXT_CHILD; - if ((p = mdoc_a2lib(n->child->string)) != NULL) { + if (n->sec == SEC_SYNOPSIS) { + roff_word_alloc(mdoc, n->line, n->pos, "/*"); + mdoc->last->flags = NODE_NOSRC; + while (nch != NULL) { + roff_word_alloc(mdoc, n->line, n->pos, "-l"); + mdoc->last->flags = NODE_DELIMO | NODE_NOSRC; + mdoc->last = nch; + assert(nch->type == ROFFT_TEXT); + cp = nch->string; + if (strncmp(cp, "lib", 3) == 0) + memmove(cp, cp + 3, strlen(cp) - 3 + 1); + nch = nch->next; + } + roff_word_alloc(mdoc, n->line, n->pos, "*/"); + mdoc->last->flags = NODE_NOSRC; + mdoc->last = n; + return; + } + + if ((ccp = mdoc_a2lib(n->child->string)) != NULL) { n->child->flags |= NODE_NOPRT; - roff_word_alloc(mdoc, n->line, n->pos, p); + roff_word_alloc(mdoc, n->line, n->pos, ccp); mdoc->last->flags = NODE_NOSRC; mdoc->last = n; return; } mandoc_msg(MANDOCERR_LB_BAD, n->child->line, n->child->pos, "Lb %s", n->child->string); roff_word_alloc(mdoc, n->line, n->pos, "library"); mdoc->last->flags = NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, "\\(lq"); mdoc->last->flags = NODE_DELIMO | NODE_NOSRC; mdoc->last = mdoc->last->next; roff_word_alloc(mdoc, n->line, n->pos, "\\(rq"); mdoc->last->flags = NODE_DELIMC | NODE_NOSRC; mdoc->last = n; } static void post_rv(POST_ARGS) { struct roff_node *n; int ic; post_std(mdoc); n = mdoc->last; mdoc->next = ROFF_NEXT_CHILD; if (n->child != NULL) { roff_word_alloc(mdoc, n->line, n->pos, "The"); mdoc->last->flags |= NODE_NOSRC; ic = build_list(mdoc, MDOC_Fn); roff_word_alloc(mdoc, n->line, n->pos, ic > 1 ? "functions return" : "function returns"); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, "the value\\~0 if successful;"); } else roff_word_alloc(mdoc, n->line, n->pos, "Upon successful " "completion, the value\\~0 is returned;"); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, "otherwise " "the value\\~\\-1 is returned and the global variable"); mdoc->last->flags |= NODE_NOSRC; roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, "errno"); mdoc->last->flags |= NODE_NOSRC; mdoc->last = mdoc->last->parent; mdoc->next = ROFF_NEXT_SIBLING; roff_word_alloc(mdoc, n->line, n->pos, "is set to indicate the error."); mdoc->last->flags |= NODE_EOS | NODE_NOSRC; mdoc->last = n; } static void post_std(POST_ARGS) { struct roff_node *n; post_delim(mdoc); n = mdoc->last; if (n->args && n->args->argc == 1) if (n->args->argv[0].arg == MDOC_Std) return; mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos, "%s", roff_name[n->tok]); } static void post_st(POST_ARGS) { struct roff_node *n, *nch; const char *p; n = mdoc->last; nch = n->child; assert(nch->type == ROFFT_TEXT); if ((p = mdoc_a2st(nch->string)) == NULL) { mandoc_msg(MANDOCERR_ST_BAD, nch->line, nch->pos, "St %s", nch->string); roff_node_delete(mdoc, n); return; } nch->flags |= NODE_NOPRT; mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, nch->line, nch->pos, p); mdoc->last->flags |= NODE_NOSRC; mdoc->last= n; } static void post_tg(POST_ARGS) { struct roff_node *n; /* The .Tg node. */ struct roff_node *nch; /* The first child of the .Tg node. */ struct roff_node *nn; /* The next node after the .Tg node. */ struct roff_node *np; /* The parent of the next node. */ struct roff_node *nt; /* The TEXT node containing the tag. */ size_t len; /* The number of bytes in the tag. */ /* Find the next node. */ n = mdoc->last; for (nn = n; nn != NULL; nn = nn->parent) { if (nn->type != ROFFT_HEAD && nn->type != ROFFT_BODY && nn->type != ROFFT_TAIL && nn->next != NULL) { nn = nn->next; break; } } /* Find the tag. */ nt = nch = n->child; if (nch == NULL && nn != NULL && nn->child != NULL && nn->child->type == ROFFT_TEXT) nt = nn->child; /* Validate the tag. */ if (nt == NULL || *nt->string == '\0') mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg"); if (nt == NULL) { roff_node_delete(mdoc, n); return; } len = strcspn(nt->string, " \t\\"); if (nt->string[len] != '\0') mandoc_msg(MANDOCERR_TG_SPC, nt->line, nt->pos + len, "Tg %s", nt->string); /* Keep only the first argument. */ if (nch != NULL && nch->next != NULL) { mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line, nch->next->pos, "Tg ... %s", nch->next->string); while (nch->next != NULL) roff_node_delete(mdoc, nch->next); } /* Drop the macro if the first argument is invalid. */ if (len == 0 || nt->string[len] != '\0') { roff_node_delete(mdoc, n); return; } /* By default, tag the .Tg node itself. */ if (nn == NULL || nn->flags & NODE_ID) nn = n; /* Explicit tagging of specific macros. */ switch (nn->tok) { case MDOC_Sh: case MDOC_Ss: case MDOC_Fo: nn = nn->head->child == NULL ? n : nn->head; break; case MDOC_It: np = nn->parent; while (np->tok != MDOC_Bl) np = np->parent; switch (np->norm->Bl.type) { case LIST_column: break; case LIST_diag: case LIST_hang: case LIST_inset: case LIST_ohang: case LIST_tag: nn = nn->head; break; case LIST_bullet: case LIST_dash: case LIST_enum: case LIST_hyphen: case LIST_item: nn = nn->body->child == NULL ? n : nn->body; break; default: abort(); } break; case MDOC_Bd: case MDOC_Bl: case MDOC_D1: case MDOC_Dl: nn = nn->body->child == NULL ? n : nn->body; break; case MDOC_Pp: break; case MDOC_Cm: case MDOC_Dv: case MDOC_Em: case MDOC_Er: case MDOC_Ev: case MDOC_Fl: case MDOC_Fn: case MDOC_Ic: case MDOC_Li: case MDOC_Ms: case MDOC_No: case MDOC_Sy: if (nn->child == NULL) nn = n; break; default: nn = n; break; } tag_put(nt->string, TAG_MANUAL, nn); if (nn != n) n->flags |= NODE_NOPRT; } static void post_obsolete(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK) mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos, "%s", roff_name[n->tok]); } static void post_useless(POST_ARGS) { struct roff_node *n; n = mdoc->last; mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos, "%s", roff_name[n->tok]); } /* * Block macros. */ static void post_bf(POST_ARGS) { struct roff_node *np, *nch; /* * Unlike other data pointers, these are "housed" by the HEAD * element, which contains the goods. */ np = mdoc->last; if (np->type != ROFFT_HEAD) return; assert(np->parent->type == ROFFT_BLOCK); assert(np->parent->tok == MDOC_Bf); /* Check the number of arguments. */ nch = np->child; if (np->parent->args == NULL) { if (nch == NULL) { mandoc_msg(MANDOCERR_BF_NOFONT, np->line, np->pos, "Bf"); return; } nch = nch->next; } if (nch != NULL) mandoc_msg(MANDOCERR_ARG_EXCESS, nch->line, nch->pos, "Bf ... %s", nch->string); /* Extract argument into data. */ if (np->parent->args != NULL) { switch (np->parent->args->argv[0].arg) { case MDOC_Emphasis: np->norm->Bf.font = FONT_Em; break; case MDOC_Literal: np->norm->Bf.font = FONT_Li; break; case MDOC_Symbolic: np->norm->Bf.font = FONT_Sy; break; default: abort(); } return; } /* Extract parameter into data. */ if ( ! strcmp(np->child->string, "Em")) np->norm->Bf.font = FONT_Em; else if ( ! strcmp(np->child->string, "Li")) np->norm->Bf.font = FONT_Li; else if ( ! strcmp(np->child->string, "Sy")) np->norm->Bf.font = FONT_Sy; else mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line, np->child->pos, "Bf %s", np->child->string); } static void post_fname(POST_ARGS) { struct roff_node *n, *nch; const char *cp; size_t pos; n = mdoc->last; nch = n->child; cp = nch->string; if (*cp == '(') { if (cp[strlen(cp + 1)] == ')') return; pos = 0; } else { pos = strcspn(cp, "()"); if (cp[pos] == '\0') { if (n->sec == SEC_DESCRIPTION || n->sec == SEC_CUSTOM) tag_put(NULL, fn_prio++, n); return; } } mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp); } static void post_fn(POST_ARGS) { post_fname(mdoc); post_fa(mdoc); } static void post_fo(POST_ARGS) { const struct roff_node *n; n = mdoc->last; if (n->type != ROFFT_HEAD) return; if (n->child == NULL) { mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo"); return; } if (n->child != n->last) { mandoc_msg(MANDOCERR_ARG_EXCESS, n->child->next->line, n->child->next->pos, "Fo ... %s", n->child->next->string); while (n->child != n->last) roff_node_delete(mdoc, n->last); } else post_delim(mdoc); post_fname(mdoc); } static void post_fa(POST_ARGS) { const struct roff_node *n; const char *cp; for (n = mdoc->last->child; n != NULL; n = n->next) { for (cp = n->string; *cp != '\0'; cp++) { /* Ignore callbacks and alterations. */ if (*cp == '(' || *cp == '{') break; if (*cp != ',') continue; mandoc_msg(MANDOCERR_FA_COMMA, n->line, n->pos + (int)(cp - n->string), "%s", n->string); break; } } post_delim_nb(mdoc); } static void post_nm(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->sec == SEC_NAME && n->child != NULL && n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL) mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1); if (n->last != NULL && n->last->tok == MDOC_Pp) roff_node_relink(mdoc, n->last); if (mdoc->meta.name == NULL) deroff(&mdoc->meta.name, n); if (mdoc->meta.name == NULL || (mdoc->lastsec == SEC_NAME && n->child == NULL)) mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm"); switch (n->type) { case ROFFT_ELEM: post_delim_nb(mdoc); break; case ROFFT_HEAD: post_delim(mdoc); break; default: return; } if ((n->child != NULL && n->child->type == ROFFT_TEXT) || mdoc->meta.name == NULL) return; mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; } static void post_nd(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->type != ROFFT_BODY) return; if (n->sec != SEC_NAME) mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd"); if (n->child == NULL) mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd"); else post_delim(mdoc); post_hyph(mdoc); } static void post_display(POST_ARGS) { struct roff_node *n, *np; n = mdoc->last; switch (n->type) { case ROFFT_BODY: if (n->end != ENDBODY_NOT) { if (n->tok == MDOC_Bd && n->body->parent->args == NULL) roff_node_delete(mdoc, n); } else if (n->child == NULL) mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "%s", roff_name[n->tok]); else if (n->tok == MDOC_D1) post_hyph(mdoc); break; case ROFFT_BLOCK: if (n->tok == MDOC_Bd) { if (n->args == NULL) { mandoc_msg(MANDOCERR_BD_NOARG, n->line, n->pos, "Bd"); mdoc->next = ROFF_NEXT_SIBLING; while (n->body->child != NULL) roff_node_relink(mdoc, n->body->child); roff_node_delete(mdoc, n); break; } post_bd(mdoc); post_prevpar(mdoc); } for (np = n->parent; np != NULL; np = np->parent) { if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) { mandoc_msg(MANDOCERR_BD_NEST, n->line, n->pos, "%s in Bd", roff_name[n->tok]); break; } } break; default: break; } } static void post_defaults(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->child != NULL) { post_delim_nb(mdoc); return; } mdoc->next = ROFF_NEXT_CHILD; switch (n->tok) { case MDOC_Ar: roff_word_alloc(mdoc, n->line, n->pos, "file"); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, "..."); break; case MDOC_Pa: case MDOC_Mt: roff_word_alloc(mdoc, n->line, n->pos, "~"); break; default: abort(); } mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; } static void post_at(POST_ARGS) { struct roff_node *n, *nch; const char *att; n = mdoc->last; nch = n->child; /* * If we have a child, look it up in the standard keys. If a * key exist, use that instead of the child; if it doesn't, * prefix "AT&T UNIX " to the existing data. */ att = NULL; if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL)) mandoc_msg(MANDOCERR_AT_BAD, nch->line, nch->pos, "At %s", nch->string); mdoc->next = ROFF_NEXT_CHILD; if (att != NULL) { roff_word_alloc(mdoc, nch->line, nch->pos, att); nch->flags |= NODE_NOPRT; } else roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX"); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; } static void post_an(POST_ARGS) { struct roff_node *np, *nch; post_an_norm(mdoc); np = mdoc->last; nch = np->child; if (np->norm->An.auth == AUTH__NONE) { if (nch == NULL) mandoc_msg(MANDOCERR_MACRO_EMPTY, np->line, np->pos, "An"); else post_delim_nb(mdoc); } else if (nch != NULL) mandoc_msg(MANDOCERR_ARG_EXCESS, nch->line, nch->pos, "An ... %s", nch->string); } static void post_em(POST_ARGS) { post_tag(mdoc); tag_put(NULL, TAG_FALLBACK, mdoc->last); } static void post_en(POST_ARGS) { post_obsolete(mdoc); if (mdoc->last->type == ROFFT_BLOCK) mdoc->last->norm->Es = mdoc->last_es; } static void post_er(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->sec == SEC_ERRORS && (n->parent->tok == MDOC_It || (n->parent->tok == MDOC_Bq && n->parent->parent->parent->tok == MDOC_It))) tag_put(NULL, TAG_STRONG, n); post_delim_nb(mdoc); } static void post_tag(POST_ARGS) { struct roff_node *n; n = mdoc->last; if ((n->prev == NULL || (n->prev->type == ROFFT_TEXT && strcmp(n->prev->string, "|") == 0)) && (n->parent->tok == MDOC_It || (n->parent->tok == MDOC_Xo && n->parent->parent->prev == NULL && n->parent->parent->parent->tok == MDOC_It))) tag_put(NULL, TAG_STRONG, n); post_delim_nb(mdoc); } static void post_es(POST_ARGS) { post_obsolete(mdoc); mdoc->last_es = mdoc->last; } static void post_fl(POST_ARGS) { struct roff_node *n; char *cp; /* * Transform ".Fl Fl long" to ".Fl \-long", * resulting for example in better HTML output. */ n = mdoc->last; if (n->prev != NULL && n->prev->tok == MDOC_Fl && n->prev->child == NULL && n->child != NULL && (n->flags & NODE_LINE) == 0) { mandoc_asprintf(&cp, "\\-%s", n->child->string); free(n->child->string); n->child->string = cp; roff_node_delete(mdoc, n->prev); } post_tag(mdoc); } static void post_xx(POST_ARGS) { struct roff_node *n; const char *os; char *v; post_delim_nb(mdoc); n = mdoc->last; switch (n->tok) { case MDOC_Bsx: os = "BSD/OS"; break; case MDOC_Dx: os = "DragonFly"; break; case MDOC_Fx: os = "FreeBSD"; break; case MDOC_Nx: os = "NetBSD"; if (n->child == NULL) break; v = n->child->string; if ((v[0] != '0' && v[0] != '1') || v[1] != '.' || v[2] < '0' || v[2] > '9' || v[3] < 'a' || v[3] > 'z' || v[4] != '\0') break; n->child->flags |= NODE_NOPRT; mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->child->line, n->child->pos, v); v = mdoc->last->string; v[3] = toupper((unsigned char)v[3]); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; break; case MDOC_Ox: os = "OpenBSD"; break; case MDOC_Ux: os = "UNIX"; break; default: abort(); } mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->line, n->pos, os); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; } static void post_it(POST_ARGS) { struct roff_node *nbl, *nit, *nch; int i, cols; enum mdoc_list lt; post_prevpar(mdoc); nit = mdoc->last; if (nit->type != ROFFT_BLOCK) return; nbl = nit->parent->parent; lt = nbl->norm->Bl.type; switch (lt) { case LIST_tag: case LIST_hang: case LIST_ohang: case LIST_inset: case LIST_diag: if (nit->head->child == NULL) mandoc_msg(MANDOCERR_IT_NOHEAD, nit->line, nit->pos, "Bl -%s It", mdoc_argnames[nbl->args->argv[0].arg]); break; case LIST_bullet: case LIST_dash: case LIST_enum: case LIST_hyphen: if (nit->body == NULL || nit->body->child == NULL) mandoc_msg(MANDOCERR_IT_NOBODY, nit->line, nit->pos, "Bl -%s It", mdoc_argnames[nbl->args->argv[0].arg]); /* FALLTHROUGH */ case LIST_item: if ((nch = nit->head->child) != NULL) mandoc_msg(MANDOCERR_ARG_SKIP, nit->line, nit->pos, "It %s", nch->type == ROFFT_TEXT ? nch->string : roff_name[nch->tok]); break; case LIST_column: cols = (int)nbl->norm->Bl.ncols; assert(nit->head->child == NULL); if (nit->head->next->child == NULL && nit->head->next->next == NULL) { mandoc_msg(MANDOCERR_MACRO_EMPTY, nit->line, nit->pos, "It"); roff_node_delete(mdoc, nit); break; } i = 0; for (nch = nit->child; nch != NULL; nch = nch->next) { if (nch->type != ROFFT_BODY) continue; if (i++ && nch->flags & NODE_LINE) mandoc_msg(MANDOCERR_TA_LINE, nch->line, nch->pos, "Ta"); } if (i < cols || i > cols + 1) mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos, "%d columns, %d cells", cols, i); else if (nit->head->next->child != NULL && nit->head->next->child->flags & NODE_LINE) mandoc_msg(MANDOCERR_IT_NOARG, nit->line, nit->pos, "Bl -column It"); break; default: abort(); } } static void post_bl_block(POST_ARGS) { struct roff_node *n, *ni, *nc; post_prevpar(mdoc); n = mdoc->last; for (ni = n->body->child; ni != NULL; ni = ni->next) { if (ni->body == NULL) continue; nc = ni->body->last; while (nc != NULL) { switch (nc->tok) { case MDOC_Pp: case ROFF_br: break; default: nc = NULL; continue; } if (ni->next == NULL) { mandoc_msg(MANDOCERR_PAR_MOVE, nc->line, nc->pos, "%s", roff_name[nc->tok]); roff_node_relink(mdoc, nc); } else if (n->norm->Bl.comp == 0 && n->norm->Bl.type != LIST_column) { mandoc_msg(MANDOCERR_PAR_SKIP, nc->line, nc->pos, "%s before It", roff_name[nc->tok]); roff_node_delete(mdoc, nc); } else break; nc = ni->body->last; } } } /* * If "in" begins with a dot, a word, and whitespace, return a dynamically * allocated copy of "in" that skips all of those. Otherwise, return NULL. * * This is a partial workaround for the TODO list item beginning with: * - When the -width string contains macros, the macros must be rendered */ static char * skip_leading_dot_word(const char *in) { const char *iter = in; const char *space; if (*iter != '.') return NULL; iter++; while (*iter != '\0' && !isspace(*iter)) iter++; /* * If the dot was followed by space or NUL, * do not skip anything. */ if (iter == in + 1) return NULL; space = iter; while (isspace(*iter)) iter++; /* * If the word was not followed by space, * do not skip anything. */ if (iter == space) return NULL; return strdup(iter); } /* * If the argument of -offset or -width is a macro, * replace it with the associated default width. */ static void rewrite_macro2len(struct roff_man *mdoc, char **arg) { size_t width; enum roff_tok tok; char *newarg; newarg = NULL; if (*arg == NULL) return; else if ( ! strcmp(*arg, "Ds")) width = 6; else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) != TOKEN_NONE) width = macro2len(tok); else if ((newarg = skip_leading_dot_word(*arg)) == NULL) return; free(*arg); if (newarg != NULL) *arg = newarg; else mandoc_asprintf(arg, "%zun", width); } static void post_bl_head(POST_ARGS) { struct roff_node *nbl, *nh, *nch, *nnext; struct mdoc_argv *argv; int i, j; post_bl_norm(mdoc); nh = mdoc->last; if (nh->norm->Bl.type != LIST_column) { if ((nch = nh->child) == NULL) return; mandoc_msg(MANDOCERR_ARG_EXCESS, nch->line, nch->pos, "Bl ... %s", nch->string); while (nch != NULL) { roff_node_delete(mdoc, nch); nch = nh->child; } return; } /* * Append old-style lists, where the column width specifiers * trail as macro parameters, to the new-style ("normal-form") * lists where they're argument values following -column. */ if (nh->child == NULL) return; nbl = nh->parent; for (j = 0; j < (int)nbl->args->argc; j++) if (nbl->args->argv[j].arg == MDOC_Column) break; assert(j < (int)nbl->args->argc); /* * Accommodate for new-style groff column syntax. Shuffle the * child nodes, all of which must be TEXT, as arguments for the * column field. Then, delete the head children. */ argv = nbl->args->argv + j; i = argv->sz; for (nch = nh->child; nch != NULL; nch = nch->next) argv->sz++; argv->value = mandoc_reallocarray(argv->value, argv->sz, sizeof(char *)); nh->norm->Bl.ncols = argv->sz; nh->norm->Bl.cols = (void *)argv->value; for (nch = nh->child; nch != NULL; nch = nnext) { argv->value[i++] = nch->string; nch->string = NULL; nnext = nch->next; roff_node_delete(NULL, nch); } nh->child = NULL; } static void post_bl(POST_ARGS) { struct roff_node *nbody; /* of the Bl */ struct roff_node *nchild, *nnext; /* of the Bl body */ const char *prev_Er; int order; nbody = mdoc->last; switch (nbody->type) { case ROFFT_BLOCK: post_bl_block(mdoc); return; case ROFFT_HEAD: post_bl_head(mdoc); return; case ROFFT_BODY: break; default: return; } if (nbody->end != ENDBODY_NOT) return; /* * Up to the first item, move nodes before the list, * but leave transparent nodes where they are * if they precede an item. * The next non-transparent node is kept in nchild. * It only needs to be updated after a non-transparent * node was moved out, and at the very beginning * when no node at all was moved yet. */ nchild = mdoc->last; for (;;) { if (nchild == mdoc->last) nchild = roff_node_child(nbody); if (nchild == NULL) { mdoc->last = nbody; mandoc_msg(MANDOCERR_BLK_EMPTY, nbody->line, nbody->pos, "Bl"); return; } if (nchild->tok == MDOC_It) { mdoc->last = nbody; break; } mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line, nbody->child->pos, "%s", roff_name[nbody->child->tok]); if (nbody->parent->prev == NULL) { mdoc->last = nbody->parent->parent; mdoc->next = ROFF_NEXT_CHILD; } else { mdoc->last = nbody->parent->prev; mdoc->next = ROFF_NEXT_SIBLING; } roff_node_relink(mdoc, nbody->child); } /* * We have reached the first item, * so moving nodes out is no longer possible. * But in .Bl -column, the first rows may be implicit, * that is, they may not start with .It macros. * Such rows may be followed by nodes generated on the * roff level, for example .TS. * Wrap such roff nodes into an implicit row. */ while (nchild != NULL) { if (nchild->tok == MDOC_It) { nchild = roff_node_next(nchild); continue; } nnext = nchild->next; mdoc->last = nchild->prev; mdoc->next = ROFF_NEXT_SIBLING; roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It); roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It); mdoc->next = ROFF_NEXT_SIBLING; roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It); while (nchild->tok != MDOC_It) { roff_node_relink(mdoc, nchild); if (nnext == NULL) break; nchild = nnext; nnext = nchild->next; mdoc->next = ROFF_NEXT_SIBLING; } mdoc->last = nbody; } if (mdoc->meta.os_e != MANDOC_OS_NETBSD) return; prev_Er = NULL; for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) { if (nchild->tok != MDOC_It) continue; if ((nnext = nchild->head->child) == NULL) continue; if (nnext->type == ROFFT_BLOCK) nnext = nnext->body->child; if (nnext == NULL || nnext->tok != MDOC_Er) continue; nnext = nnext->child; if (prev_Er != NULL) { order = strcmp(prev_Er, nnext->string); if (order > 0) mandoc_msg(MANDOCERR_ER_ORDER, nnext->line, nnext->pos, "Er %s %s (NetBSD)", prev_Er, nnext->string); else if (order == 0) mandoc_msg(MANDOCERR_ER_REP, nnext->line, nnext->pos, "Er %s (NetBSD)", prev_Er); } prev_Er = nnext->string; } } static void post_bk(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->type == ROFFT_BLOCK && n->body->child == NULL) { mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk"); roff_node_delete(mdoc, n); } } static void post_sm(POST_ARGS) { struct roff_node *nch; nch = mdoc->last->child; if (nch == NULL) { mdoc->flags ^= MDOC_SMOFF; return; } assert(nch->type == ROFFT_TEXT); if ( ! strcmp(nch->string, "on")) { mdoc->flags &= ~MDOC_SMOFF; return; } if ( ! strcmp(nch->string, "off")) { mdoc->flags |= MDOC_SMOFF; return; } mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos, "%s %s", roff_name[mdoc->last->tok], nch->string); roff_node_relink(mdoc, nch); return; } static void post_root(POST_ARGS) { struct roff_node *n; /* Add missing prologue data. */ if (mdoc->meta.date == NULL) mdoc->meta.date = mandoc_normdate(NULL, NULL); if (mdoc->meta.title == NULL) { mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF"); mdoc->meta.title = mandoc_strdup("UNTITLED"); } if (mdoc->meta.vol == NULL) mdoc->meta.vol = mandoc_strdup("LOCAL"); if (mdoc->meta.os == NULL) { mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL); mdoc->meta.os = mandoc_strdup(""); } else if (mdoc->meta.os_e && (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0) mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0, mdoc->meta.os_e == MANDOC_OS_OPENBSD ? "(OpenBSD)" : "(NetBSD)"); if (mdoc->meta.arch != NULL && arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) { n = mdoc->meta.first->child; while (n->tok != MDOC_Dt || n->child == NULL || n->child->next == NULL || n->child->next->next == NULL) n = n->next; n = n->child->next->next; mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos, "Dt ... %s %s", mdoc->meta.arch, mdoc->meta.os_e == MANDOC_OS_OPENBSD ? "(OpenBSD)" : "(NetBSD)"); } /* Check that we begin with a proper `Sh'. */ n = mdoc->meta.first->child; while (n != NULL && (n->type == ROFFT_COMMENT || (n->tok >= MDOC_Dd && mdoc_macro(n->tok)->flags & MDOC_PROLOGUE))) n = n->next; if (n == NULL) mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL); else if (n->tok != MDOC_Sh) mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos, "%s", roff_name[n->tok]); } static void post_rs(POST_ARGS) { struct roff_node *np, *nch, *next, *prev; int i, j; np = mdoc->last; if (np->type != ROFFT_BODY) return; if (np->child == NULL) { mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs"); return; } /* * The full `Rs' block needs special handling to order the * sub-elements according to `rsord'. Pick through each element * and correctly order it. This is an insertion sort. */ next = NULL; for (nch = np->child->next; nch != NULL; nch = next) { /* Determine order number of this child. */ for (i = 0; i < RSORD_MAX; i++) if (rsord[i] == nch->tok) break; if (i == RSORD_MAX) { mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos, "%s", roff_name[nch->tok]); i = -1; } else if (nch->tok == MDOC__J || nch->tok == MDOC__B) np->norm->Rs.quote_T++; /* * Remove this child from the chain. This somewhat * repeats roff_node_unlink(), but since we're * just re-ordering, there's no need for the * full unlink process. */ if ((next = nch->next) != NULL) next->prev = nch->prev; if ((prev = nch->prev) != NULL) prev->next = nch->next; nch->prev = nch->next = NULL; /* * Scan back until we reach a node that's * to be ordered before this child. */ for ( ; prev ; prev = prev->prev) { /* Determine order of `prev'. */ for (j = 0; j < RSORD_MAX; j++) if (rsord[j] == prev->tok) break; if (j == RSORD_MAX) j = -1; if (j <= i) break; } /* * Set this child back into its correct place * in front of the `prev' node. */ nch->prev = prev; if (prev == NULL) { np->child->prev = nch; nch->next = np->child; np->child = nch; } else { if (prev->next) prev->next->prev = nch; nch->next = prev->next; prev->next = nch; } } } /* * For some arguments of some macros, * convert all breakable hyphens into ASCII_HYPH. */ static void post_hyph(POST_ARGS) { struct roff_node *n, *nch; char *cp; n = mdoc->last; for (nch = n->child; nch != NULL; nch = nch->next) { if (nch->type != ROFFT_TEXT) continue; cp = nch->string; if (*cp == '\0') continue; while (*(++cp) != '\0') if (*cp == '-' && isalpha((unsigned char)cp[-1]) && isalpha((unsigned char)cp[1])) { if (n->tag == NULL && n->flags & NODE_ID) n->tag = mandoc_strdup(nch->string); *cp = ASCII_HYPH; } } } static void post_ns(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->flags & NODE_LINE || (n->next != NULL && n->next->flags & NODE_DELIMC)) mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL); } static void post_sx(POST_ARGS) { post_delim(mdoc); post_hyph(mdoc); } static void post_sh(POST_ARGS) { post_section(mdoc); switch (mdoc->last->type) { case ROFFT_HEAD: post_sh_head(mdoc); break; case ROFFT_BODY: switch (mdoc->lastsec) { case SEC_NAME: post_sh_name(mdoc); break; case SEC_SEE_ALSO: post_sh_see_also(mdoc); break; case SEC_AUTHORS: post_sh_authors(mdoc); break; default: break; } break; default: break; } } static void post_sh_name(POST_ARGS) { struct roff_node *n; int hasnm, hasnd; hasnm = hasnd = 0; for (n = mdoc->last->child; n != NULL; n = n->next) { switch (n->tok) { case MDOC_Nm: if (hasnm && n->child != NULL) mandoc_msg(MANDOCERR_NAMESEC_PUNCT, n->line, n->pos, "Nm %s", n->child->string); hasnm = 1; continue; case MDOC_Nd: hasnd = 1; if (n->next != NULL) mandoc_msg(MANDOCERR_NAMESEC_ND, n->line, n->pos, NULL); break; case TOKEN_NONE: if (n->type == ROFFT_TEXT && n->string[0] == ',' && n->string[1] == '\0' && n->next != NULL && n->next->tok == MDOC_Nm) { n = n->next; continue; } /* FALLTHROUGH */ default: mandoc_msg(MANDOCERR_NAMESEC_BAD, n->line, n->pos, "%s", roff_name[n->tok]); continue; } break; } if ( ! hasnm) mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->last->line, mdoc->last->pos, NULL); if ( ! hasnd) mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->last->line, mdoc->last->pos, NULL); } static void post_sh_see_also(POST_ARGS) { const struct roff_node *n; const char *name, *sec; const char *lastname, *lastsec, *lastpunct; int cmp; n = mdoc->last->child; lastname = lastsec = lastpunct = NULL; while (n != NULL) { if (n->tok != MDOC_Xr || n->child == NULL || n->child->next == NULL) break; /* Process one .Xr node. */ name = n->child->string; sec = n->child->next->string; if (lastsec != NULL) { if (lastpunct[0] != ',' || lastpunct[1] != '\0') mandoc_msg(MANDOCERR_XR_PUNCT, n->line, n->pos, "%s before %s(%s)", lastpunct, name, sec); cmp = strcmp(lastsec, sec); if (cmp > 0) mandoc_msg(MANDOCERR_XR_ORDER, n->line, n->pos, "%s(%s) after %s(%s)", name, sec, lastname, lastsec); else if (cmp == 0 && strcasecmp(lastname, name) > 0) mandoc_msg(MANDOCERR_XR_ORDER, n->line, n->pos, "%s after %s", name, lastname); } lastname = name; lastsec = sec; /* Process the following node. */ n = n->next; if (n == NULL) break; if (n->tok == MDOC_Xr) { lastpunct = "none"; continue; } if (n->type != ROFFT_TEXT) break; for (name = n->string; *name != '\0'; name++) if (isalpha((const unsigned char)*name)) return; lastpunct = n->string; if (n->next == NULL || n->next->tok == MDOC_Rs) mandoc_msg(MANDOCERR_XR_PUNCT, n->line, n->pos, "%s after %s(%s)", lastpunct, lastname, lastsec); n = n->next; } } static int child_an(const struct roff_node *n) { for (n = n->child; n != NULL; n = n->next) if ((n->tok == MDOC_An && n->child != NULL) || child_an(n)) return 1; return 0; } static void post_sh_authors(POST_ARGS) { if ( ! child_an(mdoc->last)) mandoc_msg(MANDOCERR_AN_MISSING, mdoc->last->line, mdoc->last->pos, NULL); } /* * Return an upper bound for the string distance (allowing * transpositions). Not a full Levenshtein implementation * because Levenshtein is quadratic in the string length * and this function is called for every standard name, * so the check for each custom name would be cubic. * The following crude heuristics is linear, resulting * in quadratic behaviour for checking one custom name, * which does not cause measurable slowdown. */ static int similar(const char *s1, const char *s2) { const int maxdist = 3; int dist = 0; while (s1[0] != '\0' && s2[0] != '\0') { if (s1[0] == s2[0]) { s1++; s2++; continue; } if (++dist > maxdist) return INT_MAX; if (s1[1] == s2[1]) { /* replacement */ s1++; s2++; } else if (s1[0] == s2[1] && s1[1] == s2[0]) { s1 += 2; /* transposition */ s2 += 2; } else if (s1[0] == s2[1]) /* insertion */ s2++; else if (s1[1] == s2[0]) /* deletion */ s1++; else return INT_MAX; } dist += strlen(s1) + strlen(s2); return dist > maxdist ? INT_MAX : dist; } static void post_sh_head(POST_ARGS) { struct roff_node *nch; const char *goodsec; const char *const *testsec; int dist, mindist; enum roff_sec sec; /* * Process a new section. Sections are either "named" or * "custom". Custom sections are user-defined, while named ones * follow a conventional order and may only appear in certain * manual sections. */ sec = mdoc->last->sec; /* The NAME should be first. */ if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE) mandoc_msg(MANDOCERR_NAMESEC_FIRST, mdoc->last->line, mdoc->last->pos, "Sh %s", sec != SEC_CUSTOM ? secnames[sec] : (nch = mdoc->last->child) == NULL ? "" : nch->type == ROFFT_TEXT ? nch->string : roff_name[nch->tok]); /* The SYNOPSIS gets special attention in other areas. */ if (sec == SEC_SYNOPSIS) { roff_setreg(mdoc->roff, "nS", 1, '='); mdoc->flags |= MDOC_SYNOPSIS; } else { roff_setreg(mdoc->roff, "nS", 0, '='); mdoc->flags &= ~MDOC_SYNOPSIS; } if (sec == SEC_DESCRIPTION) fn_prio = TAG_STRONG; /* Mark our last section. */ mdoc->lastsec = sec; /* We don't care about custom sections after this. */ if (sec == SEC_CUSTOM) { if ((nch = mdoc->last->child) == NULL || nch->type != ROFFT_TEXT || nch->next != NULL) return; goodsec = NULL; mindist = INT_MAX; for (testsec = secnames + 1; *testsec != NULL; testsec++) { dist = similar(nch->string, *testsec); if (dist < mindist) { goodsec = *testsec; mindist = dist; } } if (goodsec != NULL) mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos, "Sh %s instead of %s", nch->string, goodsec); return; } /* * Check whether our non-custom section is being repeated or is * out of order. */ if (sec == mdoc->lastnamed) mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line, mdoc->last->pos, "Sh %s", secnames[sec]); if (sec < mdoc->lastnamed) mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line, mdoc->last->pos, "Sh %s", secnames[sec]); /* Mark the last named section. */ mdoc->lastnamed = sec; /* Check particular section/manual conventions. */ if (mdoc->meta.msec == NULL) return; goodsec = NULL; switch (sec) { case SEC_ERRORS: if (*mdoc->meta.msec == '4') break; goodsec = "2, 3, 4, 9"; /* FALLTHROUGH */ case SEC_RETURN_VALUES: case SEC_LIBRARY: if (*mdoc->meta.msec == '2') break; if (*mdoc->meta.msec == '3') break; if (NULL == goodsec) goodsec = "2, 3, 9"; /* FALLTHROUGH */ case SEC_CONTEXT: if (*mdoc->meta.msec == '9') break; if (NULL == goodsec) goodsec = "9"; mandoc_msg(MANDOCERR_SEC_MSEC, mdoc->last->line, mdoc->last->pos, "Sh %s for %s only", secnames[sec], goodsec); break; default: break; } } static void post_xr(POST_ARGS) { struct roff_node *n, *nch; n = mdoc->last; nch = n->child; if (nch->next == NULL) { mandoc_msg(MANDOCERR_XR_NOSEC, n->line, n->pos, "Xr %s", nch->string); } else { assert(nch->next == n->last); if(mandoc_xr_add(nch->next->string, nch->string, nch->line, nch->pos)) mandoc_msg(MANDOCERR_XR_SELF, nch->line, nch->pos, "Xr %s %s", nch->string, nch->next->string); } post_delim_nb(mdoc); } static void post_section(POST_ARGS) { struct roff_node *n, *nch; char *cp, *tag; n = mdoc->last; switch (n->type) { case ROFFT_BLOCK: post_prevpar(mdoc); return; case ROFFT_HEAD: tag = NULL; deroff(&tag, n); if (tag != NULL) { for (cp = tag; *cp != '\0'; cp++) if (*cp == ' ') *cp = '_'; if ((nch = n->child) != NULL && nch->type == ROFFT_TEXT && strcmp(nch->string, tag) == 0) tag_put(NULL, TAG_STRONG, n); else tag_put(tag, TAG_FALLBACK, n); free(tag); } post_delim(mdoc); post_hyph(mdoc); return; case ROFFT_BODY: break; default: return; } if ((nch = n->child) != NULL && (nch->tok == MDOC_Pp || nch->tok == ROFF_br || nch->tok == ROFF_sp)) { mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos, "%s after %s", roff_name[nch->tok], roff_name[n->tok]); roff_node_delete(mdoc, nch); } if ((nch = n->last) != NULL && (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) { mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos, "%s at the end of %s", roff_name[nch->tok], roff_name[n->tok]); roff_node_delete(mdoc, nch); } } static void post_prevpar(POST_ARGS) { struct roff_node *n, *np; n = mdoc->last; if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) return; if ((np = roff_node_prev(n)) == NULL) return; /* * Don't allow `Pp' prior to a paragraph-type * block: `Pp' or non-compact `Bd' or `Bl'. */ if (np->tok != MDOC_Pp && np->tok != ROFF_br) return; if (n->tok == MDOC_Bl && n->norm->Bl.comp) return; if (n->tok == MDOC_Bd && n->norm->Bd.comp) return; if (n->tok == MDOC_It && n->parent->norm->Bl.comp) return; mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, "%s before %s", roff_name[np->tok], roff_name[n->tok]); roff_node_delete(mdoc, np); } static void post_par(POST_ARGS) { struct roff_node *np; fn_prio = TAG_STRONG; post_prevpar(mdoc); np = mdoc->last; if (np->child != NULL) mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos, "%s %s", roff_name[np->tok], np->child->string); } static void post_dd(POST_ARGS) { struct roff_node *n; n = mdoc->last; n->flags |= NODE_NOPRT; if (mdoc->meta.date != NULL) { mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd"); free(mdoc->meta.date); } else if (mdoc->flags & MDOC_PBODY) mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd"); else if (mdoc->meta.title != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, n->line, n->pos, "Dd after Dt"); else if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, n->line, n->pos, "Dd after Os"); if (mdoc->quick && n != NULL) mdoc->meta.date = mandoc_strdup(""); else mdoc->meta.date = mandoc_normdate(n->child, n); } static void post_dt(POST_ARGS) { struct roff_node *nn, *n; const char *cp; char *p; n = mdoc->last; n->flags |= NODE_NOPRT; if (mdoc->flags & MDOC_PBODY) { mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt"); return; } if (mdoc->meta.title != NULL) mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt"); else if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, n->line, n->pos, "Dt after Os"); free(mdoc->meta.title); free(mdoc->meta.msec); free(mdoc->meta.vol); free(mdoc->meta.arch); mdoc->meta.title = NULL; mdoc->meta.msec = NULL; mdoc->meta.vol = NULL; mdoc->meta.arch = NULL; /* Mandatory first argument: title. */ nn = n->child; if (nn == NULL || *nn->string == '\0') { mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt"); mdoc->meta.title = mandoc_strdup("UNTITLED"); } else { mdoc->meta.title = mandoc_strdup(nn->string); /* Check that all characters are uppercase. */ for (p = nn->string; *p != '\0'; p++) if (islower((unsigned char)*p)) { mandoc_msg(MANDOCERR_TITLE_CASE, nn->line, nn->pos + (int)(p - nn->string), "Dt %s", nn->string); break; } } /* Mandatory second argument: section. */ if (nn != NULL) nn = nn->next; if (nn == NULL) { mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos, "Dt %s", mdoc->meta.title); mdoc->meta.vol = mandoc_strdup("LOCAL"); return; /* msec and arch remain NULL. */ } mdoc->meta.msec = mandoc_strdup(nn->string); /* Infer volume title from section number. */ cp = mandoc_a2msec(nn->string); if (cp == NULL) { mandoc_msg(MANDOCERR_MSEC_BAD, nn->line, nn->pos, "Dt ... %s", nn->string); mdoc->meta.vol = mandoc_strdup(nn->string); } else { mdoc->meta.vol = mandoc_strdup(cp); if (mdoc->filesec != '\0' && mdoc->filesec != *nn->string && *nn->string >= '1' && *nn->string <= '9') mandoc_msg(MANDOCERR_MSEC_FILE, nn->line, nn->pos, "*.%c vs Dt ... %c", mdoc->filesec, *nn->string); } /* Optional third argument: architecture. */ if ((nn = nn->next) == NULL) return; for (p = nn->string; *p != '\0'; p++) *p = tolower((unsigned char)*p); mdoc->meta.arch = mandoc_strdup(nn->string); /* Ignore fourth and later arguments. */ if ((nn = nn->next) != NULL) mandoc_msg(MANDOCERR_ARG_EXCESS, nn->line, nn->pos, "Dt ... %s", nn->string); } static void post_bx(POST_ARGS) { struct roff_node *n, *nch; const char *macro; post_delim_nb(mdoc); n = mdoc->last; nch = n->child; if (nch != NULL) { macro = !strcmp(nch->string, "Open") ? "Ox" : !strcmp(nch->string, "Net") ? "Nx" : !strcmp(nch->string, "Free") ? "Fx" : !strcmp(nch->string, "DragonFly") ? "Dx" : NULL; if (macro != NULL) mandoc_msg(MANDOCERR_BX, n->line, n->pos, "%s", macro); mdoc->last = nch; nch = nch->next; mdoc->next = ROFF_NEXT_SIBLING; roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); mdoc->last->flags |= NODE_NOSRC; mdoc->next = ROFF_NEXT_SIBLING; } else mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->line, n->pos, "BSD"); mdoc->last->flags |= NODE_NOSRC; if (nch == NULL) { mdoc->last = n; return; } roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); mdoc->last->flags |= NODE_NOSRC; mdoc->next = ROFF_NEXT_SIBLING; roff_word_alloc(mdoc, n->line, n->pos, "-"); mdoc->last->flags |= NODE_NOSRC; roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; /* * Make `Bx's second argument always start with an uppercase * letter. Groff checks if it's an "accepted" term, but we just * uppercase blindly. */ *nch->string = (char)toupper((unsigned char)*nch->string); } static void post_os(POST_ARGS) { #ifndef OSNAME struct utsname utsname; #endif struct roff_node *n; n = mdoc->last; n->flags |= NODE_NOPRT; if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os"); else if (mdoc->flags & MDOC_PBODY) mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os"); post_delim(mdoc); /* * Set the operating system by way of the `Os' macro. * The order of precedence is: * 1. the argument of the `Os' macro, unless empty * 2. the -Ios=foo command line argument, if provided * 3. -DOSNAME="\"foo\"", if provided during compilation * 4. "sysname release" from uname(3) */ free(mdoc->meta.os); mdoc->meta.os = NULL; deroff(&mdoc->meta.os, n); if (mdoc->meta.os) goto out; if (mdoc->os_s != NULL) { mdoc->meta.os = mandoc_strdup(mdoc->os_s); goto out; } #ifdef OSNAME mdoc->meta.os = mandoc_strdup(OSNAME); #else /*!OSNAME */ if (mdoc->os_r == NULL) { if (uname(&utsname) == -1) { mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os"); mdoc->os_r = mandoc_strdup("UNKNOWN"); } else mandoc_asprintf(&mdoc->os_r, "%s %s", utsname.sysname, utsname.release); } mdoc->meta.os = mandoc_strdup(mdoc->os_r); #endif /*!OSNAME*/ out: if (mdoc->meta.os_e == MANDOC_OS_OTHER) { if (strstr(mdoc->meta.os, "OpenBSD") != NULL) mdoc->meta.os_e = MANDOC_OS_OPENBSD; else if (strstr(mdoc->meta.os, "NetBSD") != NULL) mdoc->meta.os_e = MANDOC_OS_NETBSD; } /* * This is the earliest point where we can check * Mdocdate conventions because we don't know * the operating system earlier. */ if (n->child != NULL) mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos, "Os %s (%s)", n->child->string, mdoc->meta.os_e == MANDOC_OS_OPENBSD ? "OpenBSD" : "NetBSD"); while (n->tok != MDOC_Dd) if ((n = n->prev) == NULL) return; if ((n = n->child) == NULL) return; if (strncmp(n->string, "$" "Mdocdate", 9)) { if (mdoc->meta.os_e == MANDOC_OS_OPENBSD) mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line, n->pos, "Dd %s (OpenBSD)", n->string); } else { if (mdoc->meta.os_e == MANDOC_OS_NETBSD) mandoc_msg(MANDOCERR_MDOCDATE, n->line, n->pos, "Dd %s (NetBSD)", n->string); } } enum roff_sec mdoc_a2sec(const char *p) { int i; for (i = 0; i < (int)SEC__MAX; i++) if (secnames[i] && 0 == strcmp(p, secnames[i])) return (enum roff_sec)i; return SEC_CUSTOM; } static size_t macro2len(enum roff_tok macro) { switch (macro) { case MDOC_Ad: return 12; case MDOC_Ao: return 12; case MDOC_An: return 12; case MDOC_Aq: return 12; case MDOC_Ar: return 12; case MDOC_Bo: return 12; case MDOC_Bq: return 12; case MDOC_Cd: return 12; case MDOC_Cm: return 10; case MDOC_Do: return 10; case MDOC_Dq: return 12; case MDOC_Dv: return 12; case MDOC_Eo: return 12; case MDOC_Em: return 10; case MDOC_Er: return 17; case MDOC_Ev: return 15; case MDOC_Fa: return 12; case MDOC_Fl: return 10; case MDOC_Fo: return 16; case MDOC_Fn: return 16; case MDOC_Ic: return 10; case MDOC_Li: return 16; case MDOC_Ms: return 6; case MDOC_Nm: return 10; case MDOC_No: return 12; case MDOC_Oo: return 10; case MDOC_Op: return 14; case MDOC_Pa: return 32; case MDOC_Pf: return 12; case MDOC_Po: return 12; case MDOC_Pq: return 12; case MDOC_Ql: return 16; case MDOC_Qo: return 12; case MDOC_So: return 12; case MDOC_Sq: return 12; case MDOC_Sy: return 6; case MDOC_Sx: return 16; case MDOC_Tn: return 10; case MDOC_Va: return 12; case MDOC_Vt: return 12; case MDOC_Xr: return 10; default: break; - }; + } return 0; }