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