1/* $Id: term_tag.c,v 1.6 2021/03/30 17:16:55 schwarze Exp $ */
2/*
3 * Copyright (c) 2015,2016,2018,2019,2020 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 * Functions to write a ctags(1) file.
18 * For use by the mandoc(1) ASCII and UTF-8 formatters only.
19 */
20#include "config.h"
21
22#include <sys/types.h>
23
24#include <errno.h>
25#include <fcntl.h>
26#include <signal.h>
27#include <stddef.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
32
33#include "mandoc.h"
34#include "roff.h"
35#include "roff_int.h"
36#include "tag.h"
37#include "term_tag.h"
38
39static void tag_signal(int) __attribute__((__noreturn__));
40
41static struct tag_files tag_files;
42
43
44/*
45 * Prepare for using a pager.
46 * Not all pagers are capable of using a tag file,
47 * but for simplicity, create it anyway.
48 */
49struct tag_files *
50term_tag_init(const char *outfilename, const char *suffix,
51    const char *tagfilename)
52{
53	struct sigaction	 sa;
54	int			 ofd;	/* In /tmp/, dup(2)ed to stdout. */
55	int			 tfd;
56
57	ofd = tfd = -1;
58	tag_files.tfs = NULL;
59	tag_files.tcpgid = -1;
60
61	/* Clean up when dying from a signal. */
62
63	memset(&sa, 0, sizeof(sa));
64	sigfillset(&sa.sa_mask);
65	sa.sa_handler = tag_signal;
66	sigaction(SIGHUP, &sa, NULL);
67	sigaction(SIGINT, &sa, NULL);
68	sigaction(SIGTERM, &sa, NULL);
69
70	/*
71	 * POSIX requires that a process calling tcsetpgrp(3)
72	 * from the background gets a SIGTTOU signal.
73	 * In that case, do not stop.
74	 */
75
76	sa.sa_handler = SIG_IGN;
77	sigaction(SIGTTOU, &sa, NULL);
78
79	/* Save the original standard output for use by the pager. */
80
81	if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1) {
82		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
83		goto fail;
84	}
85
86	/* Create both temporary output files. */
87
88	if (outfilename == NULL) {
89		(void)snprintf(tag_files.ofn, sizeof(tag_files.ofn),
90		    "/tmp/man.XXXXXXXXXX%s", suffix);
91		if ((ofd = mkstemps(tag_files.ofn, strlen(suffix))) == -1) {
92			mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
93			    "%s: %s", tag_files.ofn, strerror(errno));
94			goto fail;
95		}
96	} else {
97		(void)strlcpy(tag_files.ofn, outfilename,
98		   sizeof(tag_files.ofn));
99		unlink(outfilename);
100		ofd = open(outfilename, O_WRONLY | O_CREAT | O_EXCL, 0644);
101		if (ofd == -1) {
102			mandoc_msg(MANDOCERR_OPEN, 0, 0,
103			    "%s: %s", outfilename, strerror(errno));
104			goto fail;
105		}
106	}
107	if (tagfilename == NULL) {
108		(void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX",
109		    sizeof(tag_files.tfn));
110		if ((tfd = mkstemp(tag_files.tfn)) == -1) {
111			mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
112			    "%s: %s", tag_files.tfn, strerror(errno));
113			goto fail;
114		}
115	} else {
116		(void)strlcpy(tag_files.tfn, tagfilename,
117		    sizeof(tag_files.tfn));
118		unlink(tagfilename);
119		tfd = open(tagfilename, O_WRONLY | O_CREAT | O_EXCL, 0644);
120		if (tfd == -1) {
121			mandoc_msg(MANDOCERR_OPEN, 0, 0,
122			    "%s: %s", tagfilename, strerror(errno));
123			goto fail;
124		}
125	}
126	if ((tag_files.tfs = fdopen(tfd, "w")) == NULL) {
127		mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
128		goto fail;
129	}
130	tfd = -1;
131	if (dup2(ofd, STDOUT_FILENO) == -1) {
132		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
133		goto fail;
134	}
135	close(ofd);
136	return &tag_files;
137
138fail:
139	term_tag_unlink();
140	if (ofd != -1)
141		close(ofd);
142	if (tfd != -1)
143		close(tfd);
144	if (tag_files.ofd != -1) {
145		close(tag_files.ofd);
146		tag_files.ofd = -1;
147	}
148	return NULL;
149}
150
151void
152term_tag_write(struct roff_node *n, size_t line)
153{
154	const char	*cp;
155	int		 len;
156
157	if (tag_files.tfs == NULL)
158		return;
159	cp = n->tag == NULL ? n->child->string : n->tag;
160	if (cp[0] == '\\' && (cp[1] == '&' || cp[1] == 'e'))
161		cp += 2;
162	len = strcspn(cp, " \t\\");
163	fprintf(tag_files.tfs, "%.*s %s %zu\n",
164	    len, cp, tag_files.ofn, line);
165}
166
167/*
168 * Close both output files and restore the original standard output
169 * to the terminal.  In the unlikely case that the latter fails,
170 * trying to start a pager would be useless, so report the failure
171 * to the main program.
172 */
173int
174term_tag_close(void)
175{
176	int irc = 0;
177
178	if (tag_files.tfs != NULL) {
179		fclose(tag_files.tfs);
180		tag_files.tfs = NULL;
181	}
182	if (tag_files.ofd != -1) {
183		fflush(stdout);
184		if ((irc = dup2(tag_files.ofd, STDOUT_FILENO)) == -1)
185			mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
186		close(tag_files.ofd);
187		tag_files.ofd = -1;
188	}
189	return irc;
190}
191
192void
193term_tag_unlink(void)
194{
195	pid_t	 tc_pgid;
196
197	if (tag_files.tcpgid != -1) {
198		tc_pgid = tcgetpgrp(STDOUT_FILENO);
199		if (tc_pgid == tag_files.pager_pid ||
200		    tc_pgid == getpgid(0) ||
201		    getpgid(tc_pgid) == -1)
202			(void)tcsetpgrp(STDOUT_FILENO, tag_files.tcpgid);
203	}
204	if (strncmp(tag_files.ofn, "/tmp/man.", 9) == 0) {
205		unlink(tag_files.ofn);
206		*tag_files.ofn = '\0';
207	}
208	if (strncmp(tag_files.tfn, "/tmp/man.", 9) == 0) {
209		unlink(tag_files.tfn);
210		*tag_files.tfn = '\0';
211	}
212}
213
214static void
215tag_signal(int signum)
216{
217	struct sigaction	 sa;
218
219	term_tag_unlink();
220	memset(&sa, 0, sizeof(sa));
221	sigemptyset(&sa.sa_mask);
222	sa.sa_handler = SIG_DFL;
223	sigaction(signum, &sa, NULL);
224	kill(getpid(), signum);
225	/* NOTREACHED */
226	_exit(1);
227}
228