xref: /openbsd-src/usr.bin/mandoc/tag.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
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