Index: head/contrib/ctfdump/Makefile =================================================================== --- head/contrib/ctfdump/Makefile (nonexistent) +++ head/contrib/ctfdump/Makefile (revision 324358) @@ -0,0 +1,11 @@ + +PROG= ctfdump +SRCS= ctfdump.c elf.c + +CFLAGS+= -W -Wall -Wstrict-prototypes -Wno-unused -Wunused-variable + +CFLAGS+= -DZLIB +LDADD+= -lz +DPADD+= ${LIBZ} + +.include Property changes on: head/contrib/ctfdump/Makefile ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/ctfdump/ctfdump.1 =================================================================== --- head/contrib/ctfdump/ctfdump.1 (nonexistent) +++ head/contrib/ctfdump/ctfdump.1 (revision 324358) @@ -0,0 +1,53 @@ +.\" +.\" Copyright (c) 2016 Martin Pieuchot +.\" +.\" 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$ +.Dt CTFDUMP 1 +.Os +.Sh NAME +.Nm ctfdump +.Nd display CTF information +.Sh SYNOPSIS +.Nm ctfdump +.Op Fl dfhlst +file ... +.Sh DESCRIPTION +The +.Nm +utility display CTF information from the +.Dv \.SUNW_ctf +section of an +.Xr elf 5 +file or from a raw CTF file. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d +Display the object section. +.It Fl f +Display the function section. +.It Fl h +Dump the CTF header. +.It Fl l +Display the label section. +.It Fl s +Display the string table. +.It Fl t +Display the type section. +.El +.Sh EXIT STATUS +.Ex -std ctfdump +.Sh SEE ALSO +.Xr elf 5 Property changes on: head/contrib/ctfdump/ctfdump.1 ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/ctfdump/ctfdump.c =================================================================== --- head/contrib/ctfdump/ctfdump.c (nonexistent) +++ head/contrib/ctfdump/ctfdump.c (revision 324358) @@ -0,0 +1,638 @@ +/* + * Copyright (c) 2016 Martin Pieuchot + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef ZLIB +#include +#endif /* ZLIB */ + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#ifndef ELF_STRTAB +#define ELF_STRTAB ".strtab" +#endif +#ifndef ELF_CTF +#define ELF_CTF ".SUNW_ctf" +#endif + +#define DUMP_OBJECT (1 << 0) +#define DUMP_FUNCTION (1 << 1) +#define DUMP_HEADER (1 << 2) +#define DUMP_LABEL (1 << 3) +#define DUMP_STRTAB (1 << 4) +#define DUMP_STATISTIC (1 << 5) +#define DUMP_TYPE (1 << 6) + +int dump(const char *, uint8_t); +int isctf(const char *, size_t); +__dead2 void usage(void); + +int ctf_dump(const char *, size_t, uint8_t); +uint32_t ctf_dump_type(struct ctf_header *, const char *, off_t, + uint32_t, uint32_t); +const char *ctf_kind2name(uint16_t); +const char *ctf_enc2name(uint16_t); +const char *ctf_off2name(struct ctf_header *, const char *, off_t, + uint32_t); + +int elf_dump(char *, size_t, uint8_t); +const char *elf_idx2sym(size_t *, uint8_t); + +/* elf.c */ +int iself(const char *, size_t); +int elf_getshstab(const char *, size_t, const char **, size_t *); +ssize_t elf_getsymtab(const char *, const char *, size_t, + const Elf_Sym **, size_t *); +ssize_t elf_getsection(char *, const char *, const char *, + size_t, const char **, size_t *); + +char *decompress(const char *, size_t, size_t); + +int +main(int argc, char *argv[]) +{ + const char *filename; + uint8_t flags = 0; + int ch, error = 0; + + setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "dfhlst")) != -1) { + switch (ch) { + case 'd': + flags |= DUMP_OBJECT; + break; + case 'f': + flags |= DUMP_FUNCTION; + break; + case 'h': + flags |= DUMP_HEADER; + break; + case 'l': + flags |= DUMP_LABEL; + break; + case 's': + flags |= DUMP_STRTAB; + break; + case 't': + flags |= DUMP_TYPE; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc <= 0) + usage(); + + /* Dump everything by default */ + if (flags == 0) + flags = 0xff; + + while ((filename = *argv++) != NULL) + error |= dump(filename, flags); + + return error; +} + +int +dump(const char *path, uint8_t flags) +{ + struct stat st; + int fd, error = 1; + char *p; + + fd = open(path, O_RDONLY); + if (fd == -1) { + warn("open"); + return 1; + } + if (fstat(fd, &st) == -1) { + warn("fstat"); + return 1; + } + if ((uintmax_t)st.st_size > SIZE_MAX) { + warnx("file too big to fit memory"); + return 1; + } + + p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (p == MAP_FAILED) + err(1, "mmap"); + + if (iself(p, st.st_size)) { + error = elf_dump(p, st.st_size, flags); + } else if (isctf(p, st.st_size)) { + error = ctf_dump(p, st.st_size, flags); + } + + munmap(p, st.st_size); + close(fd); + + return error; +} + +const char *strtab; +const Elf_Sym *symtab; +size_t strtabsz, nsymb; + +const char * +elf_idx2sym(size_t *idx, uint8_t type) +{ + const Elf_Sym *st; + size_t i; + + for (i = *idx + 1; i < nsymb; i++) { + st = &symtab[i]; + + if (ELF_ST_TYPE(st->st_info) != type) + continue; + + *idx = i; + return strtab + st->st_name; + } + + return NULL; +} + +int +elf_dump(char *p, size_t filesize, uint8_t flags) +{ + Elf_Ehdr *eh = (Elf_Ehdr *)p; + Elf_Shdr *sh; + const char *shstab; + size_t i, shstabsz; + + /* Find section header string table location and size. */ + if (elf_getshstab(p, filesize, &shstab, &shstabsz)) + return 1; + + /* Find symbol table location and number of symbols. */ + if (elf_getsymtab(p, shstab, shstabsz, &symtab, &nsymb) == -1) + warnx("symbol table not found"); + + /* Find string table location and size. */ + if (elf_getsection(p, ELF_STRTAB, shstab, shstabsz, &strtab, + &strtabsz) == -1) + warnx("string table not found"); + + /* Find CTF section and dump it. */ + for (i = 0; i < eh->e_shnum; i++) { + sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize); + + if ((sh->sh_link >= eh->e_shnum) || + (sh->sh_name >= shstabsz)) + continue; + + if (strncmp(shstab + sh->sh_name, ELF_CTF, strlen(ELF_CTF))) + continue; + + if (!isctf(p + sh->sh_offset, sh->sh_size)) + break; + + return ctf_dump(p + sh->sh_offset, sh->sh_size, flags); + } + + warnx("%s section not found", ELF_CTF); + return 1; +} + +int +isctf(const char *p, size_t filesize) +{ + struct ctf_header *cth = (struct ctf_header *)p; + size_t dlen; + + if (filesize < sizeof(struct ctf_header)) { + warnx("file too small to be CTF"); + return 0; + } + + if (cth->cth_magic != CTF_MAGIC || cth->cth_version != CTF_VERSION) + return 0; + + dlen = cth->cth_stroff + cth->cth_strlen; + if (dlen > filesize && !(cth->cth_flags & CTF_F_COMPRESS)) { + warnx("bogus file size"); + return 0; + } + + if ((cth->cth_lbloff & 3) || (cth->cth_objtoff & 1) || + (cth->cth_funcoff & 1) || (cth->cth_typeoff & 3)) { + warnx("wrongly aligned offset"); + return 0; + } + + if ((cth->cth_lbloff >= dlen) || (cth->cth_objtoff >= dlen) || + (cth->cth_funcoff >= dlen) || (cth->cth_typeoff >= dlen)) { + warnx("truncated file"); + return 0; + } + + if ((cth->cth_lbloff > cth->cth_objtoff) || + (cth->cth_objtoff > cth->cth_funcoff) || + (cth->cth_funcoff > cth->cth_typeoff) || + (cth->cth_typeoff > cth->cth_stroff)) { + warnx("corrupted file"); + return 0; + } + + return 1; +} + +int +ctf_dump(const char *p, size_t size, uint8_t flags) +{ + struct ctf_header *cth = (struct ctf_header *)p; + off_t dlen = cth->cth_stroff + cth->cth_strlen; + char *data; + + if (cth->cth_flags & CTF_F_COMPRESS) { + data = decompress(p + sizeof(*cth), size - sizeof(*cth), dlen); + if (data == NULL) + return 1; + } else { + data = (char *)p + sizeof(*cth); + } + + if (flags & DUMP_HEADER) { + printf(" cth_magic = 0x%04x\n", cth->cth_magic); + printf(" cth_version = %d\n", cth->cth_version); + printf(" cth_flags = 0x%02x\n", cth->cth_flags); + printf(" cth_parlabel = %s\n", + ctf_off2name(cth, data, dlen, cth->cth_parname)); + printf(" cth_parname = %s\n", + ctf_off2name(cth, data, dlen, cth->cth_parname)); + printf(" cth_lbloff = %d\n", cth->cth_lbloff); + printf(" cth_objtoff = %d\n", cth->cth_objtoff); + printf(" cth_funcoff = %d\n", cth->cth_funcoff); + printf(" cth_typeoff = %d\n", cth->cth_typeoff); + printf(" cth_stroff = %d\n", cth->cth_stroff); + printf(" cth_strlen = %d\n", cth->cth_strlen); + printf("\n"); + } + + if (flags & DUMP_LABEL) { + uint32_t lbloff = cth->cth_lbloff; + struct ctf_lblent *ctl; + + while (lbloff < cth->cth_objtoff) { + ctl = (struct ctf_lblent *)(data + lbloff); + + printf(" %5u %s\n", ctl->ctl_typeidx, + ctf_off2name(cth, data, dlen, ctl->ctl_label)); + + lbloff += sizeof(*ctl); + } + printf("\n"); + } + + if (flags & DUMP_OBJECT) { + uint32_t objtoff = cth->cth_objtoff; + size_t idx = 0, i = 0; + uint16_t *dsp; + const char *s; + int l; + + while (objtoff < cth->cth_funcoff) { + dsp = (uint16_t *)(data + objtoff); + + l = printf(" [%zu] %u", i++, *dsp); + if ((s = elf_idx2sym(&idx, STT_OBJECT)) != NULL) + printf("%*s %s (%zu)\n", (14 - l), "", s, idx); + else + printf("\n"); + + objtoff += sizeof(*dsp); + } + printf("\n"); + } + + if (flags & DUMP_FUNCTION) { + uint16_t *fsp, kind, vlen; + size_t idx = 0, i = -1; + const char *s; + int l; + + fsp = (uint16_t *)(data + cth->cth_funcoff); + while (fsp < (uint16_t *)(data + cth->cth_typeoff)) { + kind = CTF_INFO_KIND(*fsp); + vlen = CTF_INFO_VLEN(*fsp); + s = elf_idx2sym(&idx, STT_FUNC); + fsp++; + i++; + + if (kind == CTF_K_UNKNOWN && vlen == 0) + continue; + + l = printf(" [%zu] FUNC ", i); + if (s != NULL) + printf("(%s)", s); + printf(" returns: %u args: (", *fsp++); + while (vlen-- > 0) + printf("%u%s", *fsp++, (vlen > 0) ? ", " : ""); + printf(")\n"); + } + printf("\n"); + } + + if (flags & DUMP_TYPE) { + uint32_t idx = 1, offset = cth->cth_typeoff; + + while (offset < cth->cth_stroff) { + offset += ctf_dump_type(cth, data, dlen, offset, idx++); + } + printf("\n"); + } + + if (flags & DUMP_STRTAB) { + uint32_t offset = 0; + const char *str; + + while (offset < cth->cth_strlen) { + str = ctf_off2name(cth, data, dlen, offset); + + printf(" [%u] ", offset); + if (strcmp(str, "(anon)")) + offset += printf("%s\n", str); + else { + printf("\\0\n"); + offset++; + } + } + printf("\n"); + } + + if (cth->cth_flags & CTF_F_COMPRESS) + free(data); + + return 0; +} + +uint32_t +ctf_dump_type(struct ctf_header *cth, const char *data, off_t dlen, + uint32_t offset, uint32_t idx) +{ + const char *p = data + offset; + const struct ctf_type *ctt = (struct ctf_type *)p; + const struct ctf_array *cta; + uint16_t *argp, i, kind, vlen, root; + uint32_t eob, toff; + uint64_t size; + const char *name, *kname; + + kind = CTF_INFO_KIND(ctt->ctt_info); + vlen = CTF_INFO_VLEN(ctt->ctt_info); + root = CTF_INFO_ISROOT(ctt->ctt_info); + name = ctf_off2name(cth, data, dlen, ctt->ctt_name); + + if (root) + printf(" <%u> ", idx); + else + printf(" [%u] ", idx); + + if ((kname = ctf_kind2name(kind)) != NULL) + printf("%s %s", kname, name); + + if (ctt->ctt_size <= CTF_MAX_SIZE) { + size = ctt->ctt_size; + toff = sizeof(struct ctf_stype); + } else { + size = CTF_TYPE_LSIZE(ctt); + toff = sizeof(struct ctf_type); + } + + switch (kind) { + case CTF_K_UNKNOWN: + case CTF_K_FORWARD: + break; + case CTF_K_INTEGER: + eob = *((uint32_t *)(p + toff)); + toff += sizeof(uint32_t); + printf(" encoding=%s offset=%u bits=%u", + ctf_enc2name(CTF_INT_ENCODING(eob)), CTF_INT_OFFSET(eob), + CTF_INT_BITS(eob)); + break; + case CTF_K_FLOAT: + eob = *((uint32_t *)(p + toff)); + toff += sizeof(uint32_t); + printf(" encoding=0x%x offset=%u bits=%u", + CTF_FP_ENCODING(eob), CTF_FP_OFFSET(eob), CTF_FP_BITS(eob)); + break; + case CTF_K_ARRAY: + cta = (struct ctf_array *)(p + toff); + printf(" content: %u index: %u nelems: %u\n", cta->cta_contents, + cta->cta_index, cta->cta_nelems); + toff += sizeof(struct ctf_array); + break; + case CTF_K_FUNCTION: + argp = (uint16_t *)(p + toff); + printf(" returns: %u args: (%u", ctt->ctt_type, *argp); + for (i = 1; i < vlen; i++) { + argp++; + printf(", %u", *argp); + } + printf(")"); + toff += (vlen + (vlen & 1)) * sizeof(uint16_t); + break; + case CTF_K_STRUCT: + case CTF_K_UNION: + printf(" (%lu bytes)\n", size); + + if (size < CTF_LSTRUCT_THRESH) { + for (i = 0; i < vlen; i++) { + struct ctf_member *ctm; + + ctm = (struct ctf_member *)(p + toff); + toff += sizeof(struct ctf_member); + + printf("\t%s type=%u off=%u\n", + ctf_off2name(cth, data, dlen, + ctm->ctm_name), + ctm->ctm_type, ctm->ctm_offset); + } + } else { + for (i = 0; i < vlen; i++) { + struct ctf_lmember *ctlm; + + ctlm = (struct ctf_lmember *)(p + toff); + toff += sizeof(struct ctf_lmember); + + printf("\t%s type=%u off=%zu\n", + ctf_off2name(cth, data, dlen, + ctlm->ctlm_name), + ctlm->ctlm_type, CTF_LMEM_OFFSET(ctlm)); + } + } + break; + case CTF_K_ENUM: + printf("\n"); + for (i = 0; i < vlen; i++) { + struct ctf_enum *cte; + + cte = (struct ctf_enum *)(p + toff); + toff += sizeof(struct ctf_enum); + + printf("\t%s = %d\n", + ctf_off2name(cth, data, dlen, cte->cte_name), + cte->cte_value); + } + break; + case CTF_K_POINTER: + case CTF_K_TYPEDEF: + case CTF_K_VOLATILE: + case CTF_K_CONST: + case CTF_K_RESTRICT: + printf(" refers to %u", ctt->ctt_type); + break; + default: + errx(1, "incorrect type %u at offset %u", kind, offset); + } + + printf("\n"); + + return toff; +} + +const char * +ctf_kind2name(uint16_t kind) +{ + static const char *kind_name[] = { NULL, "INTEGER", "FLOAT", "POINTER", + "ARRAY", "FUNCTION", "STRUCT", "UNION", "ENUM", "FORWARD", + "TYPEDEF", "VOLATILE", "CONST", "RESTRICT" }; + + if (kind >= nitems(kind_name)) + return NULL; + + return kind_name[kind]; +} + +const char * +ctf_enc2name(uint16_t enc) +{ + static const char *enc_name[] = { "SIGNED", "CHAR", "SIGNED CHAR", + "BOOL", "SIGNED BOOL" }; + static char invalid[7]; + + if (enc == CTF_INT_VARARGS) + return "VARARGS"; + + if (enc > 0 && enc < nitems(enc_name)) + return enc_name[enc - 1]; + + snprintf(invalid, sizeof(invalid), "0x%x", enc); + return invalid; +} + +const char * +ctf_off2name(struct ctf_header *cth, const char *data, off_t dlen, + uint32_t offset) +{ + const char *name; + + if (CTF_NAME_STID(offset) != CTF_STRTAB_0) + return "external"; + + if (CTF_NAME_OFFSET(offset) >= cth->cth_strlen) + return "exceeds strlab"; + + if (cth->cth_stroff + CTF_NAME_OFFSET(offset) >= dlen) + return "invalid"; + + name = data + cth->cth_stroff + CTF_NAME_OFFSET(offset); + if (*name == '\0') + return "(anon)"; + + return name; +} + +char * +decompress(const char *buf, size_t size, size_t len) +{ +#ifdef ZLIB + z_stream stream; + char *data; + int error; + + data = malloc(len); + if (data == NULL) { + warn(NULL); + return NULL; + } + + memset(&stream, 0, sizeof(stream)); + stream.next_in = (void *)buf; + stream.avail_in = size; + stream.next_out = (uint8_t *)data; + stream.avail_out = len; + + if ((error = inflateInit(&stream)) != Z_OK) { + warnx("zlib inflateInit failed: %s", zError(error)); + goto exit; + } + + if ((error = inflate(&stream, Z_FINISH)) != Z_STREAM_END) { + warnx("zlib inflate failed: %s", zError(error)); + inflateEnd(&stream); + goto exit; + } + + if ((error = inflateEnd(&stream)) != Z_OK) { + warnx("zlib inflateEnd failed: %s", zError(error)); + goto exit; + } + + if (stream.total_out != len) { + warnx("decompression failed: %zu != %zu", + stream.total_out, len); + goto exit; + } + + return data; + +exit: + free(data); +#endif /* ZLIB */ + return NULL; +} + +__dead2 void +usage(void) +{ + fprintf(stderr, "usage: %s [-dfhlst] file ...\n", + getprogname()); + exit(1); +} Property changes on: head/contrib/ctfdump/ctfdump.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/ctfdump/elf.c =================================================================== --- head/contrib/ctfdump/elf.c (nonexistent) +++ head/contrib/ctfdump/elf.c (revision 324358) @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2016 Martin Pieuchot + * + * 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. + */ + +#include +#include + +#include + +#include +#include +#include + +#define ELF_SYMTAB ".symtab" +#define Elf_RelA __CONCAT(__CONCAT(Elf,__ELF_WORD_SIZE),_Rela) + +static int elf_reloc_size(unsigned long); +static void elf_reloc_apply(const char *, const char *, size_t, ssize_t, + char *, size_t); + +int +iself(const char *p, size_t filesize) +{ + Elf_Ehdr *eh = (Elf_Ehdr *)p; + + if (filesize < (off_t)sizeof(Elf_Ehdr)) { + warnx("file too small to be ELF"); + return 0; + } + + if (eh->e_ehsize < sizeof(Elf_Ehdr) || !IS_ELF(*eh)) + return 0; + + if (eh->e_ident[EI_CLASS] != ELF_CLASS) { + warnx("unexpected word size %u", eh->e_ident[EI_CLASS]); + return 0; + } + if (eh->e_ident[EI_VERSION] != ELF_TARG_VER) { + warnx("unexpected version %u", eh->e_ident[EI_VERSION]); + return 0; + } + if (eh->e_ident[EI_DATA] > ELFDATA2MSB) { + warnx("unexpected data format %u", eh->e_ident[EI_DATA]); + return 0; + } + if (eh->e_shoff > filesize) { + warnx("bogus section table offset 0x%lx", (off_t)eh->e_shoff); + return 0; + } + if (eh->e_shentsize < sizeof(Elf_Shdr)) { + warnx("bogus section header size %u", eh->e_shentsize); + return 0; + } + if (eh->e_shnum > (filesize - eh->e_shoff) / eh->e_shentsize) { + warnx("bogus section header count %u", eh->e_shnum); + return 0; + } + if (eh->e_shstrndx >= eh->e_shnum) { + warnx("bogus string table index %u", eh->e_shstrndx); + return 0; + } + + return 1; +} + +int +elf_getshstab(const char *p, size_t filesize, const char **shstab, + size_t *shstabsize) +{ + Elf_Ehdr *eh = (Elf_Ehdr *)p; + Elf_Shdr *sh; + + sh = (Elf_Shdr *)(p + eh->e_shoff + eh->e_shstrndx * eh->e_shentsize); + if (sh->sh_type != SHT_STRTAB) { + warnx("unexpected string table type"); + return -1; + } + if (sh->sh_offset > filesize) { + warnx("bogus string table offset"); + return -1; + } + if (sh->sh_size > filesize - sh->sh_offset) { + warnx("bogus string table size"); + return -1; + } + if (shstab != NULL) + *shstab = p + sh->sh_offset; + if (shstabsize != NULL) + *shstabsize = sh->sh_size; + + return 0; +} + +ssize_t +elf_getsymtab(const char *p, const char *shstab, size_t shstabsz, + const Elf_Sym **symtab, size_t *nsymb) +{ + Elf_Ehdr *eh = (Elf_Ehdr *)p; + Elf_Shdr *sh; + size_t snlen; + ssize_t i; + + snlen = strlen(ELF_SYMTAB); + + for (i = 0; i < eh->e_shnum; i++) { + sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize); + + if (sh->sh_type != SHT_SYMTAB) + continue; + + if ((sh->sh_link >= eh->e_shnum) || (sh->sh_name >= shstabsz)) + continue; + + if (strncmp(shstab + sh->sh_name, ELF_SYMTAB, snlen) == 0) { + if (symtab != NULL) + *symtab = (Elf_Sym *)(p + sh->sh_offset); + if (nsymb != NULL) + *nsymb = (sh->sh_size / sh->sh_entsize); + + return i; + } + } + + return -1; +} + +ssize_t +elf_getsection(char *p, const char *sname, const char *shstab, + size_t shstabsz, const char **psdata, size_t *pssz) +{ + Elf_Ehdr *eh = (Elf_Ehdr *)p; + Elf_Shdr *sh; + char *sdata = NULL; + size_t snlen, ssz = 0; + ssize_t sidx, i; + + snlen = strlen(sname); + if (snlen == 0) + return -1; + + /* Find the given section. */ + for (i = 0; i < eh->e_shnum; i++) { + sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize); + + if ((sh->sh_link >= eh->e_shnum) || (sh->sh_name >= shstabsz)) + continue; + + if (strncmp(shstab + sh->sh_name, sname, snlen) == 0) { + sidx = i; + sdata = p + sh->sh_offset; + ssz = sh->sh_size; + elf_reloc_apply(p, shstab, shstabsz, sidx, sdata, ssz); + break; + } + } + + if (sdata == NULL) + return -1; + + if (psdata != NULL) + *psdata = sdata; + if (pssz != NULL) + *pssz = ssz; + + return sidx; +} + +static int +elf_reloc_size(unsigned long type) +{ + switch (type) { +#ifdef R_X86_64_64 + case R_X86_64_64: + return sizeof(uint64_t); +#endif +#ifdef R_X86_64_32 + case R_X86_64_32: + return sizeof(uint32_t); +#endif +#ifdef RELOC_32 + case RELOC_32: + return sizeof(uint32_t); +#endif + default: + break; + } + + return -1; +} + +#define ELF_WRITE_RELOC(buf, val, rsize) \ +do { \ + if (rsize == 4) { \ + uint32_t v32 = val; \ + memcpy(buf, &v32, sizeof(v32)); \ + } else { \ + uint64_t v64 = val; \ + memcpy(buf, &v64, sizeof(v64)); \ + } \ +} while (0) + +static void +elf_reloc_apply(const char *p, const char *shstab, size_t shstabsz, + ssize_t sidx, char *sdata, size_t ssz) +{ + Elf_Ehdr *eh = (Elf_Ehdr *)p; + Elf_Shdr *sh; + Elf_Rel *rel = NULL; + Elf_RelA *rela = NULL; + const Elf_Sym *symtab, *sym; + ssize_t symtabidx; + size_t nsymb, rsym, rtyp, roff; + size_t i, j; + uint64_t value; + int rsize; + + /* Find symbol table location and number of symbols. */ + symtabidx = elf_getsymtab(p, shstab, shstabsz, &symtab, &nsymb); + if (symtabidx == -1) { + warnx("symbol table not found"); + return; + } + + /* Apply possible relocation. */ + for (i = 0; i < eh->e_shnum; i++) { + sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize); + + if (sh->sh_size == 0) + continue; + + if ((sh->sh_info != sidx) || (sh->sh_link != symtabidx)) + continue; + + switch (sh->sh_type) { + case SHT_RELA: + rela = (Elf_RelA *)(p + sh->sh_offset); + for (j = 0; j < (sh->sh_size / sizeof(Elf_RelA)); j++) { + rsym = ELF_R_SYM(rela[j].r_info); + rtyp = ELF_R_TYPE(rela[j].r_info); + roff = rela[j].r_offset; + if (rsym >= nsymb) + continue; + sym = &symtab[rsym]; + value = sym->st_value + rela[j].r_addend; + + rsize = elf_reloc_size(rtyp); + if (rsize == -1 || roff + rsize >= ssz) + continue; + + ELF_WRITE_RELOC(sdata + roff, value, rsize); + } + break; + case SHT_REL: + rel = (Elf_Rel *)(p + sh->sh_offset); + for (j = 0; j < (sh->sh_size / sizeof(Elf_Rel)); j++) { + rsym = ELF_R_SYM(rel[j].r_info); + rtyp = ELF_R_TYPE(rel[j].r_info); + roff = rel[j].r_offset; + if (rsym >= nsymb) + continue; + sym = &symtab[rsym]; + value = sym->st_value; + + rsize = elf_reloc_size(rtyp); + if (rsize == -1 || roff + rsize >= ssz) + continue; + + ELF_WRITE_RELOC(sdata + roff, value, rsize); + } + break; + default: + continue; + } + } +} Property changes on: head/contrib/ctfdump/elf.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/mdocml/Makefile =================================================================== --- head/contrib/mdocml/Makefile (revision 324357) +++ head/contrib/mdocml/Makefile (revision 324358) @@ -1,573 +1,573 @@ # $Id: Makefile,v 1.516 2017/07/20 16:24:53 schwarze Exp $ # # Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons # Copyright (c) 2011, 2013-2017 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. -VERSION = 1.14.2 +VERSION = 1.14.3 # === LIST OF FILES ==================================================== TESTSRCS = test-be32toh.c \ test-cmsg.c \ test-dirent-namlen.c \ test-EFTYPE.c \ test-err.c \ test-fts.c \ test-getline.c \ test-getsubopt.c \ test-isblank.c \ test-mkdtemp.c \ test-nanosleep.c \ test-ntohl.c \ test-O_DIRECTORY.c \ test-ohash.c \ test-PATH_MAX.c \ test-pledge.c \ test-progname.c \ test-recvmsg.c \ test-reallocarray.c \ test-recallocarray.c \ test-rewb-bsd.c \ test-rewb-sysv.c \ test-sandbox_init.c \ test-strcasestr.c \ test-stringlist.c \ test-strlcat.c \ test-strlcpy.c \ test-strptime.c \ test-strsep.c \ test-strtonum.c \ test-vasprintf.c \ test-wchar.c SRCS = att.c \ catman.c \ cgi.c \ chars.c \ compat_err.c \ compat_fts.c \ compat_getline.c \ compat_getsubopt.c \ compat_isblank.c \ compat_mkdtemp.c \ compat_ohash.c \ compat_progname.c \ compat_reallocarray.c \ compat_recallocarray.c \ compat_strcasestr.c \ compat_stringlist.c \ compat_strlcat.c \ compat_strlcpy.c \ compat_strsep.c \ compat_strtonum.c \ compat_vasprintf.c \ dba.c \ dba_array.c \ dba_read.c \ dba_write.c \ dbm.c \ dbm_map.c \ demandoc.c \ eqn.c \ eqn_html.c \ eqn_term.c \ html.c \ lib.c \ main.c \ man.c \ man_html.c \ man_macro.c \ man_term.c \ man_validate.c \ mandoc.c \ mandoc_aux.c \ mandoc_ohash.c \ mandoc_xr.c \ mandocd.c \ mandocdb.c \ manpath.c \ mansearch.c \ mdoc.c \ mdoc_argv.c \ mdoc_html.c \ mdoc_macro.c \ mdoc_man.c \ mdoc_markdown.c \ mdoc_state.c \ mdoc_term.c \ mdoc_validate.c \ msec.c \ out.c \ preconv.c \ read.c \ roff.c \ roff_html.c \ roff_term.c \ roff_validate.c \ soelim.c \ st.c \ tag.c \ tbl.c \ tbl_data.c \ tbl_html.c \ tbl_layout.c \ tbl_opts.c \ tbl_term.c \ term.c \ term_ascii.c \ term_ps.c \ term_tab.c \ tree.c DISTFILES = INSTALL \ LICENSE \ Makefile \ Makefile.depend \ NEWS \ TODO \ apropos.1 \ catman.8 \ cgi.h.example \ compat_fts.h \ compat_ohash.h \ compat_stringlist.h \ configure \ configure.local.example \ dba.h \ dba_array.h \ dba_write.h \ dbm.h \ dbm_map.h \ demandoc.1 \ eqn.7 \ gmdiff \ html.h \ lib.in \ libman.h \ libmandoc.h \ libmdoc.h \ libroff.h \ main.h \ makewhatis.8 \ man.1 \ man.7 \ man.cgi.3 \ man.cgi.8 \ man.conf.5 \ man.h \ man.options.1 \ manconf.h \ mandoc.1 \ mandoc.3 \ mandoc.css \ mandoc.db.5 \ mandoc.h \ mandoc_aux.h \ mandoc_char.7 \ mandoc_escape.3 \ mandoc_headers.3 \ mandoc_html.3 \ mandoc_malloc.3 \ mandoc_ohash.h \ mandoc_xr.h \ mandocd.8 \ mansearch.3 \ mansearch.h \ mchars_alloc.3 \ mdoc.7 \ mdoc.h \ msec.in \ out.h \ predefs.in \ roff.7 \ roff.h \ roff_int.h \ soelim.1 \ st.in \ tag.h \ tbl.3 \ tbl.7 \ term.h \ $(SRCS) \ $(TESTSRCS) LIBMAN_OBJS = man.o \ man_macro.o \ man_validate.o LIBMDOC_OBJS = att.o \ lib.o \ mdoc.o \ mdoc_argv.o \ mdoc_macro.o \ mdoc_state.o \ mdoc_validate.o \ st.o LIBROFF_OBJS = eqn.o \ roff.o \ roff_validate.o \ tbl.o \ tbl_data.o \ tbl_layout.o \ tbl_opts.o LIBMANDOC_OBJS = $(LIBMAN_OBJS) \ $(LIBMDOC_OBJS) \ $(LIBROFF_OBJS) \ chars.o \ mandoc.o \ mandoc_aux.o \ mandoc_ohash.o \ mandoc_xr.o \ msec.o \ preconv.o \ read.o COMPAT_OBJS = compat_err.o \ compat_fts.o \ compat_getline.o \ compat_getsubopt.o \ compat_isblank.o \ compat_mkdtemp.o \ compat_ohash.o \ compat_progname.o \ compat_reallocarray.o \ compat_recallocarray.o \ compat_strcasestr.o \ compat_strlcat.o \ compat_strlcpy.o \ compat_strsep.o \ compat_strtonum.o \ compat_vasprintf.o MANDOC_HTML_OBJS = eqn_html.o \ html.o \ man_html.o \ mdoc_html.o \ roff_html.o \ tbl_html.o MANDOC_TERM_OBJS = eqn_term.o \ man_term.o \ mdoc_term.o \ roff_term.o \ term.o \ term_ascii.o \ term_ps.o \ term_tab.o \ tbl_term.o DBM_OBJS = dbm.o \ dbm_map.o \ mansearch.o DBA_OBJS = dba.o \ dba_array.o \ dba_read.o \ dba_write.o \ mandocdb.o MAIN_OBJS = $(MANDOC_HTML_OBJS) \ $(MANDOC_MAN_OBJS) \ $(MANDOC_TERM_OBJS) \ $(DBM_OBJS) \ $(DBA_OBJS) \ main.o \ manpath.o \ mdoc_man.o \ mdoc_markdown.o \ out.o \ tag.o \ tree.o CGI_OBJS = $(MANDOC_HTML_OBJS) \ $(DBM_OBJS) \ cgi.o \ out.o MANDOCD_OBJS = $(MANDOC_HTML_OBJS) \ $(MANDOC_TERM_OBJS) \ mandocd.o \ out.o \ tag.o DEMANDOC_OBJS = demandoc.o SOELIM_OBJS = soelim.o \ compat_err.o \ compat_getline.o \ compat_progname.o \ compat_reallocarray.o \ compat_stringlist.o WWW_MANS = apropos.1.html \ demandoc.1.html \ man.1.html \ mandoc.1.html \ soelim.1.html \ man.cgi.3.html \ mandoc.3.html \ mandoc_escape.3.html \ mandoc_headers.3.html \ mandoc_html.3.html \ mandoc_malloc.3.html \ mansearch.3.html \ mchars_alloc.3.html \ tbl.3.html \ man.conf.5.html \ mandoc.db.5.html \ eqn.7.html \ man.7.html \ mandoc_char.7.html \ mandocd.8.html \ mdoc.7.html \ roff.7.html \ tbl.7.html \ catman.8.html \ makewhatis.8.html \ man.cgi.8.html \ man.h.html \ manconf.h.html \ mandoc.h.html \ mandoc_aux.h.html \ mansearch.h.html \ mdoc.h.html \ roff.h.html # === USER CONFIGURATION =============================================== include Makefile.local # === DEPENDENCY HANDLING ============================================== all: mandoc demandoc soelim $(BUILD_TARGETS) Makefile.local install: base-install $(INSTALL_TARGETS) www: $(WWW_MANS) $(WWW_MANS): mandoc .PHONY: base-install cgi-install install www-install .PHONY: clean distclean depend include Makefile.depend # === TARGETS CONTAINING SHELL COMMANDS ================================ distclean: clean rm -f Makefile.local config.h config.h.old config.log config.log.old clean: rm -f libmandoc.a $(LIBMANDOC_OBJS) $(COMPAT_OBJS) rm -f mandoc $(MAIN_OBJS) rm -f man.cgi $(CGI_OBJS) rm -f mandocd catman catman.o $(MANDOCD_OBJS) rm -f demandoc $(DEMANDOC_OBJS) rm -f soelim $(SOELIM_OBJS) rm -f $(WWW_MANS) mandoc.tar.gz mandoc.sha256 rm -rf *.dSYM base-install: mandoc demandoc soelim mkdir -p $(DESTDIR)$(BINDIR) mkdir -p $(DESTDIR)$(SBINDIR) mkdir -p $(DESTDIR)$(MANDIR)/man1 mkdir -p $(DESTDIR)$(MANDIR)/man5 mkdir -p $(DESTDIR)$(MANDIR)/man7 mkdir -p $(DESTDIR)$(MANDIR)/man8 $(INSTALL_PROGRAM) mandoc demandoc $(DESTDIR)$(BINDIR) $(INSTALL_PROGRAM) soelim $(DESTDIR)$(BINDIR)/$(BINM_SOELIM) cd $(DESTDIR)$(BINDIR) && $(LN) mandoc $(BINM_MAN) cd $(DESTDIR)$(BINDIR) && $(LN) mandoc $(BINM_APROPOS) cd $(DESTDIR)$(BINDIR) && $(LN) mandoc $(BINM_WHATIS) cd $(DESTDIR)$(SBINDIR) && \ $(LN) ${BIN_FROM_SBIN}/mandoc $(BINM_MAKEWHATIS) $(INSTALL_MAN) mandoc.1 demandoc.1 $(DESTDIR)$(MANDIR)/man1 $(INSTALL_MAN) soelim.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_SOELIM).1 $(INSTALL_MAN) man.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_MAN).1 $(INSTALL_MAN) apropos.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 cd $(DESTDIR)$(MANDIR)/man1 && $(LN) $(BINM_APROPOS).1 $(BINM_WHATIS).1 $(INSTALL_MAN) man.conf.5 $(DESTDIR)$(MANDIR)/man5/$(MANM_MANCONF).5 $(INSTALL_MAN) mandoc.db.5 $(DESTDIR)$(MANDIR)/man5 $(INSTALL_MAN) man.7 $(DESTDIR)$(MANDIR)/man7/$(MANM_MAN).7 $(INSTALL_MAN) mdoc.7 $(DESTDIR)$(MANDIR)/man7/$(MANM_MDOC).7 $(INSTALL_MAN) roff.7 $(DESTDIR)$(MANDIR)/man7/$(MANM_ROFF).7 $(INSTALL_MAN) eqn.7 $(DESTDIR)$(MANDIR)/man7/$(MANM_EQN).7 $(INSTALL_MAN) tbl.7 $(DESTDIR)$(MANDIR)/man7/$(MANM_TBL).7 $(INSTALL_MAN) mandoc_char.7 $(DESTDIR)$(MANDIR)/man7 $(INSTALL_MAN) makewhatis.8 \ $(DESTDIR)$(MANDIR)/man8/$(BINM_MAKEWHATIS).8 lib-install: libmandoc.a mkdir -p $(DESTDIR)$(LIBDIR) mkdir -p $(DESTDIR)$(INCLUDEDIR) mkdir -p $(DESTDIR)$(MANDIR)/man3 $(INSTALL_LIB) libmandoc.a $(DESTDIR)$(LIBDIR) $(INSTALL_LIB) man.h mandoc.h mandoc_aux.h mdoc.h roff.h \ $(DESTDIR)$(INCLUDEDIR) $(INSTALL_MAN) mandoc.3 mandoc_escape.3 mandoc_malloc.3 \ mansearch.3 mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3 cgi-install: man.cgi mkdir -p $(DESTDIR)$(CGIBINDIR) mkdir -p $(DESTDIR)$(HTDOCDIR) $(INSTALL_PROGRAM) man.cgi $(DESTDIR)$(CGIBINDIR) $(INSTALL_DATA) mandoc.css $(DESTDIR)$(HTDOCDIR) catman-install: mandocd catman mkdir -p $(DESTDIR)$(SBINDIR) mkdir -p $(DESTDIR)$(MANDIR)/man8 $(INSTALL_PROGRAM) mandocd $(DESTDIR)$(SBINDIR) $(INSTALL_PROGRAM) catman $(DESTDIR)$(SBINDIR)/$(BINM_CATMAN) $(INSTALL_MAN) mandocd.8 $(DESTDIR)$(MANDIR)/man8 $(INSTALL_MAN) catman.8 $(DESTDIR)$(MANDIR)/man8/$(BINM_CATMAN).8 uninstall: rm -f $(DESTDIR)$(BINDIR)/mandoc rm -f $(DESTDIR)$(BINDIR)/demandoc rm -f $(DESTDIR)$(BINDIR)/$(BINM_SOELIM) rm -f $(DESTDIR)$(BINDIR)/$(BINM_MAN) rm -f $(DESTDIR)$(BINDIR)/$(BINM_APROPOS) rm -f $(DESTDIR)$(BINDIR)/$(BINM_WHATIS) rm -f $(DESTDIR)$(SBINDIR)/$(BINM_MAKEWHATIS) rm -f $(DESTDIR)$(MANDIR)/man1/mandoc.1 rm -f $(DESTDIR)$(MANDIR)/man1/demandoc.1 rm -f $(DESTDIR)$(MANDIR)/man1/$(BINM_SOELIM).1 rm -f $(DESTDIR)$(MANDIR)/man1/$(BINM_MAN).1 rm -f $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 rm -f $(DESTDIR)$(MANDIR)/man1/$(BINM_WHATIS).1 rm -f $(DESTDIR)$(MANDIR)/man5/$(MANM_MANCONF).5 rm -f $(DESTDIR)$(MANDIR)/man5/mandoc.db.5 rm -f $(DESTDIR)$(MANDIR)/man7/$(MANM_MAN).7 rm -f $(DESTDIR)$(MANDIR)/man7/$(MANM_MDOC).7 rm -f $(DESTDIR)$(MANDIR)/man7/$(MANM_ROFF).7 rm -f $(DESTDIR)$(MANDIR)/man7/$(MANM_EQN).7 rm -f $(DESTDIR)$(MANDIR)/man7/$(MANM_TBL).7 rm -f $(DESTDIR)$(MANDIR)/man7/mandoc_char.7 rm -f $(DESTDIR)$(MANDIR)/man8/$(BINM_MAKEWHATIS).8 rm -f $(DESTDIR)$(CGIBINDIR)/man.cgi rm -f $(DESTDIR)$(HTDOCDIR)/mandoc.css rm -f $(DESTDIR)$(SBINDIR)/mandocd rm -f $(DESTDIR)$(SBINDIR)/$(BINM_CATMAN) rm -f $(DESTDIR)$(MANDIR)/man8/mandocd.8 rm -f $(DESTDIR)$(MANDIR)/man8/$(BINM_CATMAN).8 rm -f $(DESTDIR)$(LIBDIR)/libmandoc.a rm -f $(DESTDIR)$(MANDIR)/man3/mandoc.3 rm -f $(DESTDIR)$(MANDIR)/man3/mandoc_escape.3 rm -f $(DESTDIR)$(MANDIR)/man3/mandoc_malloc.3 rm -f $(DESTDIR)$(MANDIR)/man3/mansearch.3 rm -f $(DESTDIR)$(MANDIR)/man3/mchars_alloc.3 rm -f $(DESTDIR)$(MANDIR)/man3/tbl.3 rm -f $(DESTDIR)$(INCLUDEDIR)/man.h rm -f $(DESTDIR)$(INCLUDEDIR)/mandoc.h rm -f $(DESTDIR)$(INCLUDEDIR)/mandoc_aux.h rm -f $(DESTDIR)$(INCLUDEDIR)/mdoc.h rm -f $(DESTDIR)$(INCLUDEDIR)/roff.h [ ! -e $(DESTDIR)$(INCLUDEDIR) ] || rmdir $(DESTDIR)$(INCLUDEDIR) regress: all cd regress && ./regress.pl regress-clean: cd regress && ./regress.pl . clean Makefile.local config.h: configure $(TESTSRCS) @echo "$@ is out of date; please run ./configure" @exit 1 libmandoc.a: $(COMPAT_OBJS) $(LIBMANDOC_OBJS) ar rs $@ $(COMPAT_OBJS) $(LIBMANDOC_OBJS) mandoc: $(MAIN_OBJS) libmandoc.a $(CC) -o $@ $(LDFLAGS) $(MAIN_OBJS) libmandoc.a $(LDADD) man.cgi: $(CGI_OBJS) libmandoc.a $(CC) $(STATIC) -o $@ $(LDFLAGS) $(CGI_OBJS) libmandoc.a $(LDADD) mandocd: $(MANDOCD_OBJS) libmandoc.a $(CC) -o $@ $(LDFLAGS) $(MANDOCD_OBJS) libmandoc.a $(LDADD) catman: catman.o libmandoc.a $(CC) -o $@ $(LDFLAGS) catman.o libmandoc.a $(LDADD) demandoc: $(DEMANDOC_OBJS) libmandoc.a $(CC) -o $@ $(LDFLAGS) $(DEMANDOC_OBJS) libmandoc.a $(LDADD) soelim: $(SOELIM_OBJS) $(CC) -o $@ $(LDFLAGS) $(SOELIM_OBJS) # --- maintainer targets --- www-install: www $(INSTALL_DATA) $(WWW_MANS) mandoc.css $(HTDOCDIR) depend: config.h mkdep -f Makefile.depend $(CFLAGS) $(SRCS) perl -e 'undef $$/; $$_ = <>; s|/usr/include/\S+||g; \ s|\\\n||g; s| +| |g; s| $$||mg; print;' \ Makefile.depend > Makefile.tmp mv Makefile.tmp Makefile.depend regress-distclean: @find regress \ -name '.#*' -o \ -name '*.orig' -o \ -name '*.rej' -o \ -name '*.core' \ -exec rm -i {} \; regress-distcheck: @find regress ! -type d ! -type f @find regress -type f \ ! -path '*/CVS/*' \ ! -name Makefile \ ! -name Makefile.inc \ ! -name '*.in' \ ! -name '*.out_ascii' \ ! -name '*.out_utf8' \ ! -name '*.out_html' \ ! -name '*.out_markdown' \ ! -name '*.out_lint' \ ! -path regress/regress.pl \ ! -path regress/regress.pl.1 dist: mandoc.sha256 mandoc.sha256: mandoc.tar.gz sha256 mandoc.tar.gz > $@ mandoc.tar.gz: $(DISTFILES) ls regress/*/*/*.mandoc_* && exit 1 || true mkdir -p .dist/mandoc-$(VERSION)/ $(INSTALL) -m 0644 $(DISTFILES) .dist/mandoc-$(VERSION) cp -pR regress .dist/mandoc-$(VERSION) find .dist/mandoc-$(VERSION)/regress \ -type d -name CVS -print0 | xargs -0 rm -rf chmod 755 .dist/mandoc-$(VERSION)/configure ( cd .dist/ && tar zcf ../$@ mandoc-$(VERSION) ) rm -rf .dist/ # === SUFFIX RULES ===================================================== .SUFFIXES: .1 .3 .5 .7 .8 .h .SUFFIXES: .1.html .3.html .5.html .7.html .8.html .h.html .h.h.html: highlight -I $< > $@ .1.1.html .3.3.html .5.5.html .7.7.html .8.8.html: mandoc ./mandoc -Thtml -Wall,stop \ -Ostyle=mandoc.css,man=%N.%S.html,includes=%I.html $< > $@ Index: head/contrib/mdocml/NEWS =================================================================== --- head/contrib/mdocml/NEWS (revision 324357) +++ head/contrib/mdocml/NEWS (revision 324358) @@ -1,978 +1,993 @@ $Id: NEWS,v 1.26 2017/07/28 14:57:56 schwarze Exp $ This file lists the most important changes in the mandoc.bsd.lv distribution. +Changes in version 1.14.3, released on August 5, 2017 + + --- BUG FIXES --- + * man(7): Do not crash with out-of-bounds read access to a constant + array if .sp or a blank line immediately precedes .SS or .SH. + * mdoc(7): Do not crash with out-of-bounds read access to a constant + array if .sp or a blank line precede the first .Sh macro. + * tbl(7): Ignore explicitly specified negative column widths rather than + wrapping around to huge numbers and risking memory exhaustion. + * man(1): No longer use names that only occur in the SYNOPSIS section. + Gets rid of some surprising behaviour and bogus warnings. + --- THANKS TO --- + Leah Neukirchen (Void Linux), Markus Waldeck (Debian), + Peter Bui (nd.edu), and Yuri Pankov (illumos) for bug reports. + Changes in version 1.14.2, released on July 28, 2017 --- MAJOR NEW FEATURES --- * New mdoc(7) -Tmarkdown output mode. * For -Thtml, implement internal hyperlinks pointing to authoritative definitions of various syntax elements, similar to the ctags(1)-like less(1) :t internal searching in terminal mode. * Provide a superset of the functionality of the former mdoclint(1) utility and a new -Wstyle message level with several new messages, including validity checking of .Xr cross references. * tbl(7): Implement automatic line breaking inside individual table cells, and several other formatting improvements. * eqn(7): Complete rewrite of the lexer, resulting in several bugfixes. * Continue parser unification, in particular allowing generation of syntax tree nodes on the roff(7) level, allowing implementation of many additional roff requests. --- REMOVED FUNCTIONALITY --- * Delete the manpage(1) utility. It was never enabled in any release. * Delete the -Txhtml command line option. It has been an obsolete alias for the -Thtml output mode for more than two years. --- MINOR NEW FEATURES --- * -Tlint now puts parser messages on stdout instead of stderr, making commands like "man -l -Tlint *.1" useful. * mdoc(7): Various .Lk formatting improvements. * mdoc(7) -Thtml: Better CSS for .Bl lists. * man(7): Implement the .MT/.ME block macro (mailto hyperlink). * man(7): Implement the .DT macro (restore default tab positions). * man(7): Improved support for manuals generated with reStructuredText by partial support for the \n[an-margin] number register. * man(7) -Thtml: Support deep linking to .SH and .SS headers. * tbl(7): Implement the "allbox" table option. * tbl(7): Implement the column spacing and the 'w' (minimum column width) layout modifiers. * tbl(7): Significant improvements of the manual page. * eqn(7): Much improved font selection, including recognition of well-known function names, and a few other formatting improvements. * eqn(7) -Thtml: Use and in addition to . * roff(7): Implement the .ce (centering), .mc (margin character), .rj (right justify), .ta (define tab stops), .ti (temporary indent), .als (macro alias), .ec and .eo (escape character control), .po (page offset), and .rn (macro rename) requests. * roff(7) .am: Implement appending to mdoc(7) and man(7) macros. * roff(7): implement the \h (horizontol motion), \l (horizontal line drawing), and \p (break output line) escape sequences, and also several additional character escape sequences. * roff(7): Implement the 'd' conditional (macro or string defined). * man.cgi(8) now uses pledge(2), too. * regress.pl(1): simpler user interface, better summary output, simpler code, and no more recursion. --- THANKS TO --- * Anthony Bentley (OpenBSD) for the implementation of .MT/.ME, reports of many bugs and missing features, and suggestions for a number of feature and documentation improvements. * Sebastien Marie (OpenBSD) for two source code patches and for some useful discussions. * Florian Obser (OpenBSD) for a bugfix patch and a bug report. * Jonathan Gray (OpenBSD) for several bug reports from afl(1) and several more from static analysis tools. * Theo Buehler (OpenBSD) for several bug reports, most from afl(1). * Jason McIntyre (OpenBSD) for many useful discussions about a wide variety of topics, lots of continuous testing, a number of bug reports, and some suggestions for messages and documentation. * Thomas Klausner (NetBSD) for lots of help while migrating mdoclint(1) functionality to mandoc -Tlint, for suggesting several useful new messages, and for release testing. * Reyk Floeter (OpenBSD) and Vsevolod Stakhov (FreeBSD) for suggesting a markdown output mode. * Thomas Guettler for suggesting -Thtml internal hyperlinks. * Yuri Pankov (Illumos) for inspiring new warning messages and for extensive release testing. * Anton Lindqvist and TJ Townsend (both OpenBSD) and Jan Stary for multiple bug reports. * Leah Neukirchen (Void Linux) for bug reports and release testing. * Michael Stapelberg (Debian) for suggesting feature improvements and for release testing. * Martin Natano and Theo de Raadt (both OpenBSD), Andreas Voegele, Gabriel Guzman, Gonzalo Tornaria, Markus Waldeck, and Raf Czlonka for bug reports. * Antoine Jacoutot (OpenBSD) and Steffen Nurpmeso for suggesting feature improvements. * Dag-Erling Smoergrav (FreeBSD) for inspiring new warning messages. * Ted Unangst and Marc Espie (OpenBSD) for providing useful ideas. * Svyatoslav Mishyn (Crux Linux) for release testing. * Carsten Kunze (Heirloom roff) for help keeping mandoc and groff compatible and for committing some of my patches to groff. Changes in version 1.14.1, released on February 21, 2017 --- MAJOR NEW FEATURES --- * apropos(1): Reimplement complete semantic search functionality without the dependency on SQLite3, using only POSIX APIs. This comes with a completely new mandoc.db(5) file format. * man(1): Support more than one tag entry for the same search term, plus some minor improvements to the less(1) :t support. * -Thtml: Use real macro names for CSS classes. Systematic cleanup of and many improvements to mandoc.css. * -Thtml: Produce human readable HTML code by using indentation and better line breaks. Improve various HTML elements, and trim several useless ones. * New catman(8) utility, still somewhat experimental. * Now includes a portable version of the OpenBSD mandoc regression suite, see regress/regress.pl.1 for details. --- REMOVED FUNCTIONALITY --- * Operating systems that don't provide mmap(3) are no longer supported. * Drop support for manpath(1). Even if your system has manpath(1), it is simpler to use MANPATH_DEFAULT in configure.local for operating system defaults, man.conf(5) for machine-specific modifications, and ${MANPATH}, -m, and -M for user preferences than to bother with the complexity of manpath(1). * makewhatis(8) -p: No longer warn about missing MLINKS since these are no longer needed for anything. --- MINOR NEW FEATURES --- * mdoc(7): Warn about invalid punctuation and content below NAME. * mdoc(7): Warn about .Xr lacking the second argument (section). * mdoc(7): Warn about violations of the rule "new sentence, new line". * roff(7): Warn about trailing whitespace at the end of comments. * mdoc(7): Improve rendering of double quotes. * mdoc(7): Always do text production in the validator, never in the formatters. Cleaner, simpler, shorter, helps NetBSD apropos(1) and also makes -Ttree output more useful. * -Ttree: Show metadata and some additional node flags. New -Onoval output option to show the unvalidated tree. --- RELIABILITY BUGFIXES --- * man(1): Make "man -l" work with standard input from a pipe or file, as long as standard output is a terminal. * man(7): Fix out of bounds read access if a text node immediately preceded the first .SH header. * mdoc(7): Fix out of bounds read access for .Bl without a type but with a width. * mdoc(7): Fix out of bounds read access for .Bl -column starting with a tab character instead of a child .It macro. * mdoc(7): Fix syntax tree corruption leading to segfaults caused by stray block end macros in nested blocks of mismatching type. * man(1): Fix NULL dereference when the first of multiple pages shown was preformatted. * mdoc(7): Fix syntax tree corruption leading to NULL dereference caused by partial implicit macros inside .Bl -column table cells. * mdoc(7): Fix syntax tree corruption leading to NULL dereference for macro sequences like .Bl .Bl .It Bo .El .It. * mdoc(7): Fix syntax tree corruption leading to NULL dereference caused by .Ta following a nested .Bl -column breaking another block. * mdoc(7): Fix syntax tree corruption sometimes leading to NULL dereference caused by indirectly broken .Nd or .Nm blocks. * mdoc(7) -Thtml: Fix a NULL dereference for .Bl -column with 0 columns. * mdoc(7): Fix NULL dereference in some specific cases of a block-end macro calling another block-end macro. * mdoc(7): Fix NULL dereference if the only child of the head of the first .Sh was an empty in-line macro. * eqn(7): Fix NULL dereference in the terminal formatter for empty matrices and empty square roots. * mdoc(7): Fix an assertion failure for a .Bd without a type that breaks another block. * mdoc(7): Fix an assertion failure that happened for some .Bl -column lists containing a column width of "-4n", "-3n", or "-2n". * mdoc(7): Fix an assertion failure caused by .Bl -column without .It but containing eqn(7) or tbl(7) code. * roff(7): Fix an assertion failure caused by \z\[u00FF] with -Tps/-Tpdf. * roff(7): Fix an assertion failures caused by whitespace inside \o'' (overstrike) sequences. * -Thtml: Fix an assertion failure caused by -Oman or -Oincludes of excessive length. --- PORTABILITY IMPROVEMENTS --- * man(1): Do not mix stdio narrow and wide stream orientation on stdout, which could cause output corruption on glibc. * mandoc(1): Autodetect a suitable locale for -Tutf8 mode. * ./configure: Autodetect whether PATH_MAX and O_DIRECTORY are defined. * ./configure: Autodetect if nanosleep(3) needs -lrt. * ./configure: Provide an ${LN} configuration variable. * ./configure: Put compiler arguments that may contain -l at the end. --- MINOR BUGFIXES --- * mdoc(7): Fix SYNOPSIS output if the first child of .Nm is a macro. * mdoc(7) -Thtml: Improve formatting of .Bl -tag with short tags. * man(7) -Thtml: Preserve whitespace in .nf (nofill) mode. * mandoc(1): Error out on invalid output options on the command line. --- STRUCTURAL CHANGES, no functional change --- * Redesign part of the mandoc_html(3) interfaces, making them much easier to use and reducing the amount of code by a few hundred lines. --- THANKS TO --- * Michael Stapelberg (Debian) for designing the new mandocd(8) and parts of the new catman(8), for release testing, and for a number of patches and bug reports. * Baptiste Daroussin (FreeBSD) for profiling the new makewhatis(8) implementation and suggesting an algorithmic improvement which more than doubled performance, and for a few bug reports. * Ed Maste (FreeBSD) for an important patch improving reproducibility of builds in makewhatis(8), and for a few bug reports. * Theo Buehler (OpenBSD) for almost twenty important bug reports, most of them found by systematic afl(1) fuzzing. * Benny Lofgren, David Dahlberg, and in particular Vadim Zhukov for crucial help in getting .Bl -tag CSS formatting fixed. * Svyatoslav Mishyn (Crux Linux) for an initial version of the patch to autodetect a suitable locale for -Tutf8 mode and for release testing. * Jason McIntyre (OpenBSD) for multiple useful discussions and a number of bug reports. * Sevan Janiyan (NetBSD) for extensive release testing and multiple bug reports. * Thomas Klausner and Christos Zoulas (NetBSD), Yuri Pankov (illumos), and Leah Neukirchen (Void Linux) for release testing and bug reports. * Ulrich Spoerlein (FreeBSD) for release testing. * Alexander Bluhm, Andrew Fresh, Antoine Jacoutot, Antony Bentley, Christian Weisgerber, Jonathan Gray, Marc Espie, Martijn van Duren, Stuart Henderson, Ted Unangst, Theo de Raadt (OpenBSD), Abhinav Upadhyay, Kamil Rytarowski (NetBSD), Aaron M. Ucko, Bdale Garbee, Reiner Herrmann, Shane Kerr (Debian), Daniel Sabogal (Alpine Linux), Carsten Kunze (Heirloom roff), Kristaps Dzonsons (bsd.lv), Anton Lindqvist, Jan Stary, Jeremy A. Mates, Mark Patruck, Pavan Maddamsetti, Sean Levy , and Tiago Silva for bug reports. * Brent Cook, Marc Espie, Philip Guenther, Todd Miller (OpenBSD) and Markus Waldeck for useful discussions. * And as usual, OpenCSW for providing me with a Solaris 9/10/11 testing environment. Changes in version 1.13.4, released on July 14, 2016 --- MAJOR NEW FEATURES --- * man.conf(5): Design and implement a simpler configuration file format. * man(1): Leverage less(1) -T and :t in a way resembling ctags(1) to jump to the definitions of various terms inside manual pages. * soelim(1): New implementation by Baptiste Daroussin. * privilege limitation: Use OpenBSD pledge(2) or OS X sandbox_init(3) when available. * man.cgi(8): Support short URIs like http://man.openbsd.org/mdoc . * mandoc.css: Use one unified stylesheet rather than three different ones. --- MAJOR FUNCTIONALLY RELEVANT BUGFIXES --- * mdoc(7): Fix multiple aspects of SYNOPSIS .Nm formatting. * man(1): Fix process group handling, avoiding unclean shutdowns. --- PORTABILITY IMPROVEMENTS --- * Correctly use the ohash(3) compatibility implementation even when building without SQLite support. * Add compat glue for building on Solaris 9 and 10. * Let ./configure select a supported RE syntax for word boundaries. * Support LDFLAGS, to be used for example for hardening options. * Avoid mixing putchar(3) and putwchar(3) on the same file descriptor, it resulted in output corruption on some platforms. * Avoid reusing va_lists, use va_copy(3) for better portability. * Do not hardcode the path to the more(1) program. --- MINOR NEW FEATURES --- * roff(7): Implement \n(.$ (number of macro arguments). * roff(7): Fully implement \z (do not advance cursor). * roff(7): Implement the `r' conditional (register exists). * roff(7): Implement \\$* (interpolate all arguments). * roff(7): Parse and ignore \, and \/ (italic corrections). * When there is no -m, no -M, no MANPATH and no /etc/man.conf, fall back to /usr/share/man:/usr/X11R6/man:/usr/local/man. * man(1): Give manuals in purely numerical sections priority over manuals of the same name in sections with an alphabetical suffix. * man.cgi(8): Support "header.html" and "footer.html". * man.cgi(8): Set the "autofocus" attribute on the query text box. * man.cgi(8): Simplify the search form, drop two useless buttons. * man.cgi(8): Delete the pseudo-manpath "mandoc", assume that apropos(1) and man.cgi(8) are installed in the default manpath. --- RELIABILITY BUGFIXES --- * mdoc(7): Avoid a use after free and an assertion failure when nodes are deleted during validation. * mdoc(7): Avoid a NULL pointer access when .Bd has no arguments. * mdoc(7): Avoid a NULL pointer access triggered by mismatching end macros. * mdoc(7): Avoid an assertion when .Fo has no argument. * mdoc(7): Avoid an assertion when .Ta occurs in .Bl -column. * mdoc(7): Avoid an assertion when a body gets broken and has a tail. * roff(7): Avoid an assertion caused by blanks inside \o. * roff(7): Make .so links to gziped manuals work without mandoc.db(5). * tbl(7): Avoid a use after free when the last line of a layout is empty. * eqn(7): Avoid an infinite loop caused by recursive "define". * makewhatis(8): Avoid a segfault caused by unusual directory structures. * Fix handling of leading, trailing, and double colons in MANPATH and -m. --- MINOR BUGFIXES --- * mdoc(7): Put arguments to end macros of broken partial explicit blocks inside the breaking block. * mdoc(7): Let .Dv force normal font. * mdoc(7): Make trailing whitespace significant in .Bl -tag widths. * mdoc(7): Fix macro interpretation around tabs in .Bl -column. * man(7): Use the default width for .RS without arguments. * man(7): On a new RS nesting level, the saved width starts from the default width, not from the saved width of the previous level. * man(7): Allow .PD in next-line scope. * man(7): Improve handling of empty .HP. * man(7): Improve formatting of .br and .sp inside .HP. * man(7): Do not mistreat empty arguments to font alternating macros as vertical spacing requests. * man(7): Allow fill mode changes in tagged paragraph next-line scope. * man(7): Fix minor bugs in block rewinding and simplify the related code. * man(7): Add missing line breaks before subsection headers. * man(7): Give section and subsection headers hanging indentation. * man(7): Make trailing whitespace significant in .TP widths. * roff(7): Don't allow breaking the output line after hyphens that immediately follow escape sequences. * roff(7): Ignore blank characters at the beginning of conditional blocks. * roff(7): Escape breakable hyphens only after handling input line traps. * roff(7): Reject \[uD800] to \[uDFFF] (surrogates) in the parser. * tbl(7): Allow more than one data field after T} on the same input line. * terminal output: Apply bold and italic to non-ASCII Unicode codepoints. * terminal output: Improve rounding rules for horizontal scaling widths. * HTML output: Render ASCII_NBRSP as " ", not "-". * man(1): Do not match the first part of a name if it continues with a dot. * man(1): Keep working even if the current directory is unusable. * man(1): Better error message when $PAGER is invalid. * makewhatis(8): Improve handling of .Va and .Vt macros. * apropos(1): Print "nothing appropriate" to stderr when appropriate. * apropos(1): Abort with a useful error message when elementary database operations like preparing queries or binding variables fail. --- STRUCTURAL CHANGES, no functional change --- * mdoc(7) and man(7): Unified data structures struct roff_node etc. * mdoc(7) and man(7): Unified node handling library in roff.c. * mdoc(7) and man(7): Seperate validation phase from parsing. * roff(7): Major character table cleanup. * Link with libz rather than forking gunzip(1). --- THANKS TO --- * Baptiste Daroussin (FreeBSD) for the new soelim(1) and for release testing. * Anthony Bentley (OpenBSD) for unifying mandoc.css, two nice patches for man.cgi(8), some documentation patches, some bug reports, and various useful discussions. * Todd Miller (OpenBSD) for lots of help with process group and signal handling, a few patches, some bug reports and some useful discussions. * Jonathan Gray (OpenBSD) for yet more testing with afl(1) again resulting in more than half a dozen important bug reports. * Svyatoslav Mishyn (Crux Linux) for some patches, several bug reports, and extensive release testing. * Leah Neukirchen (Void Linux) for a number of compatibility patches and suggestions and several bug reports. * Christos Zoulas (NetBSD) for a bug fix patch and some useful suggestions for cleanup. * Florian Obser (OpenBSD) for a bugfix patch and some bug reports. * Sevan Janiyan for help with Solaris compatibility and release testing on many platforms. * Jan Holzhueter and OpenCSW in general for help with Solaris compatibility, and for providing me with a Solaris 9/10/11 testing environment. * Michael McConville (OpenBSD) for some simple cleanup patches. * Thomas Klausner (NetBSD) for some bug reports and release testing. * Christian Weisgerber, Dmitrij Czarkoff, Igor Sobrado, Ken Westerback, Marc Espie, Mike Belopuhov, Rafael Neves, Ted Unangst, Tim van der Molen, Theo Buehler, Theo de Raadt (OpenBSD), Kurt Jaeger, Dag Erling Smoergrav (FreeBSD), Joerg Sonnenberger (NetBSD), Carsten Kunze (Heirloom troff), Daniel Levai, Fabian Raetz, Jan Stary, Jean-Yves Migeon, Lorenzo Beretta, Markus Waldeck, Maxim Belooussov, Michael Reed, Peter Bray, and Serguey Parkhomovsky for bug reports and feature suggestions. * Alexander Hall, Andrew Fresh, Antoine Jacoutot, Doug Hogan, Jason McIntyre, Jasper Lievisse Adriaanse, Kent Spillner, Nicholas Marriott, Peter Hessler, Sebastien Marie, Stefan Sperling, and Theo de Raadt (OpenBSD) for helpful discussions and feedback. Changes in version 1.13.3, released on March 13, 2015 --- MAJOR NEW FEATURES --- * When a manual is missing from an outdated database, let man(1) show it anyway, using a KISS file system lookup as a fallback. * Use this to always provide man(1), even without database support. * Fatal errors no longer exist. If a file can be opened, mandoc will produce some output; at worst, the output may be almost empty. * New -Wunsupp message level. --- POTENTIONALLY SECURITY RELEVANT BUGFIXES --- * Fix a potential write buffer overrun on incomplete string conditionals. http://mandoc.bsd.lv/cgi-bin/cvsweb/roff.c#rev1.241 * Fix a potential write buffer overrun on backslash at EOF in a conditional. http://mandoc.bsd.lv/cgi-bin/cvsweb/roff.c#rev1.247 * Fix a use after free sometimes hit when validation deletes a block. http://mandoc.bsd.lv/cgi-bin/cvsweb/mdoc_macro.c#rev1.180 --- MAJOR FUNCTIONALLY RELEVANT BUGFIXES --- * Let man(1) show manuals for the current architecture by default, and support the MACHINE environment variable. * Fix the man(1) and apropos(1) -m option, it didn't work at all. * Do not spawn a pager when there is no output. * In makewhatis(8), fix detection of hardlinked manuals on platforms having padding in struct inodev (typically 64bit platforms). --- PORTABILITY IMPROVEMENTS --- * Ignore O_CLOEXEC when the operating system doesn't provide it. * Avoid forward reference to enum type which violates ISO C99. * Support homebrew-style linking on Mac OS X. --- MINOR NEW FEATURES --- * lookup: Accept digit+letter and "n" as section names in man(1), and consistently handle digit+letter in file name extensions. * lookup: Speed up -s/-S by using the "mlinks" rather than the "keys" table. * output: Insert horizontal lines between formatted manual pages. * input: New stricter and more resilient UTF-8 parser. * mdoc(7): Refactor block rewinding for simpler and more robust parsing. * man(7): Use the -Ios option when .TH has less than four arguments. * tbl(7): Implement the "center" option. * tbl(7): New option and format parsers, improved in many respects. * roff(7): Basic implementation of the \o escape sequence (overstrike), and improved rendering of overstrikes in PostScript and PDF output. * Message improvements, in particular for, but not restricted to, eqn(7), tbl(7), and wrong numbers of arguments in mdoc(7) and man(7), in various cases also improving output generated by invalid input. * Delete the -V option. It serves no purpose but keeps confusing people. * gmdiff: Minimal support for Heirloom roff. --- RELIABILITY BUGFIXES --- * tbl(7): Fix a read buffer overrun on 'f' at EOL in a layout. * roff(7): Fix a read buffer overrun on incomplete numerical conditions. * mdoc(7): Fix a NULL pointer access on .Nd followed by an explicit block. * mdoc(7): Fix a NULL pointer access on .It Xo without .Xc. * mdoc(7): Fix a NULL pointer access on .Eo without a tail. * mdoc(7): Fix a NULL pointer access in the validation of empty .St macros. * man(7)/tbl(7): Fix a NULL pointer access on .TS right after .TP. * tbl(7): Fix a NULL pointer access on layout lines without any cells. * eqn(7): Fix NULL pointer accesses in the terminal formatter. * roff(7): Fix a NULL pointer access on trailing \s-/\s+ without an argument. * gz: Fix a potential NULL pointer access after waitpid() failure. * roff(7): Don't let the modulo operator divide by zero. * input: Fix an assertion failure on certain invalid UTF-8 input. * terminal output: Allow arbitrary depth of the font stack (assertion fix). * mdoc(7): Fix assertion failures and endless loops on invalid block closing. * mdoc(7): Fix an assertion failure on .Bl .Sm not followed by .It. * mdoc(7): Fix an assertion failure on .Bl -column ... .El .Ta. * tbl(7): Fix assertion failures by macros inside table data, but do not throw away the macro arguments. * Prevent certain kinds of unreasonable input from producing excessive output, in one case caused by unsigned integer underflow. * Fix a potential memory leak in makewhatis(8) on very long filenames. --- MINOR BUGFIXES --- * mdoc(7): Fix parsing of badly nested blocks with multiple identical blocks. * mdoc(7): Support negative indentations for displays and lists. * mdoc(7): Don't mistreat negative .sp arguments as large positive ones. * mdoc(7): Some spacing fixes for .Eo/.Ec. * man(7): Support negative horizontal widths. * man(7): Do not print out invalid .IP arguments. * man(7): Correctly handle scaling units after .PD. * man(7): Support .RE with an argument. * man(7): Fix restoring indentation after .RS with large negative arguments. * tbl(7): Prevent tables from breaking the filling of preceding text. * tbl(7): Fix vertical spacing at the beginning of tables. * tbl(7): Parser and formatter fixes for line drawing and font modifiers. * tbl(7): Correct handling of blank data lines. * eqn(7): Add sometimes missing whitespace before equation output. * roff(7): Fix vertical scaling, most of it was wrong. * roff(7): Slightly improve \w width measurements. * roff(7): Accept the historic aliases \s10 to \s39 for \s(10 to \s(39. * roff(7): Correctly escape quotes when expanding macro arguments. * roff(7): Correctly handle scaling units in numerical expressions, and some other improvements to the parsing of numerical expressions. * roff(7): Three minor fixes with respect to evaluation of conditionals. * roff(7): Let .it accept numerical expressions, not just constants. * mandoc_char(7): Correct some character names and renderings. * If earlier files set a non-zero exit status, never reset it to zero. --- THANKS TO --- * Jonathan Gray (OpenBSD) for yet more testing with afl (the American Fuzzy Lop security fuzzer), again resulting in many bug reports. * Theo de Raadt (OpenBSD) for suggesting the main new feature (man(1) file system lookup) and for reporting an important bug (pager without output). * Theo Buehler for an important bug report (-s/-S slowness) and for proposing a nice new feature (lines between pages). * Jason McIntyre for an important bug report (hardlink detection) and multiple documentation patches. * Pascal Stumpf (OpenBSD) and Alessandro de Laurenzis for important bug reports (architecture and man -m, respectively). * Thomas Klausner (NetBSD) for proposing a new feature (man(7) -Ios), a bug report, and release testing. * Anthony Bentley, Daniel Dickman, Ted Unangst (OpenBSD) and Kristaps Dzonsons (bsd.lv) for source code patches and bug reports. * Christian Weisgerber (OpenBSD) for more than half a dozen bug reports. * Carsten Kunze (Heirloom troff) for bug reports and release testing. * Antoine Jacoutot (OpenBSD) for release testing. * Alexis Hildebrandt (Homebrew), Baptiste Daroussin (FreeBSD), Jonathan Perkin (SmartOS), Pedro Giffuni (FreeBSD), Svyatoslav Mishyn (Crux Linux), Ulrich Spoerlein (FreeBSD), Jan Stary, Patrick Keshishian, Sebastien Marie, and Steffen Nurpmeso for bug reports. Changes in version 1.13.2, released on December 13, 2014 --- MAJOR NEW FEATURES --- * Include an implementation of man(1), the manual page viewer. * Unified set of command line option, each one supported by all command names, including new options -a (format all), -c (no pager), -h (synopsis only), and -w (list filenames). * Support the MANPAGER and PAGER environment variables. * Support gzip'ed manuals by the whole toolset, even as .so targets. * Support UTF-8 and Latin-1 input by the whole toolset, delete preconv(1). * Switch the default output mode from -Tascii to -Tlocale. * Improve -Tascii output for Unicode escape sequences. * Let the -Thtml output mode produce polyglot HTML5. * Many improvements for eqn(7), in particular in-line equations, MathML output in -Thtml mode, and much improved terminal formatting. --- PORTABILITY IMPROVEMENTS --- * Change the build sequence to the usual ./configure; make; make install. * Support ./configure.local for build customizations. * Autodetect wchar, sqlite3, and manpath support. * Provide a fallback version of fts(3) for systems lacking it. * Support choosing alternative binary and manual names. --- MINOR NEW FEATURES --- * Rudimentary implementation of the e, x, and z tbl(7) layout modifiers to equalize, maximize, and ignore the width of columns. * Implement font modifiers in tbl(7) layouts. * Allow comma-separated options in the tbl(7) options line. * Parse and ignore the .pl (page length) roff(7) request. * Implement .An -[no]split for the mdoc(7) -Thtml output mode. * Support bold italic font in PostScript and PDF output. * Warn about commas in function arguments and parentheses in function names. * Warn about botched .Xr ordering and punctuation below SEE ALSO. * Warn about AUTHORS sections without .An macros. * Warn about attempts to call non-callable macros. * New developer documentation manual page mandoc_headers(3). --- BUGFIXES --- * Fix read buffer overrun sometimes triggered by trailing whitespace. * Fix read buffer overrun triggered by certain invalid \H sequences. * Fix NULL pointer access triggered by .Bl without any arguments. * Fix NULL pointer access triggered by .It Nm Fo without .Fc. * Fix NULL pointer access triggered by .Sh Xo .Sh without .Xc. * Fix NULL pointer access triggered by missing .Nm. * Fix an assertion triggered by .It right after .El. * Fix an assertion triggered by .Ec without preceding .Eo. * Fix an assertion triggered by .Sm or .Db with multiple arguments. * Fix assertion failures triggered by very large width arguments. * Fix a division by zero in the roff(7) parser. * Prevent negative arguments to .ll from causing integer underflow. * Correctly autodetect source format even when .Dd is preceded by .ll. * Multiple fixes with respect to .Bd and .Bl -offset and -width. * Many bugfixes with respect to scaling units. * Multiple fixes with respect to delimiter handling by in-line macros. * Multiple fixes with respect to .Pf. * Make \c work properly in no-fill mode. * Stricter syntax checking of Unicode character names. --- THANKS TO --- * Kristaps Dzonsons for rewriting the eqn(7) parser, implementing HTML5 and MathML output, and various other code contributions. * Jonathan Gray (OpenBSD) for extensive testing with afl (the American Fuzzy Lop security fuzzer) resulting in many bug reports. * Anthony Bentley (OpenBSD), Baptiste Daroussin (FreeBSD), Daniel Dickman, Doug Hogan, Jason McIntyre, Theo de Raadt (OpenBSD), and Martin Natano for source code patches. * Carsten Kunze (Heirloom troff), Daniel Levai (Slackware), Garrett D'Amore (illumos), Giovanni Becchis, Matthew Dempsky, Stuart Henderson, Ted Unangst, Todd Miller (OpenBSD), Thomas Klausner (NetBSD), Ulrich Spoerlein (FreeBSD), Justin Haynes, Marcus Merighi, Sebastien Marie, Steffen Nurpmeso and Theo Buehler for bug reports. Changes in version 1.13.1, released on August 10, 2014 --- MAJOR NEW FEATURES --- * A complete apropos(1)/makewhatis(8)/man.cgi(8) suite based on SQLite3 is now included. * The roff(7) parser now provides an almost complete implementation of numerical expressions. * Warning and error messages have been improved in many ways. Almost all fatal errors were downgraded to normal errors and some even to warnings. Almost all messages now mention the macro where the issue is detected and many indicate the workaround employed. The mandoc(1) manual now includes a list explaining all messages. --- MINOR NEW FEATURES --- * The roff(7) parser now supports the .ami (append to macro with indirectly specified name), .as (append to user-defined string), .dei (define macro with indirectly specified name), .ll (line length), and .rr (remove register) requests. * The roff(7) parser now supports string comparison and numerical conditionals in the .if and .ie requests. * The roff parser now fully supports the \B (validate numerical expression) and partially supports the \w (measure text width) escape sequences. * The terminal formatter now supports the \: (optional line break) escape sequence. * The roff parser now supports expansion of user-defined strings involving indirect references. * The roff(7) parser now handles some pre-defined read-only number registers that occur in the pod2man(1) preamble. * For backward compatibility, the mdoc(7) parser and formatters now support the obsolete macros .En, .Es, .Fr, and .Ot. * The mdoc(7) formatter non partially supports .Bd -centered. * tbl(7) now handles leading and trailing vertical lines. * The build system now provides fallback versions of strcasestr(3) and strsep(3) for systems lacking them. * The mdoc(7) manual now explains how various standards supported by the .St macro are related to each other. --- BUGFIXES --- * In the roff(7) parser, several bugs were fixed with respect to closing conditional blocks on macro lines. * Parsing of roff(7) identifiers and escape sequences was improved in multiple respects. * In the mdoc(7) parser, the handling of defective document prologues was improved in multiple ways. * The mdoc(7) parser no longer skips content before the first section header, and it no longer deletes non-.% content from .Rs blocks. * In the mdoc(7) parser, a crash was fixed related to weird .Sh headers. * In the mdoc(7) parser, handling of .Sm with missing or invalid arguments was corrected. * In the mdoc(7) parser, trailing punctuation at the end of partial implicit macros no longer triggers end-of-sentence spacing. * In the terminal formatter, two crashes were fixed: one triggered by excessive indentation and another by excessively long .Nm arguments. * In the terminal formatter, a floating point rounding bug was fixed that sometimes caused an off-by-one error in indentation. * In the UTF-8 formatter, rendering of accents, breakable hyphens, and non-breakable spaces was corrected. * In the HTML formatter, encoding of special characters was corrected in multiple respects. * In the mdoc(7) formatter, rendering of .Ex and .Rv was improved for various edge cases. * In the mdoc(7) formatter, handling of empty .Bl -inset item heads was improved. * In the man(7) formatter, some bugs were fixed with respect to same-line detection in the context of .TP and .nf macros, and the indentation of .IP and .TP blocks was improved. * The mandoc(3) library no longer prints to stderr. --- THANKS TO --- Abhinav Upadhyay (NetBSD), Andreas Voegele, Anthony Bentley (OpenBSD), Christian Weisgerber (OpenBSD), Havard Eidnes (NetBSD), Jan Stary, Jason McIntyre (OpenBSD), Jeremie Courreges-Anglas (OpenBSD), Joerg Sonnenberger (NetBSD), Juan Francisco Cantero Hurtado (OpenBSD), Marc Espie (OpenBSD), Matthias Scheler (NetBSD), Pascal Stumpf (OpenBSD), Paul Onyschuk (Alpine Linux), Sebastien Marie, Steffen Nurpmeso, Stuart Henderson (OpenBSD), Ted Unangst (OpenBSD), Theo de Raadt (OpenBSD), Thomas Klausner (NetBSD), and Ulrich Spoerlein (FreeBSD) for reporting bugs and missing features. Changes in version 1.12.3, released on December 31, 2013 * In the mdoc(7) SYNOPSIS, line breaks and hanging indentation now work correctly for .Fo/.Fa/.Fc and .Fn blocks. Thanks to Franco Fichtner for doing part of the work. * The mdoc(7) .Bk macro got some addititonal bugfixes. * In mdoc(7) macro arguments, double quotes can now be quoted by doubling them, just like in man(7). Thanks to Tsugutomo ENAMI for the patch. * At the end of man(7) macro lines, end-of-sentence spacing now works. Thanks to Franco Fichtner for the patch. * For backward compatibility, the man(7) parser now supports the man-ext .UR/.UE (uniform resource identifier) block macros. * The man(7) parser now handles closing blocks that are not open more gracefully. * The man(7) parser now ignores blank lines right after .SH and .SS. * In the man(7) formatter, reset indentation when leaving a block, not just when entering the next one. * The roff(7) .nr request now supports incrementing and decrementing number registers and stops parsing the number right before the first non-digit character. * The roff(7) parser now supports the alternative escape sequence syntax \C'uXXXX' for Unicode characters. * The roff(7) parser now parses and ignores the .fam (font family) and .hw (hyphenation points) requests and the \d and \u escape sequences. * The roff(7) manual got a new ESCAPE SEQUENCE REFERENCE. Changes in version 1.12.2, released on Oktober 5, 2013 * The mdoc(7) to man(7) converter, to be called as mandoc -Tman, is now fully functional. * The mandoc(1) utility now supports the -Ios (default operating system) input option, and the -Tutf8 output mode now actually works. * The mandocdb(8) utility no longer truncates existing databases when starting to build new ones, but only replaces them when the build actually succeeds. * The man(7) parser now supports the PD macro (paragraph distance), and (for GNU man-ext compatibility only) EX (example block) and EE (example end). Plus several bugfixes regarding indentation, line breaks, and vertical spacing, and regarding RS following TP. * The roff(7) parser now supports the \f(BI (bold+italic) font escape, the \z (zero cursor advance) escape and the cc (change control character) and it (input line trap) requests. Plus bugfixes regarding the \t (tab) escape, nested escape sequences, and conditional requests. * In mdoc(7), several bugs were fixed related to UTF-8 output of quoting enclosures, delimiter handling, list indentation and horizontal and vertical spacing, formatting of the Lk, %U, and %C macros, plus some bugfixes related to the handling of syntax errors like badly nested font blocks, stray Ta macros outside column lists, unterminated It Xo blocks, and non-text children of Nm blocks. * In tbl(7), the width of horizontal spans and the vertical spacing around tables was corrected, and in man(7) files, a crash was fixed that was triggered by some particular unclosed T{ macros. * For mandoc developers, we now provide a tbl(3) library manual and gmdiff, a very small, very simplistic groff-versus-mandoc output comparison tool. * Provide this NEWS file. Changes in version 1.12.1, released on March 23, 2012 * Significant work on apropos(1) and mandocdb(8). These tools are now much more robust. A whatis(1) implementation is now handled as an apropos(1) mode. These tools are also able to minimally handle pre-formatted pages, that is, those already formatted by another utility such as GNU troff. * The man.cgi(7) script is also now available for wider testing. It interfaces with mandocdb(8) manuals cached by catman(8). HTML output is generated on-the-fly by libmandoc or internal methods to convert pre-formatted pages. * The mailing list archive for the discuss and tech lists are being hosted by Gmane at gmane.comp.tools.mdocml.user and gmane.comp.tools.mdocml.devel, respectively. Changes in version 1.12.0, released on October 8, 2011 * This version features a new, work-in-progress mandoc(1) output mode: -Tman. This mode allows a system maintainer to distribute man(7) media for older systems that may not natively support mdoc(7), such as old Solaris systems. * The -Ofragment option was added to mandoc(1)'s -Thtml and -Txhtml modes. * While adding features, an apropos(1) utility has been merged from the mandoc-tools sandbox. This interfaces with mandocdb(8) for semantic search of manual content. apropos(1) is different from the traditional apropos primarily in allowing keyword search (such as for functions, utilities, etc.) and regular expressions. Note that the calling syntax for apropos is likely to change as it settles down. * In documentation news, the mdoc(7) and man(7) manuals have been made considerably more readable by adding MACRO OVERVIEW sections, by moving the gory details of the LANGUAGE SYNTAX to the roff(7) manual, and by moving the very technical MACRO SYNTAX sections down to the bottom of the page. * Furthermore, for tbl(7), the -Tascii mode horizontal spacing of tables was rewritten completely. It is now compatible with groff(1), both with and without frames and rulers. * Nesting of indented blocks is now supported in man(7), and several bugs were fixed regarding indentation and alignment. * The page headers in mdoc(7) are now nicer for very long titles. Changes in version 1.11.7, released on September 2, 2011 * Added demandoc(1) utility for stripping away macros and escapes. This replaces the historical deroff(1) utility. * Also improved the mdoc(7) and man(7) manuals. Changes in version 1.11.6, released on August 16, 2011 * Handling of tr macro in roff(7) implemented. This makes Perl documentation much more readable. Hyphenation is also now enabled in man(7) format documents. Many other general improvements have been implemented. Changes in version 1.11.5, released on July 24, 2011 * Significant eqn(7) improvements. mdocml can now parse arbitrary eqn input (although few GNU extensions are accepted, nor is mixing low-level roff with eqn). See the eqn(7) manual for details. For the time being, equations are rendered as simple in-line text. The equation parser satisfies the language specified in the Second Edition User's Guide: http://www.kohala.com/start/troff/v7man/eqn/eqn2e.ps Changes in version 1.11.4, released on July 12, 2011 * Bug-fixes and clean-ups across all systems, especially in mandocdb(8) and the man(7) parser. This release was significantly assisted by participants in OpenBSD's c2k11. Thanks! Changes in version 1.11.3, released on May 26, 2011 * Introduce locale-encoding of output with the -Tlocale output option and Unicode escaped-character input. See mandoc(1) and mandoc_char(7), respectively, for details. This allows for non-ASCII characters (e.g., \[u5000]) to be rendered in the locale's encoding, if said environment supports wide-character encoding (if it does not, -Tascii is used instead). Locale support can be turned off at compile time by removing -DUSE_WCHAR in the Makefile, in which case -Tlocale is always a synonym for -Tascii. * Furthermore, multibyte-encoded documents, such as those in UTF-8, may be on-the-fly recoded into mandoc(1) input by using the newly-added preconv(1) utility. Note: in the future, this feature may be integrated into mandoc(1). Changes in version 1.11.2, released on May 12, 2011 * Corrected some installation issues in version 1.11.1. * Further migration to libmandoc. * Initial public release (this utility is very much under development) of mandocdb(8). This utility produces keyword databases of manual content, which features semantic querying of manual content. Changes in version 1.11.1, released on April 4, 2011 * The earlier libroff, libmdoc, and libman soup have been merged into a single library, libmandoc, which manages all aspects of parsing real manuals, from line-handling to tbl(7) parsing. * As usual, many general fixes and improvements have also occurred. In particular, a great deal of redundancy and superfluous code has been removed with the merging of the backend libraries. * see also the changes in 1.10.10 Changes in version 1.10.10, March 20, 2011, NOT released * Initial eqn(7) functionality is in place. For the time being, this is limited to the recognition of equation blocks; future version of mdocml will expand upon this framework. Changes in version 1.10.9, released on January 7, 2011 * Many back-end fixes have been implemented: argument handling (quoting), man(7) improvements, error/warning classes, and many more. * Initial tbl(7) functionality (see the "TS", "TE", and "T&" macros in the roff(7) manual) has been merged from tbl.bsd.lv. Output is still minimal, especially for -Thtml and -Txhtml, but manages to at least display data. This means that mandoc(1) now has built-in support for two troff preprocessors via libroff: soelim(1) and tbl(1). Changes in version 1.10.8, released on December 24, 2010 * Overhauled the -Thtml and -Txhtml output modes. They now display readable output in arbitrary browsers, including text-based ones like lynx(1). See HTML and XHTML manuals in the DOCUMENTATION section for examples. Attention: available style-sheet classes have been considerably changed! See the example.style.css file for details. Lastly, libmdoc and libman have been cleaned up and reduced in size and complexity. * see also the changes in 1.10.7 Changes in version 1.10.7, December 6, 2010, NOT released Significant improvements merged from OpenBSD downstream, including: * many new roff(7) components, * in-line implementation of troff's soelim(1), * broken-block handling, * overhauled error classifications, and * cleaned up handling of error conditions. Changes in version 1.10.6, released on September 27, 2010 * Calling conventions for mandoc(1) have changed: -W improved and -f deprecated. * Non-ASCII characters are also now uniformly discarded. * Lots of documentation improvements. * Many incremental fixes accomodating for groff's more interesting productions. * Lastly, pod2man(1) preambles are now fully accepted after some considerable roff(7) and special character support. Changes in version 1.10.5, released on July 27, 2010 * Primarily a bug-fix and polish release, but including -Tpdf support in mandoc(1) by way of "Summer of Code". Highlights: * fix "Sm" and "Bd" handling * fix end-of-sentence handling for embedded sentences * polish man(7) documentation * document all mdoc(7) macros * polish mandoc(1) -Tps output * lots of internal clean-ups in character escapes * un-break literal contexts in man(7) documents * improve -Thtml output for -man * add mandoc(1) -Tpdf support Changes in version 1.10.4, released on July 12, 2010 * Lots of features developed during both "Summer of Code" and the OpenBSD c2k10 hackathon: * minimal "ds" roff(7) symbols are supported * beautified SYNOPSIS section output * acceptance of scope-block breakage in mdoc(7) * clarify error message status * many minor bug-fixes and formatting issues resolved * see also changes in 1.10.3 Changes in version 1.10.3, June 29, 2010, NOT released * variable font-width and paper-size support in mandoc(1) -Tps output * "Bk" mdoc(7) support Changes in version 1.10.2, released on June 19, 2010 * Small release featuring text-decoration in -Tps output, a few minor relaxations of errors, and some optimisations. Changes in version 1.10.1, released on June 7, 2010 * This primarily focusses on the "Bl" and "It" macros described in mdoc(7). Multi-line column support is now fully compatible with groff, as are implicit list entries for columns. * Removed manuals(7) in favour of http://manpages.bsd.lv. * The way we handle the SYNOPSIS section (see the SYNOPSIS documentation in MANUAL STRUCTURE) has also been considerably simplified compared to groff's method. * Furthermore, the -Owidth=width output option has been added to -Tascii, see mandoc(1). * Lastly, initial PostScript output has been added with the -Tps option to mandoc(1). It's brutally simple at the moment: fixed-font, with no font decorations. Changes in version 1.10.0, released on May 29, 2010 * Release consisting of the results from the m2k10 hackathon and up-merge from OpenBSD. This requires a significant note of thanks to Ingo Schwarze (OpenBSD) and Joerg Sonnenberger (NetBSD) for their hard work, and again to Joerg for hosting m2k10. Highlights (mostly cribbed from Ingo's m2k10 report) follow in no particular order: * a libroff preprocessor in front of libmdoc and libman stripping out roff(7) instructions; * end-of-sentence (EOS) detection in free-form and macro lines; * correct handling of tab-separated columnar lists in mdoc(7); * improved main calling routines to optionally use mmap(3) for better performance; * cleaned up exiting when invoked as -Tlint or over multiple files with -fign-errors; * error and warning message handling re-written to be unified for libroff, libmdoc, and libman; * handling of badly-nested explicit-scoped macros; * improved free-form text parsing in libman and libmdoc; * significant GNU troff compatibility improvements in -Tascii, largely in terms of spacing; * a regression framework for making sure the many fragilities of GNU troff aren't trampled in subsequent work; * support for -Tascii breaking at hyphens encountered in free-form text; * and many more minor fixes and improvements Changes in version 1.9.25, released on May 13, 2010 * Fixed handling of "\*(Ba" escape. * Backed out -fno-ign-chars (pointless complexity). * Fixed erroneous breaking of literal lines. * Fixed SYNOPSIS breaking lines before non-initial macros. * Changed default section ordering. * Most importantly, the framework for end-of-sentence double-spacing is in place, now implemented for the "end-of-sentence, end-of-line" rule. * This is a stable roll-back point before the mandoc hackathon in Rostock! Changes in version 1.9.24, released on May 9, 2010 * Rolled back break-at-hyphen. * -DUGLY is now the default (no feature splits!). * Free-form text is not de-chunked any more: lines are passed whole-sale into the front-end, including whitespace. * Added mailing lists. Changes in version 1.9.23, released on April 7, 2010 * mdocml has been linked to the OpenBSD build. * This version incorporates many small changes, mostly from patches by OpenBSD, allowing crufty manuals to slip by with warnings instead of erroring-out. * Some subtle semantic issues, such as punctuation scope, have also been fixed. * Lastly, some issues with -Thtml have been fixed, which prompted an update to the online manual pages style layout. Changes in version 1.9.22, released on March 31, 2010 * Adjusted merge of the significant work by Ingo Schwarze in getting "Xo" blocks (block full implicit, e.g., "It" for non-columnar lists) to work properly. This isn't enabled by default: you must specify -DUGLY as a compiler flag (see the Makefile for details). Changes in version 1.9.20, released on March 30, 2010 * More efforts to get roff instructions in man(7) documents under control. Note that roff instructions embedded in line-scoped, next-line macros (e.g. "B") are not supported. * Leading punctuation for mdoc(7) macros, such as "Fl ( ( a", are now correctly handled. Changes in version 1.9.18, released on March 27, 2010 * Many fixes (largely pertaining to scope) and improvements (e.g., handling of apostrophe-control macros, which fixes the strange "BR" seen in some macro output) to handling roff instructions in man(7) documents. Changes in version 1.9.17, released on March 25, 2010 * Accept perlpod(1) standard preamble. * Also accept (and discard) "de", "dei", "am", "ami", and "ig" roff macro blocks. Changes in version 1.9.16, released on March 22, 2010 * Inspired by patches and bug reports by Ingo Schwarze, allowed man(7) to accept non-printing elements to be nested within next-line scopes, such as "br" within "B" or "TH", which is valid roff. * Longsoon architecture also noted and Makefile cleaned up. Changes in version 1.9.15, released on February 18, 2010 * Moved to our new BSD.lv home. * XHTML is now an acceptable output mode for mandoc(1); * "Xr" made more compatible with groff; * "Vt" fixed when invoked in SYNOPSIS; * "\\" escape removed; * end-of-line white-space detected for all lines; * subtle bug fixed in list display for some modes; * compatibility layer checked in for compilation in diverse UNIX systems; * and column lengths handled correctly. For older releases, see the ChangeLog files in http://mandoc.bsd.lv/snapshots/ . Index: head/contrib/mdocml/man_term.c =================================================================== --- head/contrib/mdocml/man_term.c (revision 324357) +++ head/contrib/mdocml/man_term.c (revision 324358) @@ -1,1101 +1,1101 @@ -/* $Id: man_term.c,v 1.208 2017/06/25 11:42:02 schwarze Exp $ */ +/* $Id: man_term.c,v 1.209 2017/07/31 15:19:06 schwarze Exp $ */ /* * Copyright (c) 2008-2012 Kristaps Dzonsons * Copyright (c) 2010-2015, 2017 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 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 #include "mandoc_aux.h" #include "mandoc.h" #include "roff.h" #include "man.h" #include "out.h" #include "term.h" #include "main.h" #define MAXMARGINS 64 /* maximum number of indented scopes */ struct mtermp { int fl; #define MANT_LITERAL (1 << 0) int lmargin[MAXMARGINS]; /* margins (incl. vis. page) */ int lmargincur; /* index of current margin */ int lmarginsz; /* actual number of nested margins */ size_t offset; /* default offset to visible page */ int pardist; /* vert. space before par., unit: [v] */ }; #define DECL_ARGS struct termp *p, \ struct mtermp *mt, \ struct roff_node *n, \ const struct roff_meta *meta struct termact { int (*pre)(DECL_ARGS); void (*post)(DECL_ARGS); int flags; #define MAN_NOTEXT (1 << 0) /* Never has text children. */ }; static void print_man_nodelist(DECL_ARGS); static void print_man_node(DECL_ARGS); static void print_man_head(struct termp *, const struct roff_meta *); static void print_man_foot(struct termp *, const struct roff_meta *); static void print_bvspace(struct termp *, const struct roff_node *, int); static int pre_B(DECL_ARGS); static int pre_DT(DECL_ARGS); static int pre_HP(DECL_ARGS); static int pre_I(DECL_ARGS); static int pre_IP(DECL_ARGS); static int pre_OP(DECL_ARGS); static int pre_PD(DECL_ARGS); static int pre_PP(DECL_ARGS); static int pre_RS(DECL_ARGS); static int pre_SH(DECL_ARGS); static int pre_SS(DECL_ARGS); static int pre_TP(DECL_ARGS); static int pre_UR(DECL_ARGS); static int pre_alternate(DECL_ARGS); static int pre_ign(DECL_ARGS); static int pre_in(DECL_ARGS); static int pre_literal(DECL_ARGS); static void post_IP(DECL_ARGS); static void post_HP(DECL_ARGS); static void post_RS(DECL_ARGS); static void post_SH(DECL_ARGS); static void post_SS(DECL_ARGS); static void post_TP(DECL_ARGS); static void post_UR(DECL_ARGS); static const struct termact __termacts[MAN_MAX - MAN_TH] = { { NULL, NULL, 0 }, /* TH */ { pre_SH, post_SH, 0 }, /* SH */ { pre_SS, post_SS, 0 }, /* SS */ { pre_TP, post_TP, 0 }, /* TP */ { pre_PP, NULL, 0 }, /* LP */ { pre_PP, NULL, 0 }, /* PP */ { pre_PP, NULL, 0 }, /* P */ { pre_IP, post_IP, 0 }, /* IP */ { pre_HP, post_HP, 0 }, /* HP */ { NULL, NULL, 0 }, /* SM */ { pre_B, NULL, 0 }, /* SB */ { pre_alternate, NULL, 0 }, /* BI */ { pre_alternate, NULL, 0 }, /* IB */ { pre_alternate, NULL, 0 }, /* BR */ { pre_alternate, NULL, 0 }, /* RB */ { NULL, NULL, 0 }, /* R */ { pre_B, NULL, 0 }, /* B */ { pre_I, NULL, 0 }, /* I */ { pre_alternate, NULL, 0 }, /* IR */ { pre_alternate, NULL, 0 }, /* RI */ { pre_literal, NULL, 0 }, /* nf */ { pre_literal, NULL, 0 }, /* fi */ { NULL, NULL, 0 }, /* RE */ { pre_RS, post_RS, 0 }, /* RS */ { pre_DT, NULL, 0 }, /* DT */ { pre_ign, NULL, MAN_NOTEXT }, /* UC */ { pre_PD, NULL, MAN_NOTEXT }, /* PD */ { pre_ign, NULL, 0 }, /* AT */ { pre_in, NULL, MAN_NOTEXT }, /* in */ { pre_OP, NULL, 0 }, /* OP */ { pre_literal, NULL, 0 }, /* EX */ { pre_literal, NULL, 0 }, /* EE */ { pre_UR, post_UR, 0 }, /* UR */ { NULL, NULL, 0 }, /* UE */ { pre_UR, post_UR, 0 }, /* MT */ { NULL, NULL, 0 }, /* ME */ }; static const struct termact *termacts = __termacts - MAN_TH; void terminal_man(void *arg, const struct roff_man *man) { struct termp *p; struct roff_node *n; struct mtermp mt; size_t save_defindent; p = (struct termp *)arg; save_defindent = p->defindent; if (p->synopsisonly == 0 && p->defindent == 0) p->defindent = 7; p->tcol->rmargin = p->maxrmargin = p->defrmargin; term_tab_set(p, NULL); term_tab_set(p, "T"); term_tab_set(p, ".5i"); memset(&mt, 0, sizeof(struct mtermp)); mt.lmargin[mt.lmargincur] = term_len(p, p->defindent); mt.offset = term_len(p, p->defindent); mt.pardist = 1; n = man->first->child; if (p->synopsisonly) { while (n != NULL) { if (n->tok == MAN_SH && n->child->child->type == ROFFT_TEXT && !strcmp(n->child->child->string, "SYNOPSIS")) { if (n->child->next->child != NULL) print_man_nodelist(p, &mt, n->child->next->child, &man->meta); term_newln(p); break; } n = n->next; } } else { term_begin(p, print_man_head, print_man_foot, &man->meta); p->flags |= TERMP_NOSPACE; if (n != NULL) print_man_nodelist(p, &mt, n, &man->meta); term_end(p); } p->defindent = save_defindent; } /* * Printing leading vertical space before a block. * This is used for the paragraph macros. * The rules are pretty simple, since there's very little nesting going * on here. Basically, if we're the first within another block (SS/SH), * then don't emit vertical space. If we are (RS), then do. If not the * first, print it. */ static void print_bvspace(struct termp *p, const struct roff_node *n, int pardist) { int i; term_newln(p); if (n->body && n->body->child) if (n->body->child->type == ROFFT_TBL) return; if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS) if (NULL == n->prev) return; for (i = 0; i < pardist; i++) term_vspace(p); } static int pre_ign(DECL_ARGS) { return 0; } static int pre_I(DECL_ARGS) { term_fontrepl(p, TERMFONT_UNDER); return 1; } static int pre_literal(DECL_ARGS) { term_newln(p); if (n->tok == MAN_nf || n->tok == MAN_EX) mt->fl |= MANT_LITERAL; else mt->fl &= ~MANT_LITERAL; /* * Unlike .IP and .TP, .HP does not have a HEAD. * So in case a second call to term_flushln() is needed, * indentation has to be set up explicitly. */ if (n->parent->tok == MAN_HP && p->tcol->rmargin < p->maxrmargin) { p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->maxrmargin; p->trailspace = 0; p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); p->flags |= TERMP_NOSPACE; } return 0; } static int pre_PD(DECL_ARGS) { struct roffsu su; n = n->child; if (n == NULL) { mt->pardist = 1; return 0; } assert(n->type == ROFFT_TEXT); if (a2roffsu(n->string, &su, SCALE_VS) != NULL) mt->pardist = term_vspan(p, &su); return 0; } static int pre_alternate(DECL_ARGS) { enum termfont font[2]; struct roff_node *nn; int savelit, i; switch (n->tok) { case MAN_RB: font[0] = TERMFONT_NONE; font[1] = TERMFONT_BOLD; break; case MAN_RI: font[0] = TERMFONT_NONE; font[1] = TERMFONT_UNDER; break; case MAN_BR: font[0] = TERMFONT_BOLD; font[1] = TERMFONT_NONE; break; case MAN_BI: font[0] = TERMFONT_BOLD; font[1] = TERMFONT_UNDER; break; case MAN_IR: font[0] = TERMFONT_UNDER; font[1] = TERMFONT_NONE; break; case MAN_IB: font[0] = TERMFONT_UNDER; font[1] = TERMFONT_BOLD; break; default: abort(); } savelit = MANT_LITERAL & mt->fl; mt->fl &= ~MANT_LITERAL; for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) { term_fontrepl(p, font[i]); if (savelit && NULL == nn->next) mt->fl |= MANT_LITERAL; assert(nn->type == ROFFT_TEXT); term_word(p, nn->string); if (nn->flags & NODE_EOS) p->flags |= TERMP_SENTENCE; if (nn->next) p->flags |= TERMP_NOSPACE; } return 0; } static int pre_B(DECL_ARGS) { term_fontrepl(p, TERMFONT_BOLD); return 1; } static int pre_OP(DECL_ARGS) { term_word(p, "["); p->flags |= TERMP_NOSPACE; if (NULL != (n = n->child)) { term_fontrepl(p, TERMFONT_BOLD); term_word(p, n->string); } if (NULL != n && NULL != n->next) { term_fontrepl(p, TERMFONT_UNDER); term_word(p, n->next->string); } term_fontrepl(p, TERMFONT_NONE); p->flags |= TERMP_NOSPACE; term_word(p, "]"); return 0; } static int pre_in(DECL_ARGS) { struct roffsu su; const char *cp; size_t v; int less; term_newln(p); if (n->child == NULL) { p->tcol->offset = mt->offset; return 0; } cp = n->child->string; less = 0; if ('-' == *cp) less = -1; else if ('+' == *cp) less = 1; else cp--; if (a2roffsu(++cp, &su, SCALE_EN) == NULL) return 0; v = term_hen(p, &su); if (less < 0) p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset; else if (less > 0) p->tcol->offset += v; else p->tcol->offset = v; if (p->tcol->offset > SHRT_MAX) p->tcol->offset = term_len(p, p->defindent); return 0; } static int pre_DT(DECL_ARGS) { term_tab_set(p, NULL); term_tab_set(p, "T"); term_tab_set(p, ".5i"); return 0; } static int pre_HP(DECL_ARGS) { struct roffsu su; const struct roff_node *nn; int len; switch (n->type) { case ROFFT_BLOCK: print_bvspace(p, n, mt->pardist); return 1; case ROFFT_BODY: break; default: return 0; } if ( ! (MANT_LITERAL & mt->fl)) { p->flags |= TERMP_NOBREAK | TERMP_BRIND; p->trailspace = 2; } /* Calculate offset. */ if ((nn = n->parent->head->child) != NULL && a2roffsu(nn->string, &su, SCALE_EN) != NULL) { len = term_hen(p, &su); if (len < 0 && (size_t)(-len) > mt->offset) len = -mt->offset; else if (len > SHRT_MAX) len = term_len(p, p->defindent); mt->lmargin[mt->lmargincur] = len; } else len = mt->lmargin[mt->lmargincur]; p->tcol->offset = mt->offset; p->tcol->rmargin = mt->offset + len; return 1; } static void post_HP(DECL_ARGS) { switch (n->type) { case ROFFT_BODY: term_newln(p); /* * Compatibility with a groff bug. * The .HP macro uses the undocumented .tag request * which causes a line break and cancels no-space * mode even if there isn't any output. */ if (n->child == NULL) term_vspace(p); p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); p->trailspace = 0; p->tcol->offset = mt->offset; p->tcol->rmargin = p->maxrmargin; break; default: break; } } static int pre_PP(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); print_bvspace(p, n, mt->pardist); break; default: p->tcol->offset = mt->offset; break; } return n->type != ROFFT_HEAD; } static int pre_IP(DECL_ARGS) { struct roffsu su; const struct roff_node *nn; int len, savelit; switch (n->type) { case ROFFT_BODY: p->flags |= TERMP_NOSPACE; break; case ROFFT_HEAD: p->flags |= TERMP_NOBREAK; p->trailspace = 1; break; case ROFFT_BLOCK: print_bvspace(p, n, mt->pardist); /* FALLTHROUGH */ default: return 1; } /* Calculate the offset from the optional second argument. */ if ((nn = n->parent->head->child) != NULL && (nn = nn->next) != NULL && a2roffsu(nn->string, &su, SCALE_EN) != NULL) { len = term_hen(p, &su); if (len < 0 && (size_t)(-len) > mt->offset) len = -mt->offset; else if (len > SHRT_MAX) len = term_len(p, p->defindent); mt->lmargin[mt->lmargincur] = len; } else len = mt->lmargin[mt->lmargincur]; switch (n->type) { case ROFFT_HEAD: p->tcol->offset = mt->offset; p->tcol->rmargin = mt->offset + len; savelit = MANT_LITERAL & mt->fl; mt->fl &= ~MANT_LITERAL; if (n->child) print_man_node(p, mt, n->child, meta); if (savelit) mt->fl |= MANT_LITERAL; return 0; case ROFFT_BODY: p->tcol->offset = mt->offset + len; p->tcol->rmargin = p->maxrmargin; break; default: break; } return 1; } static void post_IP(DECL_ARGS) { switch (n->type) { case ROFFT_HEAD: term_flushln(p); p->flags &= ~TERMP_NOBREAK; p->trailspace = 0; p->tcol->rmargin = p->maxrmargin; break; case ROFFT_BODY: term_newln(p); p->tcol->offset = mt->offset; break; default: break; } } static int pre_TP(DECL_ARGS) { struct roffsu su; struct roff_node *nn; int len, savelit; switch (n->type) { case ROFFT_HEAD: p->flags |= TERMP_NOBREAK | TERMP_BRTRSP; p->trailspace = 1; break; case ROFFT_BODY: p->flags |= TERMP_NOSPACE; break; case ROFFT_BLOCK: print_bvspace(p, n, mt->pardist); /* FALLTHROUGH */ default: return 1; } /* Calculate offset. */ if ((nn = n->parent->head->child) != NULL && nn->string != NULL && ! (NODE_LINE & nn->flags) && a2roffsu(nn->string, &su, SCALE_EN) != NULL) { len = term_hen(p, &su); if (len < 0 && (size_t)(-len) > mt->offset) len = -mt->offset; else if (len > SHRT_MAX) len = term_len(p, p->defindent); mt->lmargin[mt->lmargincur] = len; } else len = mt->lmargin[mt->lmargincur]; switch (n->type) { case ROFFT_HEAD: p->tcol->offset = mt->offset; p->tcol->rmargin = mt->offset + len; savelit = MANT_LITERAL & mt->fl; mt->fl &= ~MANT_LITERAL; /* Don't print same-line elements. */ nn = n->child; while (NULL != nn && 0 == (NODE_LINE & nn->flags)) nn = nn->next; while (NULL != nn) { print_man_node(p, mt, nn, meta); nn = nn->next; } if (savelit) mt->fl |= MANT_LITERAL; return 0; case ROFFT_BODY: p->tcol->offset = mt->offset + len; p->tcol->rmargin = p->maxrmargin; p->trailspace = 0; p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP); break; default: break; } return 1; } static void post_TP(DECL_ARGS) { switch (n->type) { case ROFFT_HEAD: term_flushln(p); break; case ROFFT_BODY: term_newln(p); p->tcol->offset = mt->offset; break; default: break; } } static int pre_SS(DECL_ARGS) { int i; switch (n->type) { case ROFFT_BLOCK: mt->fl &= ~MANT_LITERAL; mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); mt->offset = term_len(p, p->defindent); /* * No vertical space before the first subsection * and after an empty subsection. */ do { n = n->prev; - } while (n != NULL && n->tok != TOKEN_NONE && + } while (n != NULL && n->tok >= MAN_TH && termacts[n->tok].flags & MAN_NOTEXT); if (n == NULL || (n->tok == MAN_SS && n->body->child == NULL)) break; for (i = 0; i < mt->pardist; i++) term_vspace(p); break; case ROFFT_HEAD: term_fontrepl(p, TERMFONT_BOLD); p->tcol->offset = term_len(p, 3); p->tcol->rmargin = mt->offset; p->trailspace = mt->offset; p->flags |= TERMP_NOBREAK | TERMP_BRIND; break; case ROFFT_BODY: p->tcol->offset = mt->offset; p->tcol->rmargin = p->maxrmargin; p->trailspace = 0; p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); break; default: break; } return 1; } static void post_SS(DECL_ARGS) { switch (n->type) { case ROFFT_HEAD: term_newln(p); break; case ROFFT_BODY: term_newln(p); break; default: break; } } static int pre_SH(DECL_ARGS) { int i; switch (n->type) { case ROFFT_BLOCK: mt->fl &= ~MANT_LITERAL; mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); mt->offset = term_len(p, p->defindent); /* * No vertical space before the first section * and after an empty section. */ do { n = n->prev; - } while (n != NULL && n->tok != TOKEN_NONE && + } while (n != NULL && n->tok >= MAN_TH && termacts[n->tok].flags & MAN_NOTEXT); if (n == NULL || (n->tok == MAN_SH && n->body->child == NULL)) break; for (i = 0; i < mt->pardist; i++) term_vspace(p); break; case ROFFT_HEAD: term_fontrepl(p, TERMFONT_BOLD); p->tcol->offset = 0; p->tcol->rmargin = mt->offset; p->trailspace = mt->offset; p->flags |= TERMP_NOBREAK | TERMP_BRIND; break; case ROFFT_BODY: p->tcol->offset = mt->offset; p->tcol->rmargin = p->maxrmargin; p->trailspace = 0; p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); break; default: break; } return 1; } static void post_SH(DECL_ARGS) { switch (n->type) { case ROFFT_HEAD: term_newln(p); break; case ROFFT_BODY: term_newln(p); break; default: break; } } static int pre_RS(DECL_ARGS) { struct roffsu su; switch (n->type) { case ROFFT_BLOCK: term_newln(p); return 1; case ROFFT_HEAD: return 0; default: break; } n = n->parent->head; n->aux = SHRT_MAX + 1; if (n->child == NULL) n->aux = mt->lmargin[mt->lmargincur]; else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL) n->aux = term_hen(p, &su); if (n->aux < 0 && (size_t)(-n->aux) > mt->offset) n->aux = -mt->offset; else if (n->aux > SHRT_MAX) n->aux = term_len(p, p->defindent); mt->offset += n->aux; p->tcol->offset = mt->offset; p->tcol->rmargin = p->maxrmargin; if (++mt->lmarginsz < MAXMARGINS) mt->lmargincur = mt->lmarginsz; mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); return 1; } static void post_RS(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: return; case ROFFT_HEAD: return; default: term_newln(p); break; } mt->offset -= n->parent->head->aux; p->tcol->offset = mt->offset; if (--mt->lmarginsz < MAXMARGINS) mt->lmargincur = mt->lmarginsz; } static int pre_UR(DECL_ARGS) { return n->type != ROFFT_HEAD; } static void post_UR(DECL_ARGS) { if (n->type != ROFFT_BLOCK) return; term_word(p, "<"); p->flags |= TERMP_NOSPACE; if (NULL != n->child->child) print_man_node(p, mt, n->child->child, meta); p->flags |= TERMP_NOSPACE; term_word(p, ">"); } static void print_man_node(DECL_ARGS) { int c; switch (n->type) { case ROFFT_TEXT: /* * If we have a blank line, output a vertical space. * If we have a space as the first character, break * before printing the line's data. */ if (*n->string == '\0') { if (p->flags & TERMP_NONEWLINE) term_newln(p); else term_vspace(p); return; } else if (*n->string == ' ' && n->flags & NODE_LINE && (p->flags & TERMP_NONEWLINE) == 0) term_newln(p); term_word(p, n->string); goto out; case ROFFT_EQN: if ( ! (n->flags & NODE_LINE)) p->flags |= TERMP_NOSPACE; term_eqn(p, n->eqn); if (n->next != NULL && ! (n->next->flags & NODE_LINE)) p->flags |= TERMP_NOSPACE; return; case ROFFT_TBL: if (p->tbl.cols == NULL) term_vspace(p); term_tbl(p, n->span); return; default: break; } if (n->tok < ROFF_MAX) { roff_term_pre(p, n); return; } assert(n->tok >= MAN_TH && n->tok <= MAN_MAX); if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) term_fontrepl(p, TERMFONT_NONE); c = 1; if (termacts[n->tok].pre) c = (*termacts[n->tok].pre)(p, mt, n, meta); if (c && n->child) print_man_nodelist(p, mt, n->child, meta); if (termacts[n->tok].post) (*termacts[n->tok].post)(p, mt, n, meta); if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) term_fontrepl(p, TERMFONT_NONE); out: /* * If we're in a literal context, make sure that words * together on the same line stay together. This is a * POST-printing call, so we check the NEXT word. Since * -man doesn't have nested macros, we don't need to be * more specific than this. */ if (mt->fl & MANT_LITERAL && ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) && (n->next == NULL || n->next->flags & NODE_LINE)) { p->flags |= TERMP_BRNEVER | TERMP_NOSPACE; if (n->string != NULL && *n->string != '\0') term_flushln(p); else term_newln(p); p->flags &= ~TERMP_BRNEVER; if (p->tcol->rmargin < p->maxrmargin && n->parent->tok == MAN_HP) { p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->maxrmargin; } } if (NODE_EOS & n->flags) p->flags |= TERMP_SENTENCE; } static void print_man_nodelist(DECL_ARGS) { while (n != NULL) { print_man_node(p, mt, n, meta); n = n->next; } } static void print_man_foot(struct termp *p, const struct roff_meta *meta) { char *title; size_t datelen, titlen; assert(meta->title); assert(meta->msec); assert(meta->date); term_fontrepl(p, TERMFONT_NONE); if (meta->hasbody) term_vspace(p); /* * Temporary, undocumented option to imitate mdoc(7) output. * In the bottom right corner, use the operating system * instead of the title. */ if ( ! p->mdocstyle) { if (meta->hasbody) { term_vspace(p); term_vspace(p); } mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); } else if (meta->os) { title = mandoc_strdup(meta->os); } else { title = mandoc_strdup(""); } datelen = term_strlen(p, meta->date); /* Bottom left corner: operating system. */ p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; p->trailspace = 1; p->tcol->offset = 0; p->tcol->rmargin = p->maxrmargin > datelen ? (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0; if (meta->os) term_word(p, meta->os); term_flushln(p); /* At the bottom in the middle: manual date. */ p->tcol->offset = p->tcol->rmargin; titlen = term_strlen(p, title); p->tcol->rmargin = p->maxrmargin > titlen ? p->maxrmargin - titlen : 0; p->flags |= TERMP_NOSPACE; term_word(p, meta->date); term_flushln(p); /* Bottom right corner: manual title and section. */ p->flags &= ~TERMP_NOBREAK; p->flags |= TERMP_NOSPACE; p->trailspace = 0; p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->maxrmargin; term_word(p, title); term_flushln(p); free(title); } static void print_man_head(struct termp *p, const struct roff_meta *meta) { const char *volume; char *title; size_t vollen, titlen; assert(meta->title); assert(meta->msec); volume = NULL == meta->vol ? "" : meta->vol; vollen = term_strlen(p, volume); /* Top left corner: manual title and section. */ mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); titlen = term_strlen(p, title); p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; p->trailspace = 1; p->tcol->offset = 0; p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ? (p->maxrmargin - vollen + term_len(p, 1)) / 2 : vollen < p->maxrmargin ? p->maxrmargin - vollen : 0; term_word(p, title); term_flushln(p); /* At the top in the middle: manual volume. */ p->flags |= TERMP_NOSPACE; p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->tcol->offset + vollen + titlen < p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin; term_word(p, volume); term_flushln(p); /* Top right corner: title and section, again. */ p->flags &= ~TERMP_NOBREAK; p->trailspace = 0; if (p->tcol->rmargin + titlen <= p->maxrmargin) { p->flags |= TERMP_NOSPACE; p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->maxrmargin; term_word(p, title); term_flushln(p); } p->flags &= ~TERMP_NOSPACE; p->tcol->offset = 0; p->tcol->rmargin = p->maxrmargin; /* * Groff prints three blank lines before the content. * Do the same, except in the temporary, undocumented * mode imitating mdoc(7) output. */ term_vspace(p); if ( ! p->mdocstyle) { term_vspace(p); term_vspace(p); } free(title); } Index: head/contrib/mdocml/mansearch.c =================================================================== --- head/contrib/mdocml/mansearch.c (revision 324357) +++ head/contrib/mdocml/mansearch.c (revision 324358) @@ -1,810 +1,841 @@ -/* $OpenBSD: mansearch.c,v 1.50 2016/07/09 15:23:36 schwarze Exp $ */ +/* $Id: mansearch.c,v 1.76 2017/08/02 13:29:04 schwarze Exp $ */ /* * Copyright (c) 2012 Kristaps Dzonsons * Copyright (c) 2013-2017 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 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 #if HAVE_ERR #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "mandoc.h" #include "mandoc_aux.h" #include "mandoc_ohash.h" #include "manconf.h" #include "mansearch.h" #include "dbm.h" struct expr { /* Used for terms: */ struct dbm_match match; /* Match type and expression. */ uint64_t bits; /* Type mask. */ /* Used for OR and AND groups: */ struct expr *next; /* Next child in the parent group. */ struct expr *child; /* First child in this group. */ enum { EXPR_TERM, EXPR_OR, EXPR_AND } type; }; const char *const mansearch_keynames[KEY_MAX] = { "arch", "sec", "Xr", "Ar", "Fa", "Fl", "Dv", "Fn", "Ic", "Pa", "Cm", "Li", "Em", "Cd", "Va", "Ft", "Tn", "Er", "Ev", "Sy", "Sh", "In", "Ss", "Ox", "An", "Mt", "St", "Bx", "At", "Nx", "Fx", "Lk", "Ms", "Bsx", "Dx", "Rs", "Vt", "Lb", "Nm", "Nd" }; static struct ohash *manmerge(struct expr *, struct ohash *); static struct ohash *manmerge_term(struct expr *, struct ohash *); static struct ohash *manmerge_or(struct expr *, struct ohash *); static struct ohash *manmerge_and(struct expr *, struct ohash *); static char *buildnames(const struct dbm_page *); static char *buildoutput(size_t, struct dbm_page *); static size_t lstlen(const char *, size_t); static void lstcat(char *, size_t *, const char *, const char *); static int lstmatch(const char *, const char *); static struct expr *exprcomp(const struct mansearch *, int, char *[], int *); static struct expr *expr_and(const struct mansearch *, int, char *[], int *); static struct expr *exprterm(const struct mansearch *, int, char *[], int *); static void exprfree(struct expr *); static int manpage_compare(const void *, const void *); int mansearch(const struct mansearch *search, const struct manpaths *paths, int argc, char *argv[], struct manpage **res, size_t *sz) { char buf[PATH_MAX]; struct dbm_res *rp; struct expr *e; struct dbm_page *page; struct manpage *mpage; struct ohash *htab; size_t cur, i, maxres, outkey; unsigned int slot; int argi, chdir_status, getcwd_status, im; argi = 0; if ((e = exprcomp(search, argc, argv, &argi)) == NULL) { *sz = 0; return 0; } cur = maxres = 0; if (res != NULL) *res = NULL; outkey = KEY_Nd; if (search->outkey != NULL) for (im = 0; im < KEY_MAX; im++) if (0 == strcasecmp(search->outkey, mansearch_keynames[im])) { outkey = im; break; } /* * Remember the original working directory, if possible. * This will be needed if the second or a later directory * 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(buf, PATH_MAX) == NULL) { getcwd_status = 0; (void)strlcpy(buf, strerror(errno), sizeof(buf)); } else getcwd_status = 1; /* * Loop over the directories (containing databases) for us to * search. * Don't let missing/bad databases/directories phase us. * In each, try to open the resident database and, if it opens, * scan it for our match expression. */ chdir_status = 0; for (i = 0; i < paths->sz; i++) { if (chdir_status && paths->paths[i][0] != '/') { if ( ! getcwd_status) { warnx("%s: getcwd: %s", paths->paths[i], buf); continue; } else if (chdir(buf) == -1) { warn("%s", buf); continue; } } if (chdir(paths->paths[i]) == -1) { warn("%s", paths->paths[i]); continue; } chdir_status = 1; if (dbm_open(MANDOC_DB) == -1) { if (errno != ENOENT) warn("%s/%s", paths->paths[i], MANDOC_DB); continue; } if ((htab = manmerge(e, NULL)) == NULL) { dbm_close(); continue; } for (rp = ohash_first(htab, &slot); rp != NULL; rp = ohash_next(htab, &slot)) { page = dbm_page_get(rp->page); if (lstmatch(search->sec, page->sect) == 0 || - lstmatch(search->arch, page->arch) == 0) + lstmatch(search->arch, page->arch) == 0 || + (search->argmode == ARG_NAME && + rp->bits <= (int32_t)(NAME_SYN & NAME_MASK))) continue; if (res == NULL) { cur = 1; break; } if (cur + 1 > maxres) { maxres += 1024; *res = mandoc_reallocarray(*res, maxres, sizeof(**res)); } mpage = *res + cur; mandoc_asprintf(&mpage->file, "%s/%s", paths->paths[i], page->file + 1); mpage->names = buildnames(page); mpage->output = buildoutput(outkey, page); mpage->ipath = i; mpage->bits = rp->bits; mpage->sec = *page->sect - '0'; if (mpage->sec < 0 || mpage->sec > 9) mpage->sec = 10; mpage->form = *page->file; free(rp); cur++; } ohash_delete(htab); free(htab); dbm_close(); /* * In man(1) mode, prefer matches in earlier trees * over matches in later trees. */ if (cur && search->firstmatch) break; } if (res != NULL) qsort(*res, cur, sizeof(struct manpage), manpage_compare); if (chdir_status && getcwd_status && chdir(buf) == -1) warn("%s", buf); exprfree(e); *sz = cur; return res != NULL || cur; } /* * Merge the results for the expression tree rooted at e * into the the result list htab. */ static struct ohash * manmerge(struct expr *e, struct ohash *htab) { switch (e->type) { case EXPR_TERM: return manmerge_term(e, htab); case EXPR_OR: return manmerge_or(e->child, htab); case EXPR_AND: return manmerge_and(e->child, htab); default: abort(); } } static struct ohash * manmerge_term(struct expr *e, struct ohash *htab) { struct dbm_res res, *rp; uint64_t ib; unsigned int slot; int im; if (htab == NULL) { htab = mandoc_malloc(sizeof(*htab)); mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page)); } for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) { if ((e->bits & ib) == 0) continue; switch (ib) { case TYPE_arch: dbm_page_byarch(&e->match); break; case TYPE_sec: dbm_page_bysect(&e->match); break; case TYPE_Nm: dbm_page_byname(&e->match); break; case TYPE_Nd: dbm_page_bydesc(&e->match); break; default: dbm_page_bymacro(im - 2, &e->match); break; } /* * When hashing for deduplication, use the unique * page ID itself instead of a hash function; * that is quite efficient. */ for (;;) { res = dbm_page_next(); if (res.page == -1) break; slot = ohash_lookup_memory(htab, (char *)&res, sizeof(res.page), res.page); if ((rp = ohash_find(htab, slot)) != NULL) { rp->bits |= res.bits; continue; } rp = mandoc_malloc(sizeof(*rp)); *rp = res; ohash_insert(htab, slot, rp); } } return htab; } static struct ohash * manmerge_or(struct expr *e, struct ohash *htab) { while (e != NULL) { htab = manmerge(e, htab); e = e->next; } return htab; } static struct ohash * manmerge_and(struct expr *e, struct ohash *htab) { struct ohash *hand, *h1, *h2; struct dbm_res *res; unsigned int slot1, slot2; /* Evaluate the first term of the AND clause. */ hand = manmerge(e, NULL); while ((e = e->next) != NULL) { /* Evaluate the next term and prepare for ANDing. */ h2 = manmerge(e, NULL); if (ohash_entries(h2) < ohash_entries(hand)) { h1 = h2; h2 = hand; } else h1 = hand; hand = mandoc_malloc(sizeof(*hand)); mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page)); /* Keep all pages that are in both result sets. */ for (res = ohash_first(h1, &slot1); res != NULL; res = ohash_next(h1, &slot1)) { if (ohash_find(h2, ohash_lookup_memory(h2, (char *)res, sizeof(res->page), res->page)) == NULL) free(res); else ohash_insert(hand, ohash_lookup_memory(hand, (char *)res, sizeof(res->page), res->page), res); } /* Discard the merged results. */ for (res = ohash_first(h2, &slot2); res != NULL; res = ohash_next(h2, &slot2)) free(res); ohash_delete(h2); free(h2); ohash_delete(h1); free(h1); } /* Merge the result of the AND into htab. */ if (htab == NULL) return hand; for (res = ohash_first(hand, &slot1); res != NULL; res = ohash_next(hand, &slot1)) { slot2 = ohash_lookup_memory(htab, (char *)res, sizeof(res->page), res->page); if (ohash_find(htab, slot2) == NULL) ohash_insert(htab, slot2, res); else free(res); } /* Discard the merged result. */ ohash_delete(hand); free(hand); return htab; } void mansearch_free(struct manpage *res, size_t sz) { size_t i; for (i = 0; i < sz; i++) { free(res[i].file); free(res[i].names); free(res[i].output); } free(res); } static int manpage_compare(const void *vp1, const void *vp2) { const struct manpage *mp1, *mp2; const char *cp1, *cp2; size_t sz1, sz2; int diff; mp1 = vp1; mp2 = vp2; if ((diff = mp2->bits - mp1->bits) || (diff = mp1->sec - mp2->sec)) return diff; /* Fall back to alphabetic ordering of names. */ sz1 = strcspn(mp1->names, "("); sz2 = strcspn(mp2->names, "("); if (sz1 < sz2) sz1 = sz2; if ((diff = strncasecmp(mp1->names, mp2->names, sz1))) return diff; /* For identical names and sections, prefer arch-dependent. */ cp1 = strchr(mp1->names + sz1, '/'); cp2 = strchr(mp2->names + sz2, '/'); return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) : cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0; } static char * buildnames(const struct dbm_page *page) { char *buf; size_t i, sz; sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) + (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2; buf = mandoc_malloc(sz); i = 0; lstcat(buf, &i, page->name, ", "); buf[i++] = '('; lstcat(buf, &i, page->sect, ", "); if (page->arch != NULL) { buf[i++] = '/'; lstcat(buf, &i, page->arch, ", "); } buf[i++] = ')'; buf[i++] = '\0'; assert(i == sz); return buf; } /* * Count the buffer space needed to print the NUL-terminated * list of NUL-terminated strings, when printing sep separator * characters between strings. */ static size_t lstlen(const char *cp, size_t sep) { size_t sz; - for (sz = 0;; sz++) { - if (cp[0] == '\0') { - if (cp[1] == '\0') - break; - sz += sep - 1; - } else if (cp[0] < ' ') - sz--; - cp++; + for (sz = 0; *cp != '\0'; cp++) { + + /* Skip names appearing only in the SYNOPSIS. */ + if (*cp <= (char)(NAME_SYN & NAME_MASK)) { + while (*cp != '\0') + cp++; + continue; + } + + /* Skip name class markers. */ + if (*cp < ' ') + cp++; + + /* Print a separator before each but the first string. */ + if (sz) + sz += sep; + + /* Copy one string. */ + while (*cp != '\0') { + sz++; + cp++; + } } return sz; } /* * Print the NUL-terminated list of NUL-terminated strings * into the buffer, seperating strings with sep. */ static void lstcat(char *buf, size_t *i, const char *cp, const char *sep) { - const char *s; + const char *s; + size_t i_start; - for (;;) { - if (cp[0] == '\0') { - if (cp[1] == '\0') - break; + for (i_start = *i; *cp != '\0'; cp++) { + + /* Skip names appearing only in the SYNOPSIS. */ + if (*cp <= (char)(NAME_SYN & NAME_MASK)) { + while (*cp != '\0') + cp++; + continue; + } + + /* Skip name class markers. */ + if (*cp < ' ') + cp++; + + /* Print a separator before each but the first string. */ + if (*i > i_start) { s = sep; while (*s != '\0') buf[(*i)++] = *s++; - } else if (cp[0] >= ' ') - buf[(*i)++] = cp[0]; - cp++; + } + + /* Copy one string. */ + while (*cp != '\0') + buf[(*i)++] = *cp++; } + } /* * Return 1 if the string *want occurs in any of the strings * in the NUL-terminated string list *have, or 0 otherwise. * If either argument is NULL or empty, assume no filtering * is desired and return 1. */ static int lstmatch(const char *want, const char *have) { if (want == NULL || have == NULL || *have == '\0') return 1; while (*have != '\0') { if (strcasestr(have, want) != NULL) return 1; have = strchr(have, '\0') + 1; } return 0; } /* * Build a list of values taken by the macro im in the manual page. */ static char * buildoutput(size_t im, struct dbm_page *page) { const char *oldoutput, *sep, *input; char *output, *newoutput, *value; size_t sz, i; switch (im) { case KEY_Nd: return mandoc_strdup(page->desc); case KEY_Nm: input = page->name; break; case KEY_sec: input = page->sect; break; case KEY_arch: input = page->arch; if (input == NULL) input = "all\0"; break; default: input = NULL; break; } if (input != NULL) { sz = lstlen(input, 3) + 1; output = mandoc_malloc(sz); i = 0; lstcat(output, &i, input, " # "); output[i++] = '\0'; assert(i == sz); return output; } output = NULL; dbm_macro_bypage(im - 2, page->addr); while ((value = dbm_macro_next()) != NULL) { if (output == NULL) { oldoutput = ""; sep = ""; } else { oldoutput = output; sep = " # "; } mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value); free(output); output = newoutput; } return output; } /* * Compile a set of string tokens into an expression. * Tokens in "argv" are assumed to be individual expression atoms (e.g., * "(", "foo=bar", etc.). */ static struct expr * exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi) { struct expr *parent, *child; int needterm, nested; if ((nested = *argi) == argc) return NULL; needterm = 1; parent = child = NULL; while (*argi < argc) { if (strcmp(")", argv[*argi]) == 0) { if (needterm) warnx("missing term " "before closing parenthesis"); needterm = 0; if (nested) break; warnx("ignoring unmatched right parenthesis"); ++*argi; continue; } if (strcmp("-o", argv[*argi]) == 0) { if (needterm) { if (*argi > 0) warnx("ignoring -o after %s", argv[*argi - 1]); else warnx("ignoring initial -o"); } needterm = 1; ++*argi; continue; } needterm = 0; if (child == NULL) { child = expr_and(search, argc, argv, argi); continue; } if (parent == NULL) { parent = mandoc_calloc(1, sizeof(*parent)); parent->type = EXPR_OR; parent->next = NULL; parent->child = child; } child->next = expr_and(search, argc, argv, argi); child = child->next; } if (needterm && *argi) warnx("ignoring trailing %s", argv[*argi - 1]); return parent == NULL ? child : parent; } static struct expr * expr_and(const struct mansearch *search, int argc, char *argv[], int *argi) { struct expr *parent, *child; int needterm; needterm = 1; parent = child = NULL; while (*argi < argc) { if (strcmp(")", argv[*argi]) == 0) { if (needterm) warnx("missing term " "before closing parenthesis"); needterm = 0; break; } if (strcmp("-o", argv[*argi]) == 0) break; if (strcmp("-a", argv[*argi]) == 0) { if (needterm) { if (*argi > 0) warnx("ignoring -a after %s", argv[*argi - 1]); else warnx("ignoring initial -a"); } needterm = 1; ++*argi; continue; } if (needterm == 0) break; if (child == NULL) { child = exprterm(search, argc, argv, argi); if (child != NULL) needterm = 0; continue; } needterm = 0; if (parent == NULL) { parent = mandoc_calloc(1, sizeof(*parent)); parent->type = EXPR_AND; parent->next = NULL; parent->child = child; } child->next = exprterm(search, argc, argv, argi); if (child->next != NULL) { child = child->next; needterm = 0; } } if (needterm && *argi) warnx("ignoring trailing %s", argv[*argi - 1]); return parent == NULL ? child : parent; } static struct expr * exprterm(const struct mansearch *search, int argc, char *argv[], int *argi) { char errbuf[BUFSIZ]; struct expr *e; char *key, *val; uint64_t iterbit; int cs, i, irc; if (strcmp("(", argv[*argi]) == 0) { ++*argi; e = exprcomp(search, argc, argv, argi); if (*argi < argc) { assert(strcmp(")", argv[*argi]) == 0); ++*argi; } else warnx("unclosed parenthesis"); return e; } if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) { cs = 0; ++*argi; } else cs = 1; e = mandoc_calloc(1, sizeof(*e)); e->type = EXPR_TERM; e->bits = 0; e->next = NULL; e->child = NULL; if (search->argmode == ARG_NAME) { e->bits = TYPE_Nm; e->match.type = DBM_EXACT; e->match.str = argv[(*argi)++]; return e; } /* * Separate macro keys from search string. * If needed, request regular expression handling. */ if (search->argmode == ARG_WORD) { e->bits = TYPE_Nm; e->match.type = DBM_REGEX; #if HAVE_REWB_BSD mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]); #elif HAVE_REWB_SYSV mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]); #else mandoc_asprintf(&val, "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]); #endif cs = 0; } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) { e->bits = TYPE_Nm | TYPE_Nd; e->match.type = DBM_SUB; e->match.str = argv[*argi]; } else { if (val == argv[*argi]) e->bits = TYPE_Nm | TYPE_Nd; if (*val == '=') { e->match.type = DBM_SUB; e->match.str = val + 1; } else e->match.type = DBM_REGEX; *val++ = '\0'; if (strstr(argv[*argi], "arch") != NULL) cs = 0; } /* Compile regular expressions. */ if (e->match.type == DBM_REGEX) { e->match.re = mandoc_malloc(sizeof(*e->match.re)); irc = regcomp(e->match.re, val, REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE)); if (irc) { regerror(irc, e->match.re, errbuf, sizeof(errbuf)); warnx("regcomp /%s/: %s", val, errbuf); } if (search->argmode == ARG_WORD) free(val); if (irc) { free(e->match.re); free(e); ++*argi; return NULL; } } if (e->bits) { ++*argi; return e; } /* * Parse out all possible fields. * If the field doesn't resolve, bail. */ while (NULL != (key = strsep(&argv[*argi], ","))) { if ('\0' == *key) continue; for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) { if (0 == strcasecmp(key, mansearch_keynames[i])) { e->bits |= iterbit; break; } } if (i == KEY_MAX) { if (strcasecmp(key, "any")) warnx("treating unknown key " "\"%s\" as \"any\"", key); e->bits |= ~0ULL; } } ++*argi; return e; } static void exprfree(struct expr *e) { if (e->next != NULL) exprfree(e->next); if (e->child != NULL) exprfree(e->child); free(e); } Index: head/contrib/mdocml/mdoc_validate.c =================================================================== --- head/contrib/mdocml/mdoc_validate.c (revision 324357) +++ head/contrib/mdocml/mdoc_validate.c (revision 324358) @@ -1,2909 +1,2906 @@ -/* $Id: mdoc_validate.c,v 1.350 2017/07/20 12:54:02 schwarze Exp $ */ +/* $Id: mdoc_validate.c,v 1.352 2017/08/02 13:29:04 schwarze Exp $ */ /* * Copyright (c) 2008-2012 Kristaps Dzonsons * Copyright (c) 2010-2017 Ingo Schwarze * 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. */ #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" /* 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_text(struct roff_man *, int, int, char *); 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_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_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_en(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_fn(POST_ARGS); static void post_fname(POST_ARGS); static void post_fo(POST_ARGS); static void post_hyph(POST_ARGS); static void post_ignpar(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_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_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_ignpar, /* 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_delim_nb, /* Cm */ post_delim_nb, /* Dv */ post_delim_nb, /* Er */ post_delim_nb, /* Ev */ post_ex, /* Ex */ post_fa, /* Fa */ NULL, /* Fd */ post_delim_nb, /* Fl */ post_fn, /* Fn */ post_delim_nb, /* Ft */ post_delim_nb, /* Ic */ post_delim_nb, /* In */ post_defaults, /* Li */ post_nd, /* Nd */ post_nm, /* Nm */ post_delim_nb, /* Op */ post_obsolete, /* Ot */ post_defaults, /* Pa */ post_rv, /* Rv */ post_st, /* St */ post_delim_nb, /* 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 */ post_delim_nb, /* Ao */ post_delim_nb, /* Aq */ post_at, /* At */ NULL, /* Bc */ post_bf, /* Bf */ post_delim_nb, /* Bo */ NULL, /* Bq */ post_xx, /* Bsx */ post_bx, /* Bx */ post_obsolete, /* Db */ NULL, /* Dc */ NULL, /* Do */ NULL, /* Dq */ NULL, /* Ec */ NULL, /* Ef */ post_delim_nb, /* Em */ NULL, /* Eo */ post_xx, /* Fx */ post_delim_nb, /* Ms */ NULL, /* No */ post_ns, /* Ns */ post_xx, /* Nx */ post_xx, /* Ox */ NULL, /* Pc */ NULL, /* Pf */ post_delim_nb, /* Po */ post_delim_nb, /* Pq */ NULL, /* Qc */ post_delim_nb, /* Ql */ post_delim_nb, /* Qo */ post_delim_nb, /* Qq */ NULL, /* Re */ post_rs, /* Rs */ NULL, /* Sc */ post_delim_nb, /* So */ post_delim_nb, /* Sq */ post_sm, /* Sm */ post_sx, /* Sx */ post_delim_nb, /* Sy */ post_useless, /* Tn */ post_xx, /* Ux */ NULL, /* Xc */ NULL, /* Xo */ post_fo, /* Fo */ NULL, /* Fc */ post_delim_nb, /* Oo */ NULL, /* Oc */ post_bk, /* Bk */ NULL, /* Ek */ post_eoln, /* Bt */ post_obsolete, /* Hf */ post_obsolete, /* Fr */ post_eoln, /* Ud */ post_lb, /* Lb */ post_par, /* Lp */ post_delim_nb, /* Lk */ post_defaults, /* Mt */ post_delim_nb, /* Brq */ post_delim_nb, /* Bro */ NULL, /* Brc */ NULL, /* %C */ post_es, /* Es */ post_en, /* En */ post_xx, /* Dx */ NULL, /* %Q */ NULL, /* %U */ NULL, /* Ta */ }; static const v_post *const mdoc_valids = __mdoc_valids - MDOC_Dd; #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 }; void mdoc_node_validate(struct roff_man *mdoc) { struct roff_node *n; const v_post *p; n = mdoc->last; mdoc->last = mdoc->last->child; while (mdoc->last != NULL) { mdoc_node_validate(mdoc); if (mdoc->last == n) mdoc->last = mdoc->last->child; else mdoc->last = mdoc->last->next; } mdoc->last = n; mdoc->next = ROFF_NEXT_SIBLING; switch (n->type) { case ROFFT_TEXT: if (n->sec != SEC_SYNOPSIS || (n->parent->tok != MDOC_Cd && n->parent->tok != MDOC_Fd)) check_text(mdoc, n->line, n->pos, n->string); if (n->parent->tok == MDOC_It || (n->parent->type == ROFFT_BODY && (n->parent->tok == MDOC_Sh || n->parent->tok == MDOC_Ss))) check_toptext(mdoc, n->line, n->pos, n->string); break; 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) { switch(n->tok) { case ROFF_br: case ROFF_sp: post_par(mdoc); break; default: roff_validate(mdoc); break; } break; } assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); p = mdoc_valids + n->tok; 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_LITERAL & mdoc->flags) return; for (cp = p; NULL != (p = strchr(p, '\t')); p++) mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse, ln, pos + (int)(p - cp), NULL); } 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, mdoc->parse, ln, pos + (cp - p), "Ox"); if ((cp = strstr(p, "NetBSD")) != NULL) mandoc_msg(MANDOCERR_BX, mdoc->parse, ln, pos + (cp - p), "Nx"); if ((cp = strstr(p, "FreeBSD")) != NULL) mandoc_msg(MANDOCERR_BX, mdoc->parse, ln, pos + (cp - p), "Fx"); if ((cp = strstr(p, "DragonFly")) != NULL) mandoc_msg(MANDOCERR_BX, mdoc->parse, ln, pos + (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_vmsg(MANDOCERR_FUNC, mdoc->parse, ln, pos + (cpr - p), "%.*s()", (int)(cp - cpr), cpr); } } } 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_vmsg(MANDOCERR_DELIM, mdoc->parse, nch->line, nch->pos + (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_Po || 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_vmsg(MANDOCERR_DELIM_NB, mdoc->parse, nch->line, nch->pos + (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, mdoc->parse, 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, mdoc->parse, argv->line, argv->pos, "Bl -width"); n->norm->Bl.width = "0n"; break; } if (NULL != n->norm->Bl.width) mandoc_vmsg(MANDOCERR_ARG_REP, mdoc->parse, 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, mdoc->parse, argv->line, argv->pos, "Bl -offset"); break; } if (NULL != n->norm->Bl.offs) mandoc_vmsg(MANDOCERR_ARG_REP, mdoc->parse, 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_vmsg(MANDOCERR_BL_REP, mdoc->parse, 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_vmsg(MANDOCERR_BL_LATETYPE, mdoc->parse, 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, mdoc->parse, 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, mdoc->parse, 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_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse, 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, mdoc->parse, n->line, n->pos, NULL); break; case MDOC_Offset: if (0 == argv->sz) { mandoc_msg(MANDOCERR_ARG_EMPTY, mdoc->parse, argv->line, argv->pos, "Bd -offset"); break; } if (NULL != n->norm->Bd.offs) mandoc_vmsg(MANDOCERR_ARG_REP, mdoc->parse, 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, mdoc->parse, 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_vmsg(MANDOCERR_BD_REP, mdoc->parse, n->line, n->pos, "Bd -%s", mdoc_argnames[argv->arg]); } if (DISP__NONE == n->norm->Bd.type) { mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse, 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_vmsg(MANDOCERR_AN_REP, mdoc->parse, 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_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, 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; mdoc_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, mdoc->parse, 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; post_delim_nb(mdoc); n = mdoc->last; assert(n->child->type == ROFFT_TEXT); mdoc->next = ROFF_NEXT_CHILD; if ((p = mdoc_a2lib(n->child->string)) != NULL) { n->child->flags |= NODE_NOPRT; roff_word_alloc(mdoc, n->line, n->pos, p); mdoc->last->flags = NODE_NOSRC; mdoc->last = n; return; } mandoc_vmsg(MANDOCERR_LB_BAD, mdoc->parse, 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, mdoc->parse, n->line, n->pos, 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_vmsg(MANDOCERR_ST_BAD, mdoc->parse, 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_obsolete(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK) mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse, n->line, n->pos, roff_name[n->tok]); } static void post_useless(POST_ARGS) { struct roff_node *n; n = mdoc->last; mandoc_msg(MANDOCERR_MACRO_USELESS, mdoc->parse, n->line, n->pos, 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, mdoc->parse, np->line, np->pos, "Bf"); return; } nch = nch->next; } if (nch != NULL) mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 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_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse, np->child->line, np->child->pos, "Bf %s", np->child->string); } static void post_fname(POST_ARGS) { const struct roff_node *n; const char *cp; size_t pos; n = mdoc->last->child; pos = strcspn(n->string, "()"); cp = n->string + pos; if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*'))) mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse, n->line, n->pos + pos, n->string); - if (n->sec == SEC_SYNOPSIS && mdoc->meta.msec != NULL) - mandoc_xr_add(mdoc->meta.msec, n->string, -1, -1); } 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, mdoc->parse, n->line, n->pos, "Fo"); return; } if (n->child != n->last) { mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 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, mdoc->parse, n->line, n->pos + (cp - n->string), 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->sec == SEC_SYNOPSIS) && - n->child != NULL && n->child->type == ROFFT_TEXT && - mdoc->meta.msec != NULL) + 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 || n->last->tok == MDOC_Lp)) mdoc_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, mdoc->parse, 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, mdoc->parse, n->line, n->pos, "Nd"); if (n->child == NULL) mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse, 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, mdoc->parse, n->line, n->pos, 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, mdoc->parse, n->line, n->pos, "Bd"); mdoc->next = ROFF_NEXT_SIBLING; while (n->body->child != NULL) mdoc_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_vmsg(MANDOCERR_BD_NEST, mdoc->parse, n->line, n->pos, "%s in Bd", roff_name[n->tok]); break; } } break; default: break; } } static void post_defaults(POST_ARGS) { struct roff_node *nn; if (mdoc->last->child != NULL) { post_delim_nb(mdoc); return; } /* * The `Ar' defaults to "file ..." if no value is provided as an * argument; the `Mt' and `Pa' macros use "~"; the `Li' just * gets an empty string. */ nn = mdoc->last; switch (nn->tok) { case MDOC_Ar: mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, nn->line, nn->pos, "file"); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, nn->line, nn->pos, "..."); mdoc->last->flags |= NODE_NOSRC; break; case MDOC_Pa: case MDOC_Mt: mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, nn->line, nn->pos, "~"); mdoc->last->flags |= NODE_NOSRC; break; default: abort(); } mdoc->last = nn; } 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_vmsg(MANDOCERR_AT_BAD, mdoc->parse, 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, mdoc->parse, np->line, np->pos, "An"); else post_delim_nb(mdoc); } else if (nch != NULL) mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, nch->line, nch->pos, "An ... %s", nch->string); } 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_es(POST_ARGS) { post_obsolete(mdoc); mdoc->last_es = mdoc->last; } 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_vmsg(MANDOCERR_IT_NOHEAD, mdoc->parse, 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_vmsg(MANDOCERR_IT_NOBODY, mdoc->parse, 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_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, nit->line, nit->pos, "It %s", nch->string == NULL ? roff_name[nch->tok] : nch->string); 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, mdoc->parse, 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, mdoc->parse, nch->line, nch->pos, "Ta"); } if (i < cols || i > cols + 1) mandoc_vmsg(MANDOCERR_BL_COL, mdoc->parse, nit->line, nit->pos, "%d columns, %d cells", cols, i); else if (nit->head->next->child != NULL && nit->head->next->child->line > nit->line) mandoc_msg(MANDOCERR_IT_NOARG, mdoc->parse, 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 MDOC_Lp: case ROFF_br: break; default: nc = NULL; continue; } if (ni->next == NULL) { mandoc_msg(MANDOCERR_PAR_MOVE, mdoc->parse, nc->line, nc->pos, roff_name[nc->tok]); mdoc_node_relink(mdoc, nc); } else if (n->norm->Bl.comp == 0 && n->norm->Bl.type != LIST_column) { mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, nc->line, nc->pos, "%s before It", roff_name[nc->tok]); roff_node_delete(mdoc, nc); } else break; nc = ni->body->last; } } } /* * 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; if (*arg == NULL) return; else if ( ! strcmp(*arg, "Ds")) width = 6; else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE) return; else width = macro2len(tok); free(*arg); 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_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 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 *nparent, *nprev; /* of the Bl block */ struct roff_node *nblock, *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; nchild = nbody->child; if (nchild == NULL) { mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse, nbody->line, nbody->pos, "Bl"); return; } while (nchild != NULL) { nnext = nchild->next; if (nchild->tok == MDOC_It || (nchild->tok == MDOC_Sm && nnext != NULL && nnext->tok == MDOC_It)) { nchild = nnext; continue; } /* * 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, which cannot be moved * out of the list. In that case, wrap such roff nodes * into an implicit row. */ if (nchild->prev != NULL) { mdoc->last = nchild; 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) { mdoc_node_relink(mdoc, nchild); if ((nchild = nnext) == NULL) break; nnext = nchild->next; mdoc->next = ROFF_NEXT_SIBLING; } mdoc->last = nbody; continue; } mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse, nchild->line, nchild->pos, roff_name[nchild->tok]); /* * Move the node out of the Bl block. * First, collect all required node pointers. */ nblock = nbody->parent; nprev = nblock->prev; nparent = nblock->parent; /* * Unlink this child. */ nbody->child = nnext; if (nnext == NULL) nbody->last = NULL; else nnext->prev = NULL; /* * Relink this child. */ nchild->parent = nparent; nchild->prev = nprev; nchild->next = nblock; nblock->prev = nchild; if (nprev == NULL) nparent->child = nchild; else nprev->next = nchild; nchild = nnext; } 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_vmsg(MANDOCERR_ER_ORDER, mdoc->parse, nnext->line, nnext->pos, "Er %s %s (NetBSD)", prev_Er, nnext->string); else if (order == 0) mandoc_vmsg(MANDOCERR_ER_REP, mdoc->parse, 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, mdoc->parse, 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_vmsg(MANDOCERR_SM_BAD, mdoc->parse, nch->line, nch->pos, "%s %s", roff_name[mdoc->last->tok], nch->string); mdoc_node_relink(mdoc, nch); return; } static void post_root(POST_ARGS) { const char *openbsd_arch[] = { "alpha", "amd64", "arm64", "armv7", "hppa", "i386", "landisk", "loongson", "luna88k", "macppc", "mips64", "octeon", "sgi", "socppc", "sparc64", NULL }; const char *netbsd_arch[] = { "acorn26", "acorn32", "algor", "alpha", "amiga", "arc", "atari", "bebox", "cats", "cesfic", "cobalt", "dreamcast", "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5", "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa", "i386", "ibmnws", "luna68k", "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc", "netwinder", "news68k", "newsmips", "next68k", "pc532", "playstation2", "pmax", "pmppc", "prep", "sandpoint", "sbmips", "sgimips", "shark", "sparc", "sparc64", "sun2", "sun3", "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL }; const char **arches[] = { NULL, netbsd_arch, openbsd_arch }; struct roff_node *n; const char **arch; /* Add missing prologue data. */ if (mdoc->meta.date == NULL) mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : mandoc_normdate(mdoc, NULL, 0, 0); if (mdoc->meta.title == NULL) { mandoc_msg(MANDOCERR_DT_NOTITLE, mdoc->parse, 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, mdoc->parse, 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, mdoc->parse, 0, 0, mdoc->meta.os_e == MANDOC_OS_OPENBSD ? "(OpenBSD)" : "(NetBSD)"); if (mdoc->meta.arch != NULL && (arch = arches[mdoc->meta.os_e]) != NULL) { while (*arch != NULL && strcmp(*arch, mdoc->meta.arch)) arch++; if (*arch == NULL) { n = mdoc->first->child; while (n->tok != MDOC_Dt) n = n->next; n = n->child->next->next; mandoc_vmsg(MANDOCERR_ARCH_BAD, mdoc->parse, 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->first->child; - while (n != NULL && n->tok != TOKEN_NONE && + while (n != NULL && n->tok >= MDOC_Dd && mdoc_macros[n->tok].flags & MDOC_PROLOGUE) n = n->next; if (n == NULL) mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL); else if (n->tok != MDOC_Sh) mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse, n->line, n->pos, 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, mdoc->parse, 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, mdoc->parse, nch->line, nch->pos, 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 *nch; char *cp; for (nch = mdoc->last->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])) *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, mdoc->parse, n->line, n->pos, NULL); } static void post_sx(POST_ARGS) { post_delim(mdoc); post_hyph(mdoc); } static void post_sh(POST_ARGS) { post_ignpar(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_vmsg(MANDOCERR_NAMESEC_PUNCT, mdoc->parse, 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, mdoc->parse, 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, mdoc->parse, n->line, n->pos, roff_name[n->tok]); continue; } break; } if ( ! hasnm) mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse, mdoc->last->line, mdoc->last->pos, NULL); if ( ! hasnd) mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse, 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_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse, n->line, n->pos, "%s before %s(%s)", lastpunct, name, sec); cmp = strcmp(lastsec, sec); if (cmp > 0) mandoc_vmsg(MANDOCERR_XR_ORDER, mdoc->parse, n->line, n->pos, "%s(%s) after %s(%s)", name, sec, lastname, lastsec); else if (cmp == 0 && strcasecmp(lastname, name) > 0) mandoc_vmsg(MANDOCERR_XR_ORDER, mdoc->parse, 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_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse, 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->parse, 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_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse, 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; } /* 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_vmsg(MANDOCERR_SEC_TYPO, mdoc->parse, 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_vmsg(MANDOCERR_SEC_REP, mdoc->parse, mdoc->last->line, mdoc->last->pos, "Sh %s", secnames[sec]); if (sec < mdoc->lastnamed) mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse, 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_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse, 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_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse, 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_vmsg(MANDOCERR_XR_SELF, mdoc->parse, nch->line, nch->pos, "Xr %s %s", nch->string, nch->next->string); } post_delim_nb(mdoc); } static void post_ignpar(POST_ARGS) { struct roff_node *np; switch (mdoc->last->type) { case ROFFT_BLOCK: post_prevpar(mdoc); return; case ROFFT_HEAD: post_delim(mdoc); post_hyph(mdoc); return; case ROFFT_BODY: break; default: return; } if ((np = mdoc->last->child) != NULL) if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) { mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, np->line, np->pos, "%s after %s", roff_name[np->tok], roff_name[mdoc->last->tok]); roff_node_delete(mdoc, np); } if ((np = mdoc->last->last) != NULL) if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) { mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, np->line, np->pos, "%s at the end of %s", roff_name[np->tok], roff_name[mdoc->last->tok]); roff_node_delete(mdoc, np); } } static void post_prevpar(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (NULL == n->prev) return; if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) return; /* * Don't allow prior `Lp' or `Pp' prior to a paragraph-type * block: `Lp', `Pp', or non-compact `Bd' or `Bl'. */ if (n->prev->tok != MDOC_Pp && n->prev->tok != MDOC_Lp && n->prev->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_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, n->prev->line, n->prev->pos, "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]); roff_node_delete(mdoc, n->prev); } static void post_par(POST_ARGS) { struct roff_node *np; np = mdoc->last; if (np->tok != ROFF_br && np->tok != ROFF_sp) post_prevpar(mdoc); if (np->tok == ROFF_sp) { if (np->child != NULL && np->child->next != NULL) mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, np->child->next->line, np->child->next->pos, "sp ... %s", np->child->next->string); } else if (np->child != NULL) mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, np->line, np->pos, "%s %s", roff_name[np->tok], np->child->string); if ((np = mdoc->last->prev) == NULL) { np = mdoc->last->parent; if (np->tok != MDOC_Sh && np->tok != MDOC_Ss) return; } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp && (mdoc->last->tok != ROFF_br || (np->tok != ROFF_sp && np->tok != ROFF_br))) return; mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, mdoc->last->line, mdoc->last->pos, "%s after %s", roff_name[mdoc->last->tok], roff_name[np->tok]); roff_node_delete(mdoc, mdoc->last); } static void post_dd(POST_ARGS) { struct roff_node *n; char *datestr; n = mdoc->last; n->flags |= NODE_NOPRT; if (mdoc->meta.date != NULL) { mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse, n->line, n->pos, "Dd"); free(mdoc->meta.date); } else if (mdoc->flags & MDOC_PBODY) mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse, n->line, n->pos, "Dd"); else if (mdoc->meta.title != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse, n->line, n->pos, "Dd after Dt"); else if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse, n->line, n->pos, "Dd after Os"); if (n->child == NULL || n->child->string[0] == '\0') { mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : mandoc_normdate(mdoc, NULL, n->line, n->pos); return; } datestr = NULL; deroff(&datestr, n); if (mdoc->quick) mdoc->meta.date = datestr; else { mdoc->meta.date = mandoc_normdate(mdoc, datestr, n->line, n->pos); free(datestr); } } 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, mdoc->parse, n->line, n->pos, "Dt"); return; } if (mdoc->meta.title != NULL) mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse, n->line, n->pos, "Dt"); else if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse, 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, mdoc->parse, 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_vmsg(MANDOCERR_TITLE_CASE, mdoc->parse, nn->line, nn->pos + (p - nn->string), "Dt %s", nn->string); break; } } /* Mandatory second argument: section. */ if (nn != NULL) nn = nn->next; if (nn == NULL) { mandoc_vmsg(MANDOCERR_MSEC_MISSING, mdoc->parse, 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_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse, nn->line, nn->pos, "Dt ... %s", nn->string); mdoc->meta.vol = mandoc_strdup(nn->string); } else mdoc->meta.vol = mandoc_strdup(cp); /* 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_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 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, mdoc->parse, n->line, n->pos, 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; static char *defbuf; #endif struct roff_node *n; n = mdoc->last; n->flags |= NODE_NOPRT; if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse, n->line, n->pos, "Os"); else if (mdoc->flags & MDOC_PBODY) mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse, 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 (defbuf == NULL) { if (uname(&utsname) == -1) { mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse, n->line, n->pos, "Os"); defbuf = mandoc_strdup("UNKNOWN"); } else mandoc_asprintf(&defbuf, "%s %s", utsname.sysname, utsname.release); } mdoc->meta.os = mandoc_strdup(defbuf); #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_vmsg(MANDOCERR_OS_ARG, mdoc->parse, 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_vmsg(MANDOCERR_MDOCDATE_MISSING, mdoc->parse, n->line, n->pos, "Dd %s (OpenBSD)", n->string); } else { if (mdoc->meta.os_e == MANDOC_OS_NETBSD) mandoc_vmsg(MANDOCERR_MDOCDATE, mdoc->parse, 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; } Index: head/contrib/mdocml/tbl_html.c =================================================================== --- head/contrib/mdocml/tbl_html.c (revision 324357) +++ head/contrib/mdocml/tbl_html.c (revision 324358) @@ -1,158 +1,161 @@ -/* $Id: tbl_html.c,v 1.22 2017/06/12 20:14:18 schwarze Exp $ */ +/* $Id: tbl_html.c,v 1.23 2017/07/31 16:14:10 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons * Copyright (c) 2014, 2015, 2017 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. */ #include "config.h" #include #include #include #include #include #include "mandoc.h" #include "out.h" #include "html.h" static void html_tblopen(struct html *, const struct tbl_span *); static size_t html_tbl_len(size_t, void *); static size_t html_tbl_strlen(const char *, void *); static size_t html_tbl_sulen(const struct roffsu *, void *); static size_t html_tbl_len(size_t sz, void *arg) { return sz; } static size_t html_tbl_strlen(const char *p, void *arg) { return strlen(p); } static size_t html_tbl_sulen(const struct roffsu *su, void *arg) { + if (su->scale < 0.0) + return 0; + switch (su->unit) { case SCALE_FS: /* 2^16 basic units */ return su->scale * 65536.0 / 24.0; case SCALE_IN: /* 10 characters per inch */ return su->scale * 10.0; case SCALE_CM: /* 2.54 cm per inch */ return su->scale * 10.0 / 2.54; case SCALE_PC: /* 6 pica per inch */ case SCALE_VS: return su->scale * 10.0 / 6.0; case SCALE_EN: case SCALE_EM: return su->scale; case SCALE_PT: /* 12 points per pica */ return su->scale * 10.0 / 6.0 / 12.0; case SCALE_BU: /* 24 basic units per character */ return su->scale / 24.0; case SCALE_MM: /* 1/1000 inch */ return su->scale / 100.0; default: abort(); } } static void html_tblopen(struct html *h, const struct tbl_span *sp) { struct tag *t; int ic; if (h->tbl.cols == NULL) { h->tbl.len = html_tbl_len; h->tbl.slen = html_tbl_strlen; h->tbl.sulen = html_tbl_sulen; tblcalc(&h->tbl, sp, 0, 0); } assert(NULL == h->tblt); h->tblt = print_otag(h, TAG_TABLE, "c", "tbl"); t = print_otag(h, TAG_COLGROUP, ""); for (ic = 0; ic < sp->opts->cols; ic++) print_otag(h, TAG_COL, "shw", h->tbl.cols[ic].width); print_tagq(h, t); } void print_tblclose(struct html *h) { assert(h->tblt); print_tagq(h, h->tblt); h->tblt = NULL; } void print_tbl(struct html *h, const struct tbl_span *sp) { const struct tbl_dat *dp; struct tag *tt; int ic; /* Inhibit printing of spaces: we do padding ourselves. */ if (h->tblt == NULL) html_tblopen(h, sp); assert(h->tblt); h->flags |= HTML_NONOSPACE; h->flags |= HTML_NOSPACE; tt = print_otag(h, TAG_TR, ""); switch (sp->pos) { case TBL_SPAN_HORIZ: case TBL_SPAN_DHORIZ: print_otag(h, TAG_TD, "?", "colspan", "0"); break; default: dp = sp->first; for (ic = 0; ic < sp->opts->cols; ic++) { print_stagq(h, tt); print_otag(h, TAG_TD, ""); if (dp == NULL || dp->layout->col > ic) continue; if (dp->layout->pos != TBL_CELL_DOWN) if (dp->string != NULL) print_text(h, dp->string); dp = dp->next; } break; } print_tagq(h, tt); h->flags &= ~HTML_NONOSPACE; if (sp->next == NULL) { assert(h->tbl.cols); free(h->tbl.cols); h->tbl.cols = NULL; print_tblclose(h); } } Index: head/contrib/mdocml/tbl_term.c =================================================================== --- head/contrib/mdocml/tbl_term.c (revision 324357) +++ head/contrib/mdocml/tbl_term.c (revision 324358) @@ -1,673 +1,676 @@ -/* $Id: tbl_term.c,v 1.56 2017/07/08 13:43:15 schwarze Exp $ */ +/* $Id: tbl_term.c,v 1.57 2017/07/31 16:14:10 schwarze Exp $ */ /* * Copyright (c) 2009, 2011 Kristaps Dzonsons * Copyright (c) 2011,2012,2014,2015,2017 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. */ #include "config.h" #include #include #include #include #include #include "mandoc.h" #include "out.h" #include "term.h" #define IS_HORIZ(cp) ((cp)->pos == TBL_CELL_HORIZ || \ (cp)->pos == TBL_CELL_DHORIZ) static size_t term_tbl_len(size_t, void *); static size_t term_tbl_strlen(const char *, void *); static size_t term_tbl_sulen(const struct roffsu *, void *); static void tbl_char(struct termp *, char, size_t); static void tbl_data(struct termp *, const struct tbl_opts *, const struct tbl_cell *, const struct tbl_dat *, const struct roffcol *); static void tbl_literal(struct termp *, const struct tbl_dat *, const struct roffcol *); static void tbl_number(struct termp *, const struct tbl_opts *, const struct tbl_dat *, const struct roffcol *); static void tbl_hrule(struct termp *, const struct tbl_span *, int); static void tbl_word(struct termp *, const struct tbl_dat *); static size_t term_tbl_sulen(const struct roffsu *su, void *arg) { - return term_hen((const struct termp *)arg, su); + int i; + + i = term_hen((const struct termp *)arg, su); + return i > 0 ? i : 0; } static size_t term_tbl_strlen(const char *p, void *arg) { return term_strlen((const struct termp *)arg, p); } static size_t term_tbl_len(size_t sz, void *arg) { return term_len((const struct termp *)arg, sz); } void term_tbl(struct termp *tp, const struct tbl_span *sp) { const struct tbl_cell *cp, *cpn, *cpp; const struct tbl_dat *dp; static size_t offset; size_t coloff, tsz; int ic, horiz, spans, vert, more; char fc; /* Inhibit printing of spaces: we do padding ourselves. */ tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE; /* * The first time we're invoked for a given table block, * calculate the table widths and decimal positions. */ if (tp->tbl.cols == NULL) { tp->tbl.len = term_tbl_len; tp->tbl.slen = term_tbl_strlen; tp->tbl.sulen = term_tbl_sulen; tp->tbl.arg = tp; tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin); /* Tables leak .ta settings to subsequent text. */ term_tab_set(tp, NULL); coloff = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) || sp->opts->lvert; for (ic = 0; ic < sp->opts->cols; ic++) { coloff += tp->tbl.cols[ic].width; term_tab_iset(coloff); coloff += tp->tbl.cols[ic].spacing; } /* Center the table as a whole. */ offset = tp->tcol->offset; if (sp->opts->opts & TBL_OPT_CENTRE) { tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ? 2 : !!sp->opts->lvert + !!sp->opts->rvert; for (ic = 0; ic + 1 < sp->opts->cols; ic++) tsz += tp->tbl.cols[ic].width + tp->tbl.cols[ic].spacing; if (sp->opts->cols) tsz += tp->tbl.cols[sp->opts->cols - 1].width; if (offset + tsz > tp->tcol->rmargin) tsz -= 1; tp->tcol->offset = offset + tp->tcol->rmargin > tsz ? (offset + tp->tcol->rmargin - tsz) / 2 : 0; } /* Horizontal frame at the start of boxed tables. */ if (sp->opts->opts & TBL_OPT_DBOX) tbl_hrule(tp, sp, 3); if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) tbl_hrule(tp, sp, 2); } /* Set up the columns. */ tp->flags |= TERMP_MULTICOL; horiz = 0; switch (sp->pos) { case TBL_SPAN_HORIZ: case TBL_SPAN_DHORIZ: horiz = 1; term_setcol(tp, 1); break; case TBL_SPAN_DATA: term_setcol(tp, sp->opts->cols + 2); coloff = tp->tcol->offset; /* Set up a column for a left vertical frame. */ if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) || sp->opts->lvert) coloff++; tp->tcol->rmargin = coloff; /* Set up the data columns. */ dp = sp->first; spans = 0; for (ic = 0; ic < sp->opts->cols; ic++) { if (spans == 0) { tp->tcol++; tp->tcol->offset = coloff; } coloff += tp->tbl.cols[ic].width; tp->tcol->rmargin = coloff; if (ic + 1 < sp->opts->cols) coloff += tp->tbl.cols[ic].spacing; if (spans) { spans--; continue; } if (dp == NULL) continue; spans = dp->spans; if (ic || sp->layout->first->pos != TBL_CELL_SPAN) dp = dp->next; } /* Set up a column for a right vertical frame. */ tp->tcol++; tp->tcol->offset = coloff + 1; tp->tcol->rmargin = tp->maxrmargin; /* Spans may have reduced the number of columns. */ tp->lasttcol = tp->tcol - tp->tcols; /* Fill the buffers for all data columns. */ tp->tcol = tp->tcols; cp = cpn = sp->layout->first; dp = sp->first; spans = 0; for (ic = 0; ic < sp->opts->cols; ic++) { if (cpn != NULL) { cp = cpn; cpn = cpn->next; } if (spans) { spans--; continue; } tp->tcol++; tp->col = 0; tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic); if (dp == NULL) continue; spans = dp->spans; if (cp->pos != TBL_CELL_SPAN) dp = dp->next; } break; } do { /* Print the vertical frame at the start of each row. */ tp->tcol = tp->tcols; fc = '\0'; if (sp->layout->vert || (sp->next != NULL && sp->next->layout->vert && sp->next->pos == TBL_SPAN_DATA) || (sp->prev != NULL && sp->prev->layout->vert && (horiz || (IS_HORIZ(sp->layout->first) && !IS_HORIZ(sp->prev->layout->first)))) || sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)) fc = horiz || IS_HORIZ(sp->layout->first) ? '+' : '|'; else if (horiz && sp->opts->lvert) fc = '-'; if (fc != '\0') { (*tp->advance)(tp, tp->tcols->offset); (*tp->letter)(tp, fc); tp->viscol = tp->tcol->offset + 1; } /* Print the data cells. */ more = 0; if (horiz) { tbl_hrule(tp, sp, 0); term_flushln(tp); } else { cp = sp->layout->first; cpn = sp->next == NULL ? NULL : sp->next->layout->first; cpp = sp->prev == NULL ? NULL : sp->prev->layout->first; dp = sp->first; spans = 0; for (ic = 0; ic < sp->opts->cols; ic++) { /* * Figure out whether to print a * vertical line after this cell * and advance to next layout cell. */ if (cp != NULL) { vert = cp->vert; switch (cp->pos) { case TBL_CELL_HORIZ: fc = '-'; break; case TBL_CELL_DHORIZ: fc = '='; break; default: fc = ' '; break; } } else { vert = 0; fc = ' '; } if (cpp != NULL) { if (vert == 0 && cp != NULL && ((IS_HORIZ(cp) && !IS_HORIZ(cpp)) || (cp->next != NULL && cpp->next != NULL && IS_HORIZ(cp->next) && !IS_HORIZ(cpp->next)))) vert = cpp->vert; cpp = cpp->next; } if (vert == 0 && sp->opts->opts & TBL_OPT_ALLBOX) vert = 1; if (cpn != NULL) { if (vert == 0) vert = cpn->vert; cpn = cpn->next; } if (cp != NULL) cp = cp->next; /* * Skip later cells in a span, * figure out whether to start a span, * and advance to next data cell. */ if (spans) { spans--; continue; } if (dp != NULL) { spans = dp->spans; if (ic || sp->layout->first->pos != TBL_CELL_SPAN) dp = dp->next; } /* * Print one line of text in the cell * and remember whether there is more. */ tp->tcol++; if (tp->tcol->col < tp->tcol->lastcol) term_flushln(tp); if (tp->tcol->col < tp->tcol->lastcol) more = 1; /* * Vertical frames between data cells, * but not after the last column. */ if (fc == ' ' && ((vert == 0 && (cp == NULL || !IS_HORIZ(cp))) || tp->tcol + 1 == tp->tcols + tp->lasttcol)) continue; if (tp->viscol < tp->tcol->rmargin) { (*tp->advance)(tp, tp->tcol->rmargin - tp->viscol); tp->viscol = tp->tcol->rmargin; } while (tp->viscol < tp->tcol->rmargin + tp->tbl.cols[ic].spacing / 2) { (*tp->letter)(tp, fc); tp->viscol++; } if (tp->tcol + 1 == tp->tcols + tp->lasttcol) continue; if (fc == ' ' && cp != NULL) { switch (cp->pos) { case TBL_CELL_HORIZ: fc = '-'; break; case TBL_CELL_DHORIZ: fc = '='; break; default: break; } } if (tp->tbl.cols[ic].spacing) { (*tp->letter)(tp, fc == ' ' ? '|' : vert ? '+' : fc); tp->viscol++; } if (fc != ' ') { if (cp != NULL && cp->pos == TBL_CELL_HORIZ) fc = '-'; else if (cp != NULL && cp->pos == TBL_CELL_DHORIZ) fc = '='; else fc = ' '; } if (tp->tbl.cols[ic].spacing > 2 && (vert > 1 || fc != ' ')) { (*tp->letter)(tp, fc == ' ' ? '|' : vert > 1 ? '+' : fc); tp->viscol++; } } } /* Print the vertical frame at the end of each row. */ fc = '\0'; if ((sp->layout->last->vert && sp->layout->last->col + 1 == sp->opts->cols) || (sp->next != NULL && sp->next->layout->last->vert && sp->next->layout->last->col + 1 == sp->opts->cols) || (sp->prev != NULL && sp->prev->layout->last->vert && sp->prev->layout->last->col + 1 == sp->opts->cols && (horiz || (IS_HORIZ(sp->layout->last) && !IS_HORIZ(sp->prev->layout->last)))) || (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))) fc = horiz || IS_HORIZ(sp->layout->last) ? '+' : '|'; else if (horiz && sp->opts->rvert) fc = '-'; if (fc != '\0') { if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 || sp->layout->last->col + 1 < sp->opts->cols)) { tp->tcol++; (*tp->advance)(tp, tp->tcol->offset > tp->viscol ? tp->tcol->offset - tp->viscol : 1); } (*tp->letter)(tp, fc); } (*tp->endline)(tp); tp->viscol = 0; } while (more); /* * Clean up after this row. If it is the last line * of the table, print the box line and clean up * column data; otherwise, print the allbox line. */ term_setcol(tp, 1); tp->flags &= ~TERMP_MULTICOL; tp->tcol->rmargin = tp->maxrmargin; if (sp->next == NULL) { if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) { tbl_hrule(tp, sp, 2); tp->skipvsp = 1; } if (sp->opts->opts & TBL_OPT_DBOX) { tbl_hrule(tp, sp, 3); tp->skipvsp = 2; } assert(tp->tbl.cols); free(tp->tbl.cols); tp->tbl.cols = NULL; tp->tcol->offset = offset; } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX && (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA || sp->next->next != NULL)) tbl_hrule(tp, sp, 1); tp->flags &= ~TERMP_NONOSPACE; } /* * Kinds of horizontal rulers: * 0: inside the table (single or double line with crossings) * 1: inside the table (single or double line with crossings and ends) * 2: inner frame (single line with crossings and ends) * 3: outer frame (single line without crossings with ends) */ static void tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind) { const struct tbl_cell *cp, *cpn, *cpp; const struct roffcol *col; int vert; char line, cross; line = (kind < 2 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-'; cross = (kind < 3) ? '+' : '-'; if (kind) term_word(tp, "+"); cp = sp->layout->first; cpp = kind || sp->prev == NULL ? NULL : sp->prev->layout->first; if (cpp == cp) cpp = NULL; cpn = kind > 1 || sp->next == NULL ? NULL : sp->next->layout->first; if (cpn == cp) cpn = NULL; for (;;) { col = tp->tbl.cols + cp->col; tbl_char(tp, line, col->width + col->spacing / 2); vert = cp->vert; if ((cp = cp->next) == NULL) break; if (cpp != NULL) { if (vert < cpp->vert) vert = cpp->vert; cpp = cpp->next; } if (cpn != NULL) { if (vert < cpn->vert) vert = cpn->vert; cpn = cpn->next; } if (sp->opts->opts & TBL_OPT_ALLBOX && !vert) vert = 1; if (col->spacing) tbl_char(tp, vert ? cross : line, 1); if (col->spacing > 2) tbl_char(tp, vert > 1 ? cross : line, 1); if (col->spacing > 4) tbl_char(tp, line, (col->spacing - 3) / 2); } if (kind) { term_word(tp, "+"); term_flushln(tp); } } static void tbl_data(struct termp *tp, const struct tbl_opts *opts, const struct tbl_cell *cp, const struct tbl_dat *dp, const struct roffcol *col) { switch (cp->pos) { case TBL_CELL_HORIZ: tbl_char(tp, '-', col->width); return; case TBL_CELL_DHORIZ: tbl_char(tp, '=', col->width); return; default: break; } if (dp == NULL) return; switch (dp->pos) { case TBL_DATA_NONE: return; case TBL_DATA_HORIZ: case TBL_DATA_NHORIZ: tbl_char(tp, '-', col->width); return; case TBL_DATA_NDHORIZ: case TBL_DATA_DHORIZ: tbl_char(tp, '=', col->width); return; default: break; } switch (cp->pos) { case TBL_CELL_LONG: case TBL_CELL_CENTRE: case TBL_CELL_LEFT: case TBL_CELL_RIGHT: tbl_literal(tp, dp, col); break; case TBL_CELL_NUMBER: tbl_number(tp, opts, dp, col); break; case TBL_CELL_DOWN: case TBL_CELL_SPAN: break; default: abort(); } } static void tbl_char(struct termp *tp, char c, size_t len) { size_t i, sz; char cp[2]; cp[0] = c; cp[1] = '\0'; sz = term_strlen(tp, cp); for (i = 0; i < len; i += sz) term_word(tp, cp); } static void tbl_literal(struct termp *tp, const struct tbl_dat *dp, const struct roffcol *col) { size_t len, padl, padr, width; int ic, spans; assert(dp->string); len = term_strlen(tp, dp->string); width = col->width; ic = dp->layout->col; spans = dp->spans; while (spans--) width += tp->tbl.cols[++ic].width + 3; padr = width > len ? width - len : 0; padl = 0; switch (dp->layout->pos) { case TBL_CELL_LONG: padl = term_len(tp, 1); padr = padr > padl ? padr - padl : 0; break; case TBL_CELL_CENTRE: if (2 > padr) break; padl = padr / 2; padr -= padl; break; case TBL_CELL_RIGHT: padl = padr; padr = 0; break; default: break; } tbl_char(tp, ASCII_NBRSP, padl); tbl_word(tp, dp); tbl_char(tp, ASCII_NBRSP, padr); } static void tbl_number(struct termp *tp, const struct tbl_opts *opts, const struct tbl_dat *dp, const struct roffcol *col) { char *cp; char buf[2]; size_t sz, psz, ssz, d, padl; int i; /* * See calc_data_number(). Left-pad by taking the offset of our * and the maximum decimal; right-pad by the remaining amount. */ assert(dp->string); sz = term_strlen(tp, dp->string); buf[0] = opts->decimal; buf[1] = '\0'; psz = term_strlen(tp, buf); if ((cp = strrchr(dp->string, opts->decimal)) != NULL) { for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { buf[0] = dp->string[i]; ssz += term_strlen(tp, buf); } d = ssz + psz; } else d = sz + psz; if (col->decimal > d && col->width > sz) { padl = col->decimal - d; if (padl + sz > col->width) padl = col->width - sz; tbl_char(tp, ASCII_NBRSP, padl); } else padl = 0; tbl_word(tp, dp); if (col->width > sz + padl) tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); } static void tbl_word(struct termp *tp, const struct tbl_dat *dp) { int prev_font; prev_font = tp->fonti; if (dp->layout->flags & TBL_CELL_BOLD) term_fontpush(tp, TERMFONT_BOLD); else if (dp->layout->flags & TBL_CELL_ITALIC) term_fontpush(tp, TERMFONT_UNDER); term_word(tp, dp->string); term_fontpopq(tp, prev_font); } Index: head/contrib/mdocml =================================================================== --- head/contrib/mdocml (revision 324357) +++ head/contrib/mdocml (revision 324358) Property changes on: head/contrib/mdocml ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /vendor/mdocml/dist:r321809-324357