-/* $Id: tag.c,v 1.2 2015/07/18 03:41:37 schwarze Exp $ */
+/* $Id: tag.c,v 1.18 2017/02/17 14:31:52 schwarze Exp $ */
/*
- * Copyright (c) 2015 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2015, 2016 Ingo Schwarze <schwarze@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* 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 <sys/types.h>
#include <signal.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#if HAVE_OHASH
-#include <ohash.h>
-#else
-#include "compat_ohash.h"
-#endif
-
#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
#include "tag.h"
struct tag_entry {
- size_t line;
+ size_t *lines;
+ size_t maxlines;
+ size_t nlines;
+ int prio;
char s[];
};
-static void tag_signal(int);
-static void *tag_alloc(size_t, void *);
-static void tag_free(void *, void *);
-static void *tag_calloc(size_t, size_t, void *);
+static void tag_signal(int) __attribute__((__noreturn__));
static struct ohash tag_data;
-static char *tag_fn = NULL;
-static int tag_fd = -1;
+static struct tag_files tag_files;
/*
- * Set up the ohash table to collect output line numbers
- * where various marked-up terms are documented and create
- * the temporary tags file, saving the name for the pager.
+ * Prepare for using a pager.
+ * Not all pagers are capable of using a tag file,
+ * but for simplicity, create it anyway.
*/
-void
+struct tag_files *
tag_init(void)
{
- struct ohash_info tag_info;
-
- tag_fn = mandoc_strdup("/tmp/man.XXXXXXXXXX");
- signal(SIGHUP, tag_signal);
- signal(SIGINT, tag_signal);
- signal(SIGTERM, tag_signal);
- if ((tag_fd = mkstemp(tag_fn)) == -1) {
- free(tag_fn);
- tag_fn = NULL;
- return;
- }
-
- tag_info.alloc = tag_alloc;
- tag_info.calloc = tag_calloc;
- tag_info.free = tag_free;
- tag_info.key_offset = offsetof(struct tag_entry, s);
- tag_info.data = NULL;
- ohash_init(&tag_data, 4, &tag_info);
-}
-
-char *
-tag_filename(void)
-{
-
- return(tag_fn);
-}
-
-/*
- * Return the line number where a term is defined,
- * or 0 if the term is unknown.
- */
-size_t
-tag_get(const char *s, size_t len)
-{
- struct tag_entry *entry;
- const char *end;
- unsigned int slot;
-
- if (tag_fd == -1)
- return(0);
- if (len == 0)
- len = strlen(s);
- end = s + len;
- slot = ohash_qlookupi(&tag_data, s, &end);
- entry = ohash_find(&tag_data, slot);
- return(entry == NULL ? 0 : entry->line);
+ struct sigaction sa;
+ int ofd;
+
+ ofd = -1;
+ tag_files.tfd = -1;
+ tag_files.tcpgid = -1;
+
+ /* Clean up when dying from a signal. */
+
+ memset(&sa, 0, sizeof(sa));
+ sigfillset(&sa.sa_mask);
+ sa.sa_handler = tag_signal;
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ /*
+ * POSIX requires that a process calling tcsetpgrp(3)
+ * from the background gets a SIGTTOU signal.
+ * In that case, do not stop.
+ */
+
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGTTOU, &sa, NULL);
+
+ /* Save the original standard output for use by the pager. */
+
+ if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1)
+ goto fail;
+
+ /* Create both temporary output files. */
+
+ (void)strlcpy(tag_files.ofn, "/tmp/man.XXXXXXXXXX",
+ sizeof(tag_files.ofn));
+ (void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX",
+ sizeof(tag_files.tfn));
+ if ((ofd = mkstemp(tag_files.ofn)) == -1)
+ goto fail;
+ if ((tag_files.tfd = mkstemp(tag_files.tfn)) == -1)
+ goto fail;
+ if (dup2(ofd, STDOUT_FILENO) == -1)
+ goto fail;
+ close(ofd);
+
+ /*
+ * Set up the ohash table to collect output line numbers
+ * where various marked-up terms are documented.
+ */
+
+ mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
+ return &tag_files;
+
+fail:
+ tag_unlink();
+ if (ofd != -1)
+ close(ofd);
+ if (tag_files.ofd != -1)
+ close(tag_files.ofd);
+ if (tag_files.tfd != -1)
+ close(tag_files.tfd);
+ *tag_files.ofn = '\0';
+ *tag_files.tfn = '\0';
+ tag_files.ofd = -1;
+ tag_files.tfd = -1;
+ return NULL;
}
/*
- * Set the line number where a term is defined.
+ * Set the line number where a term is defined,
+ * unless it is already defined at a higher priority.
*/
void
-tag_put(const char *s, size_t len, size_t line)
+tag_put(const char *s, int prio, size_t line)
{
struct tag_entry *entry;
- const char *end;
+ size_t len;
unsigned int slot;
- if (tag_fd == -1)
+ /* Sanity checks. */
+
+ if (tag_files.tfd <= 0)
return;
- if (len == 0)
- len = strlen(s);
- end = s + len;
- slot = ohash_qlookupi(&tag_data, s, &end);
+ if (s[0] == '\\' && (s[1] == '&' || s[1] == 'e'))
+ s += 2;
+ if (*s == '\0' || strchr(s, ' ') != NULL)
+ return;
+
+ slot = ohash_qlookup(&tag_data, s);
entry = ohash_find(&tag_data, slot);
+
if (entry == NULL) {
- entry = mandoc_malloc(sizeof(*entry) + len + 1);
+
+ /* Build a new entry. */
+
+ len = strlen(s) + 1;
+ entry = mandoc_malloc(sizeof(*entry) + len);
memcpy(entry->s, s, len);
- entry->s[len] = '\0';
+ entry->lines = NULL;
+ entry->maxlines = entry->nlines = 0;
ohash_insert(&tag_data, slot, entry);
+
+ } else {
+
+ /* Handle priority 0 entries. */
+
+ if (prio == 0) {
+ if (entry->prio == 0)
+ entry->prio = -1;
+ return;
+ }
+
+ /* A better entry is already present, ignore the new one. */
+
+ if (entry->prio > 0 && entry->prio < prio)
+ return;
+
+ /* The existing entry is worse, clear it. */
+
+ if (entry->prio < 1 || entry->prio > prio)
+ entry->nlines = 0;
}
- entry->line = line;
+
+ /* Remember the new line. */
+
+ if (entry->maxlines == entry->nlines) {
+ entry->maxlines += 4;
+ entry->lines = mandoc_reallocarray(entry->lines,
+ entry->maxlines, sizeof(*entry->lines));
+ }
+ entry->lines[entry->nlines++] = line;
+ entry->prio = prio;
}
/*
{
FILE *stream;
struct tag_entry *entry;
+ size_t i;
unsigned int slot;
- if (tag_fd == -1)
+ if (tag_files.tfd <= 0)
return;
- stream = fdopen(tag_fd, "w");
+ stream = fdopen(tag_files.tfd, "w");
entry = ohash_first(&tag_data, &slot);
while (entry != NULL) {
- if (stream != NULL)
- fprintf(stream, "%s - %zu\n", entry->s, entry->line);
+ if (stream != NULL && entry->prio >= 0)
+ for (i = 0; i < entry->nlines; i++)
+ fprintf(stream, "%s %s %zu\n",
+ entry->s, tag_files.ofn, entry->lines[i]);
+ free(entry->lines);
free(entry);
entry = ohash_next(&tag_data, &slot);
}
void
tag_unlink(void)
{
-
- if (tag_fn != NULL)
- unlink(tag_fn);
+ pid_t tc_pgid;
+
+ if (tag_files.tcpgid != -1) {
+ tc_pgid = tcgetpgrp(tag_files.ofd);
+ if (tc_pgid == tag_files.pager_pid ||
+ tc_pgid == getpgid(0) ||
+ getpgid(tc_pgid) == -1)
+ (void)tcsetpgrp(tag_files.ofd, tag_files.tcpgid);
+ }
+ if (*tag_files.ofn != '\0')
+ unlink(tag_files.ofn);
+ if (*tag_files.tfn != '\0')
+ unlink(tag_files.tfn);
}
static void
tag_signal(int signum)
{
+ struct sigaction sa;
tag_unlink();
- signal(signum, SIG_DFL);
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sigaction(signum, &sa, NULL);
kill(getpid(), signum);
/* NOTREACHED */
_exit(1);
}
-
-/*
- * Memory management callback functions for ohash.
- */
-static void *
-tag_alloc(size_t sz, void *arg)
-{
-
- return(mandoc_malloc(sz));
-}
-
-static void *
-tag_calloc(size_t nmemb, size_t sz, void *arg)
-{
-
- return(mandoc_calloc(nmemb, sz));
-}
-
-static void
-tag_free(void *p, void *arg)
-{
-
- free(p);
-}