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