Annotation of mandoc/catman.c, Revision 1.27
1.27 ! schwarze 1: /* $Id: catman.c,v 1.26 2025/06/29 23:51:40 schwarze Exp $ */
1.1 kristaps 2: /*
1.26 schwarze 3: * Copyright (c) 2017, 2025 Ingo Schwarze <schwarze@openbsd.org>
1.13 schwarze 4: * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
1.13 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.1 kristaps 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.13 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.1 kristaps 13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18: #include "config.h"
1.16 schwarze 19:
1.22 schwarze 20: #if NEED_XPG4_2
1.16 schwarze 21: #define _XPG4_2
22: #endif
1.1 kristaps 23:
1.13 schwarze 24: #include <sys/types.h>
25: #include <sys/socket.h>
1.1 kristaps 26: #include <sys/stat.h>
27:
1.13 schwarze 28: #if HAVE_ERR
29: #include <err.h>
30: #endif
1.14 schwarze 31: #include <errno.h>
1.1 kristaps 32: #include <fcntl.h>
1.13 schwarze 33: #if HAVE_FTS
34: #include <fts.h>
35: #else
36: #include "compat_fts.h"
37: #endif
1.27 ! schwarze 38: #include <signal.h>
! 39: #include <stdint.h>
1.1 kristaps 40: #include <stdio.h>
41: #include <stdlib.h>
42: #include <string.h>
1.15 schwarze 43: #include <time.h>
1.1 kristaps 44: #include <unistd.h>
45:
1.26 schwarze 46: int verbose_flag = 0;
1.27 ! schwarze 47: sig_atomic_t got_signal = 0;
1.26 schwarze 48:
1.13 schwarze 49: int process_manpage(int, int, const char *);
50: int process_tree(int, int);
1.14 schwarze 51: void run_mandocd(int, const char *, const char *)
1.20 schwarze 52: __attribute__((__noreturn__));
1.27 ! schwarze 53: void signal_handler(int);
1.13 schwarze 54: ssize_t sock_fd_write(int, int, int, int);
1.20 schwarze 55: void usage(void) __attribute__((__noreturn__));
1.13 schwarze 56:
57:
58: void
1.27 ! schwarze 59: signal_handler(int signum)
! 60: {
! 61: got_signal = signum;
! 62: }
! 63:
! 64: void
1.14 schwarze 65: run_mandocd(int sockfd, const char *outtype, const char* defos)
1.13 schwarze 66: {
67: char sockfdstr[10];
1.24 schwarze 68: int len;
1.13 schwarze 69:
1.24 schwarze 70: len = snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd);
71: if (len >= (int)sizeof(sockfdstr)) {
72: errno = EOVERFLOW;
73: len = -1;
74: }
75: if (len < 0)
1.13 schwarze 76: err(1, "snprintf");
1.14 schwarze 77: if (defos == NULL)
1.18 schwarze 78: execlp("mandocd", "mandocd", "-T", outtype,
79: sockfdstr, (char *)NULL);
1.14 schwarze 80: else
81: execlp("mandocd", "mandocd", "-T", outtype,
1.18 schwarze 82: "-I", defos, sockfdstr, (char *)NULL);
1.23 schwarze 83: err(1, "exec(mandocd)");
1.13 schwarze 84: }
85:
86: ssize_t
87: sock_fd_write(int fd, int fd0, int fd1, int fd2)
88: {
1.15 schwarze 89: const struct timespec timeout = { 0, 10000000 }; /* 0.01 s */
1.13 schwarze 90: struct msghdr msg;
91: struct iovec iov;
92: union {
93: struct cmsghdr cmsghdr;
94: char control[CMSG_SPACE(3 * sizeof(int))];
95: } cmsgu;
96: struct cmsghdr *cmsg;
97: int *walk;
1.15 schwarze 98: ssize_t sz;
1.13 schwarze 99: unsigned char dummy[1] = {'\0'};
100:
101: iov.iov_base = dummy;
102: iov.iov_len = sizeof(dummy);
103:
104: msg.msg_name = NULL;
105: msg.msg_namelen = 0;
106: msg.msg_iov = &iov;
107: msg.msg_iovlen = 1;
108:
109: msg.msg_control = cmsgu.control;
110: msg.msg_controllen = sizeof(cmsgu.control);
111:
112: cmsg = CMSG_FIRSTHDR(&msg);
113: cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int));
114: cmsg->cmsg_level = SOL_SOCKET;
115: cmsg->cmsg_type = SCM_RIGHTS;
116:
117: walk = (int *)CMSG_DATA(cmsg);
118: *(walk++) = fd0;
119: *(walk++) = fd1;
120: *(walk++) = fd2;
1.1 kristaps 121:
1.15 schwarze 122: /*
123: * It appears that on some systems, sendmsg(3)
124: * may return EAGAIN even in blocking mode.
125: * Seen for example on Oracle Solaris 11.2.
126: * The sleeping time was chosen by experimentation,
127: * to neither cause more than a handful of retries
128: * in normal operation nor unnecessary delays.
129: */
1.24 schwarze 130: while ((sz = sendmsg(fd, &msg, 0)) == -1) {
131: if (errno != EAGAIN) {
132: warn("FATAL: sendmsg");
1.15 schwarze 133: break;
1.24 schwarze 134: }
1.15 schwarze 135: nanosleep(&timeout, NULL);
136: }
137: return sz;
1.13 schwarze 138: }
1.1 kristaps 139:
140: int
1.13 schwarze 141: process_manpage(int srv_fd, int dstdir_fd, const char *path)
1.1 kristaps 142: {
1.13 schwarze 143: int in_fd, out_fd;
1.14 schwarze 144: int irc;
1.1 kristaps 145:
1.13 schwarze 146: if ((in_fd = open(path, O_RDONLY)) == -1) {
1.24 schwarze 147: warn("open %s for reading", path);
148: fflush(stderr);
1.14 schwarze 149: return 0;
1.13 schwarze 150: }
1.1 kristaps 151:
1.13 schwarze 152: if ((out_fd = openat(dstdir_fd, path,
153: O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC,
1.14 schwarze 154: S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
1.24 schwarze 155: warn("openat %s for writing", path);
156: fflush(stderr);
1.13 schwarze 157: close(in_fd);
1.14 schwarze 158: return 0;
1.1 kristaps 159: }
160:
1.14 schwarze 161: irc = sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO);
162:
163: close(in_fd);
164: close(out_fd);
165:
1.24 schwarze 166: return irc;
1.1 kristaps 167: }
168:
1.13 schwarze 169: int
170: process_tree(int srv_fd, int dstdir_fd)
1.1 kristaps 171: {
1.13 schwarze 172: FTS *ftsp;
173: FTSENT *entry;
174: const char *argv[2];
175: const char *path;
1.24 schwarze 176: int fatal;
177: int gooddirs, baddirs, goodfiles, badfiles;
1.1 kristaps 178:
1.13 schwarze 179: argv[0] = ".";
180: argv[1] = (char *)NULL;
1.1 kristaps 181:
1.13 schwarze 182: if ((ftsp = fts_open((char * const *)argv,
183: FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) {
184: warn("fts_open");
185: return -1;
186: }
187:
1.24 schwarze 188: fatal = 0;
189: gooddirs = baddirs = goodfiles = badfiles = 0;
1.27 ! schwarze 190: while (fatal == 0 && got_signal == 0 &&
! 191: (entry = fts_read(ftsp)) != NULL) {
1.13 schwarze 192: path = entry->fts_path + 2;
193: switch (entry->fts_info) {
194: case FTS_F:
1.24 schwarze 195: switch (process_manpage(srv_fd, dstdir_fd, path)) {
196: case -1:
197: fatal = errno;
198: break;
199: case 0:
200: badfiles++;
201: break;
202: default:
203: goodfiles++;
204: break;
1.14 schwarze 205: }
1.13 schwarze 206: break;
207: case FTS_D:
1.14 schwarze 208: if (*path != '\0' &&
209: mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP |
210: S_IXGRP | S_IROTH | S_IXOTH) == -1 &&
211: errno != EEXIST) {
1.24 schwarze 212: warn("mkdirat %s", path);
213: fflush(stderr);
1.14 schwarze 214: (void)fts_set(ftsp, entry, FTS_SKIP);
1.24 schwarze 215: baddirs++;
216: } else
217: gooddirs++;
1.14 schwarze 218: break;
1.13 schwarze 219: case FTS_DP:
220: break;
1.24 schwarze 221: case FTS_DNR:
222: warnx("directory %s unreadable: %s",
223: path, strerror(entry->fts_errno));
224: fflush(stderr);
225: baddirs++;
226: break;
227: case FTS_DC:
228: warnx("directory %s causes cycle", path);
229: fflush(stderr);
230: baddirs++;
231: break;
232: case FTS_ERR:
233: case FTS_NS:
234: warnx("file %s: %s",
235: path, strerror(entry->fts_errno));
236: fflush(stderr);
237: badfiles++;
238: break;
1.13 schwarze 239: default:
1.25 schwarze 240: warnx("file %s: not a regular file", path);
1.24 schwarze 241: fflush(stderr);
242: badfiles++;
1.13 schwarze 243: break;
1.1 kristaps 244: }
1.13 schwarze 245: }
1.27 ! schwarze 246: if (got_signal != 0) {
! 247: switch (got_signal) {
! 248: case SIGCHLD:
! 249: warnx("FATAL: mandocd child died: got SIGCHLD");
! 250: break;
! 251: case SIGPIPE:
! 252: warnx("FATAL: mandocd child died: got SIGPIPE");
! 253: break;
! 254: default:
! 255: warnx("FATAL: signal SIG%s", sys_signame[got_signal]);
! 256: break;
! 257: }
! 258: fatal = 1;
! 259: } else if (fatal == 0 && (fatal = errno) != 0)
1.24 schwarze 260: warn("FATAL: fts_read");
1.1 kristaps 261:
1.13 schwarze 262: fts_close(ftsp);
1.26 schwarze 263: if (verbose_flag)
264: warnx("processed %d files in %d directories",
265: goodfiles, gooddirs);
1.24 schwarze 266: if (baddirs > 0)
267: warnx("skipped %d %s due to errors", baddirs,
268: baddirs == 1 ? "directory" : "directories");
269: if (badfiles > 0)
270: warnx("skipped %d %s due to errors", badfiles,
271: badfiles == 1 ? "file" : "files");
272: if (fatal != 0) {
273: warnx("processing aborted due to fatal error, "
274: "results are probably incomplete");
275: }
1.13 schwarze 276: return 0;
1.1 kristaps 277: }
278:
1.13 schwarze 279: int
280: main(int argc, char **argv)
1.1 kristaps 281: {
1.27 ! schwarze 282: struct sigaction sa;
1.14 schwarze 283: const char *defos, *outtype;
1.13 schwarze 284: int srv_fds[2];
285: int dstdir_fd;
286: int opt;
1.1 kristaps 287: pid_t pid;
288:
1.14 schwarze 289: defos = NULL;
1.13 schwarze 290: outtype = "ascii";
1.26 schwarze 291: while ((opt = getopt(argc, argv, "I:T:v")) != -1) {
1.13 schwarze 292: switch (opt) {
1.14 schwarze 293: case 'I':
294: defos = optarg;
295: break;
1.13 schwarze 296: case 'T':
297: outtype = optarg;
1.26 schwarze 298: break;
299: case 'v':
300: verbose_flag = 1;
1.1 kristaps 301: break;
1.13 schwarze 302: default:
303: usage();
1.1 kristaps 304: }
1.13 schwarze 305: }
1.1 kristaps 306:
1.13 schwarze 307: if (argc > 0) {
308: argc -= optind;
309: argv += optind;
1.1 kristaps 310: }
1.25 schwarze 311: if (argc != 2) {
312: switch (argc) {
313: case 0:
314: warnx("missing arguments: srcdir and dstdir");
315: break;
316: case 1:
317: warnx("missing argument: dstdir");
318: break;
319: default:
320: warnx("too many arguments: %s", argv[2]);
321: break;
322: }
1.13 schwarze 323: usage();
1.25 schwarze 324: }
1.27 ! schwarze 325:
! 326: memset(&sa, 0, sizeof(sa));
! 327: sa.sa_handler = &signal_handler;
! 328: sa.sa_flags = SA_NOCLDWAIT;
! 329: if (sigfillset(&sa.sa_mask) == -1)
! 330: err(1, "sigfillset");
! 331: if (sigaction(SIGHUP, &sa, NULL) == -1)
! 332: err(1, "sigaction(SIGHUP)");
! 333: if (sigaction(SIGINT, &sa, NULL) == -1)
! 334: err(1, "sigaction(SIGINT)");
! 335: if (sigaction(SIGPIPE, &sa, NULL) == -1)
! 336: err(1, "sigaction(SIGPIPE)");
! 337: if (sigaction(SIGTERM, &sa, NULL) == -1)
! 338: err(1, "sigaction(SIGTERM)");
! 339: if (sigaction(SIGCHLD, &sa, NULL) == -1)
! 340: err(1, "sigaction(SIGCHLD)");
1.1 kristaps 341:
1.13 schwarze 342: if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1)
343: err(1, "socketpair");
1.1 kristaps 344:
1.13 schwarze 345: pid = fork();
346: switch (pid) {
347: case -1:
348: err(1, "fork");
349: case 0:
350: close(srv_fds[0]);
1.14 schwarze 351: run_mandocd(srv_fds[1], outtype, defos);
1.13 schwarze 352: default:
353: break;
1.1 kristaps 354: }
1.13 schwarze 355: close(srv_fds[1]);
1.1 kristaps 356:
1.13 schwarze 357: if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1)
1.24 schwarze 358: err(1, "open destination %s", argv[1]);
1.1 kristaps 359:
1.13 schwarze 360: if (chdir(argv[0]) == -1)
1.24 schwarze 361: err(1, "chdir to source %s", argv[0]);
1.1 kristaps 362:
1.13 schwarze 363: return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0;
1.1 kristaps 364: }
365:
1.13 schwarze 366: void
367: usage(void)
1.1 kristaps 368: {
1.19 schwarze 369: fprintf(stderr, "usage: %s [-I os=name] [-T output] "
370: "srcdir dstdir\n", BINM_CATMAN);
1.13 schwarze 371: exit(1);
1.1 kristaps 372: }
CVSweb