1 /*      $Id: tag.c,v 1.18 2017/02/17 14:31:52 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2015, 2016 Ingo Schwarze <schwarze@openbsd.org>
   4  *
   5  * Permission to use, copy, modify, and distribute this software for any
   6  * purpose with or without fee is hereby granted, provided that the above
   7  * copyright notice and this permission notice appear in all copies.
   8  *
   9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16  */
  17 #include "config.h"
  18 
  19 #include <sys/types.h>
  20 
  21 #include <signal.h>
  22 #include <stddef.h>
  23 #include <stdint.h>
  24 #include <stdio.h>
  25 #include <stdlib.h>
  26 #include <string.h>
  27 #include <unistd.h>
  28 
  29 #include "mandoc_aux.h"
  30 #include "mandoc_ohash.h"
  31 #include "tag.h"
  32 
  33 struct tag_entry {
  34         size_t  *lines;
  35         size_t   maxlines;
  36         size_t   nlines;
  37         int      prio;
  38         char     s[];
  39 };
  40 
  41 static  void     tag_signal(int) __attribute__((__noreturn__));
  42 
  43 static struct ohash      tag_data;
  44 static struct tag_files  tag_files;
  45 
  46 
  47 /*
  48  * Prepare for using a pager.
  49  * Not all pagers are capable of using a tag file,
  50  * but for simplicity, create it anyway.
  51  */
  52 struct tag_files *
  53 tag_init(void)
  54 {
  55         struct sigaction         sa;
  56         int                      ofd;
  57 
  58         ofd = -1;
  59         tag_files.tfd = -1;
  60         tag_files.tcpgid = -1;
  61 
  62         /* Clean up when dying from a signal. */
  63 
  64         memset(&sa, 0, sizeof(sa));
  65         sigfillset(&sa.sa_mask);
  66         sa.sa_handler = tag_signal;
  67         sigaction(SIGHUP, &sa, NULL);
  68         sigaction(SIGINT, &sa, NULL);
  69         sigaction(SIGTERM, &sa, NULL);
  70 
  71         /*
  72          * POSIX requires that a process calling tcsetpgrp(3)
  73          * from the background gets a SIGTTOU signal.
  74          * In that case, do not stop.
  75          */
  76 
  77         sa.sa_handler = SIG_IGN;
  78         sigaction(SIGTTOU, &sa, NULL);
  79 
  80         /* Save the original standard output for use by the pager. */
  81 
  82         if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1)
  83                 goto fail;
  84 
  85         /* Create both temporary output files. */
  86 
  87         (void)strlcpy(tag_files.ofn, "/tmp/man.XXXXXXXXXX",
  88             sizeof(tag_files.ofn));
  89         (void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX",
  90             sizeof(tag_files.tfn));
  91         if ((ofd = mkstemp(tag_files.ofn)) == -1)
  92                 goto fail;
  93         if ((tag_files.tfd = mkstemp(tag_files.tfn)) == -1)
  94                 goto fail;
  95         if (dup2(ofd, STDOUT_FILENO) == -1)
  96                 goto fail;
  97         close(ofd);
  98 
  99         /*
 100          * Set up the ohash table to collect output line numbers
 101          * where various marked-up terms are documented.
 102          */
 103 
 104         mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
 105         return &tag_files;
 106 
 107 fail:
 108         tag_unlink();
 109         if (ofd != -1)
 110                 close(ofd);
 111         if (tag_files.ofd != -1)
 112                 close(tag_files.ofd);
 113         if (tag_files.tfd != -1)
 114                 close(tag_files.tfd);
 115         *tag_files.ofn = '\0';
 116         *tag_files.tfn = '\0';
 117         tag_files.ofd = -1;
 118         tag_files.tfd = -1;
 119         return NULL;
 120 }
 121 
 122 /*
 123  * Set the line number where a term is defined,
 124  * unless it is already defined at a higher priority.
 125  */
 126 void
 127 tag_put(const char *s, int prio, size_t line)
 128 {
 129         struct tag_entry        *entry;
 130         size_t                   len;
 131         unsigned int             slot;
 132 
 133         /* Sanity checks. */
 134 
 135         if (tag_files.tfd <= 0)
 136                 return;
 137         if (s[0] == '\\' && (s[1] == '&' || s[1] == 'e'))
 138                 s += 2;
 139         if (*s == '\0' || strchr(s, ' ') != NULL)
 140                 return;
 141 
 142         slot = ohash_qlookup(&tag_data, s);
 143         entry = ohash_find(&tag_data, slot);
 144 
 145         if (entry == NULL) {
 146 
 147                 /* Build a new entry. */
 148 
 149                 len = strlen(s) + 1;
 150                 entry = mandoc_malloc(sizeof(*entry) + len);
 151                 memcpy(entry->s, s, len);
 152                 entry->lines = NULL;
 153                 entry->maxlines = entry->nlines = 0;
 154                 ohash_insert(&tag_data, slot, entry);
 155 
 156         } else {
 157 
 158                 /* Handle priority 0 entries. */
 159 
 160                 if (prio == 0) {
 161                         if (entry->prio == 0)
 162                                 entry->prio = -1;
 163                         return;
 164                 }
 165 
 166                 /* A better entry is already present, ignore the new one. */
 167 
 168                 if (entry->prio > 0 && entry->prio < prio)
 169                         return;
 170 
 171                 /* The existing entry is worse, clear it. */
 172 
 173                 if (entry->prio < 1 || entry->prio > prio)
 174                         entry->nlines = 0;
 175         }
 176 
 177         /* Remember the new line. */
 178 
 179         if (entry->maxlines == entry->nlines) {
 180                 entry->maxlines += 4;
 181                 entry->lines = mandoc_reallocarray(entry->lines,
 182                     entry->maxlines, sizeof(*entry->lines));
 183         }
 184         entry->lines[entry->nlines++] = line;
 185         entry->prio = prio;
 186 }
 187 
 188 /*
 189  * Write out the tags file using the previously collected
 190  * information and clear the ohash table while going along.
 191  */
 192 void
 193 tag_write(void)
 194 {
 195         FILE                    *stream;
 196         struct tag_entry        *entry;
 197         size_t                   i;
 198         unsigned int             slot;
 199 
 200         if (tag_files.tfd <= 0)
 201                 return;
 202         stream = fdopen(tag_files.tfd, "w");
 203         entry = ohash_first(&tag_data, &slot);
 204         while (entry != NULL) {
 205                 if (stream != NULL && entry->prio >= 0)
 206                         for (i = 0; i < entry->nlines; i++)
 207                                 fprintf(stream, "%s %s %zu\n",
 208                                     entry->s, tag_files.ofn, entry->lines[i]);
 209                 free(entry->lines);
 210                 free(entry);
 211                 entry = ohash_next(&tag_data, &slot);
 212         }
 213         ohash_delete(&tag_data);
 214         if (stream != NULL)
 215                 fclose(stream);
 216 }
 217 
 218 void
 219 tag_unlink(void)
 220 {
 221         pid_t    tc_pgid;
 222 
 223         if (tag_files.tcpgid != -1) {
 224                 tc_pgid = tcgetpgrp(tag_files.ofd);
 225                 if (tc_pgid == tag_files.pager_pid ||
 226                     tc_pgid == getpgid(0) ||
 227                     getpgid(tc_pgid) == -1)
 228                         (void)tcsetpgrp(tag_files.ofd, tag_files.tcpgid);
 229         }
 230         if (*tag_files.ofn != '\0')
 231                 unlink(tag_files.ofn);
 232         if (*tag_files.tfn != '\0')
 233                 unlink(tag_files.tfn);
 234 }
 235 
 236 static void
 237 tag_signal(int signum)
 238 {
 239         struct sigaction         sa;
 240 
 241         tag_unlink();
 242         memset(&sa, 0, sizeof(sa));
 243         sigemptyset(&sa.sa_mask);
 244         sa.sa_handler = SIG_DFL;
 245         sigaction(signum, &sa, NULL);
 246         kill(getpid(), signum);
 247         /* NOTREACHED */
 248         _exit(1);
 249 }