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