1 /* $Id: tag.c,v 1.21 2018/11/22 11:30:23 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2015, 2016, 2018 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 #if HAVE_ERR 22 #include <err.h> 23 #endif 24 #include <limits.h> 25 #include <signal.h> 26 #include <stddef.h> 27 #include <stdint.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <unistd.h> 32 33 #include "mandoc_aux.h" 34 #include "mandoc_ohash.h" 35 #include "tag.h" 36 37 struct tag_entry { 38 size_t *lines; 39 size_t maxlines; 40 size_t nlines; 41 int prio; 42 char s[]; 43 }; 44 45 static void tag_signal(int) __attribute__((__noreturn__)); 46 47 static struct ohash tag_data; 48 static struct tag_files tag_files; 49 50 51 /* 52 * Prepare for using a pager. 53 * Not all pagers are capable of using a tag file, 54 * but for simplicity, create it anyway. 55 */ 56 struct tag_files * 57 tag_init(void) 58 { 59 struct sigaction sa; 60 int ofd; 61 62 ofd = -1; 63 tag_files.tfd = -1; 64 tag_files.tcpgid = -1; 65 66 /* Clean up when dying from a signal. */ 67 68 memset(&sa, 0, sizeof(sa)); 69 sigfillset(&sa.sa_mask); 70 sa.sa_handler = tag_signal; 71 sigaction(SIGHUP, &sa, NULL); 72 sigaction(SIGINT, &sa, NULL); 73 sigaction(SIGTERM, &sa, NULL); 74 75 /* 76 * POSIX requires that a process calling tcsetpgrp(3) 77 * from the background gets a SIGTTOU signal. 78 * In that case, do not stop. 79 */ 80 81 sa.sa_handler = SIG_IGN; 82 sigaction(SIGTTOU, &sa, NULL); 83 84 /* Save the original standard output for use by the pager. */ 85 86 if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1) 87 goto fail; 88 89 /* Create both temporary output files. */ 90 91 (void)strlcpy(tag_files.ofn, "/tmp/man.XXXXXXXXXX", 92 sizeof(tag_files.ofn)); 93 (void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX", 94 sizeof(tag_files.tfn)); 95 if ((ofd = mkstemp(tag_files.ofn)) == -1) 96 goto fail; 97 if ((tag_files.tfd = mkstemp(tag_files.tfn)) == -1) 98 goto fail; 99 if (dup2(ofd, STDOUT_FILENO) == -1) 100 goto fail; 101 close(ofd); 102 103 /* 104 * Set up the ohash table to collect output line numbers 105 * where various marked-up terms are documented. 106 */ 107 108 mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s)); 109 return &tag_files; 110 111 fail: 112 tag_unlink(); 113 if (ofd != -1) 114 close(ofd); 115 if (tag_files.ofd != -1) 116 close(tag_files.ofd); 117 if (tag_files.tfd != -1) 118 close(tag_files.tfd); 119 *tag_files.ofn = '\0'; 120 *tag_files.tfn = '\0'; 121 tag_files.ofd = -1; 122 tag_files.tfd = -1; 123 return NULL; 124 } 125 126 /* 127 * Set the line number where a term is defined, 128 * unless it is already defined at a lower priority. 129 */ 130 void 131 tag_put(const char *s, int prio, size_t line) 132 { 133 struct tag_entry *entry; 134 const char *se; 135 size_t len; 136 unsigned int slot; 137 138 if (tag_files.tfd <= 0) 139 return; 140 141 if (s[0] == '\\' && (s[1] == '&' || s[1] == 'e')) 142 s += 2; 143 144 /* 145 * Skip whitespace and whatever follows it, 146 * and if there is any, downgrade the priority. 147 */ 148 149 len = strcspn(s, " \t"); 150 if (len == 0) 151 return; 152 153 se = s + len; 154 if (*se != '\0') 155 prio = INT_MAX; 156 157 slot = ohash_qlookupi(&tag_data, s, &se); 158 entry = ohash_find(&tag_data, slot); 159 160 if (entry == NULL) { 161 162 /* Build a new entry. */ 163 164 entry = mandoc_malloc(sizeof(*entry) + len + 1); 165 memcpy(entry->s, s, len); 166 entry->s[len] = '\0'; 167 entry->lines = NULL; 168 entry->maxlines = entry->nlines = 0; 169 ohash_insert(&tag_data, slot, entry); 170 171 } else { 172 173 /* 174 * Lower priority numbers take precedence, 175 * but 0 is special. 176 * A tag with priority 0 is only used 177 * if the tag occurs exactly once. 178 */ 179 180 if (prio == 0) { 181 if (entry->prio == 0) 182 entry->prio = -1; 183 return; 184 } 185 186 /* A better entry is already present, ignore the new one. */ 187 188 if (entry->prio > 0 && entry->prio < prio) 189 return; 190 191 /* The existing entry is worse, clear it. */ 192 193 if (entry->prio < 1 || entry->prio > prio) 194 entry->nlines = 0; 195 } 196 197 /* Remember the new line. */ 198 199 if (entry->maxlines == entry->nlines) { 200 entry->maxlines += 4; 201 entry->lines = mandoc_reallocarray(entry->lines, 202 entry->maxlines, sizeof(*entry->lines)); 203 } 204 entry->lines[entry->nlines++] = line; 205 entry->prio = prio; 206 } 207 208 /* 209 * Write out the tags file using the previously collected 210 * information and clear the ohash table while going along. 211 */ 212 void 213 tag_write(void) 214 { 215 FILE *stream; 216 struct tag_entry *entry; 217 size_t i; 218 unsigned int slot; 219 220 if (tag_files.tfd <= 0) 221 return; 222 if (tag_files.tagname != NULL && ohash_find(&tag_data, 223 ohash_qlookup(&tag_data, tag_files.tagname)) == NULL) { 224 warnx("%s: no such tag", tag_files.tagname); 225 tag_files.tagname = NULL; 226 } 227 stream = fdopen(tag_files.tfd, "w"); 228 entry = ohash_first(&tag_data, &slot); 229 while (entry != NULL) { 230 if (stream != NULL && entry->prio >= 0) 231 for (i = 0; i < entry->nlines; i++) 232 fprintf(stream, "%s %s %zu\n", 233 entry->s, tag_files.ofn, entry->lines[i]); 234 free(entry->lines); 235 free(entry); 236 entry = ohash_next(&tag_data, &slot); 237 } 238 ohash_delete(&tag_data); 239 if (stream != NULL) 240 fclose(stream); 241 else 242 close(tag_files.tfd); 243 tag_files.tfd = -1; 244 } 245 246 void 247 tag_unlink(void) 248 { 249 pid_t tc_pgid; 250 251 if (tag_files.tcpgid != -1) { 252 tc_pgid = tcgetpgrp(tag_files.ofd); 253 if (tc_pgid == tag_files.pager_pid || 254 tc_pgid == getpgid(0) || 255 getpgid(tc_pgid) == -1) 256 (void)tcsetpgrp(tag_files.ofd, tag_files.tcpgid); 257 } 258 if (*tag_files.ofn != '\0') 259 unlink(tag_files.ofn); 260 if (*tag_files.tfn != '\0') 261 unlink(tag_files.tfn); 262 } 263 264 static void 265 tag_signal(int signum) 266 { 267 struct sigaction sa; 268 269 tag_unlink(); 270 memset(&sa, 0, sizeof(sa)); 271 sigemptyset(&sa.sa_mask); 272 sa.sa_handler = SIG_DFL; 273 sigaction(signum, &sa, NULL); 274 kill(getpid(), signum); 275 /* NOTREACHED */ 276 _exit(1); 277 } 278