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