Annotation of mandoc/catman.c, Revision 1.28
1.28 ! schwarze 1: /* $Id: catman.c,v 1.27 2025/06/30 01:44:28 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.28 ! schwarze 28: #include <assert.h>
1.13 schwarze 29: #if HAVE_ERR
30: #include <err.h>
31: #endif
1.14 schwarze 32: #include <errno.h>
1.1 kristaps 33: #include <fcntl.h>
1.13 schwarze 34: #if HAVE_FTS
35: #include <fts.h>
36: #else
37: #include "compat_fts.h"
38: #endif
1.27 schwarze 39: #include <signal.h>
40: #include <stdint.h>
1.1 kristaps 41: #include <stdio.h>
42: #include <stdlib.h>
43: #include <string.h>
1.15 schwarze 44: #include <time.h>
1.1 kristaps 45: #include <unistd.h>
46:
1.26 schwarze 47: int verbose_flag = 0;
1.27 schwarze 48: sig_atomic_t got_signal = 0;
1.26 schwarze 49:
1.13 schwarze 50: int process_manpage(int, int, const char *);
51: int process_tree(int, int);
1.14 schwarze 52: void run_mandocd(int, const char *, const char *)
1.20 schwarze 53: __attribute__((__noreturn__));
1.27 schwarze 54: void signal_handler(int);
1.13 schwarze 55: ssize_t sock_fd_write(int, int, int, int);
1.20 schwarze 56: void usage(void) __attribute__((__noreturn__));
1.13 schwarze 57:
58:
59: void
1.27 schwarze 60: signal_handler(int signum)
61: {
62: got_signal = signum;
63: }
64:
65: void
1.14 schwarze 66: run_mandocd(int sockfd, const char *outtype, const char* defos)
1.13 schwarze 67: {
68: char sockfdstr[10];
1.24 schwarze 69: int len;
1.13 schwarze 70:
1.24 schwarze 71: len = snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd);
72: if (len >= (int)sizeof(sockfdstr)) {
73: errno = EOVERFLOW;
74: len = -1;
75: }
76: if (len < 0)
1.13 schwarze 77: err(1, "snprintf");
1.14 schwarze 78: if (defos == NULL)
1.18 schwarze 79: execlp("mandocd", "mandocd", "-T", outtype,
80: sockfdstr, (char *)NULL);
1.14 schwarze 81: else
82: execlp("mandocd", "mandocd", "-T", outtype,
1.18 schwarze 83: "-I", defos, sockfdstr, (char *)NULL);
1.23 schwarze 84: err(1, "exec(mandocd)");
1.13 schwarze 85: }
86:
87: ssize_t
88: sock_fd_write(int fd, int fd0, int fd1, int fd2)
89: {
1.15 schwarze 90: const struct timespec timeout = { 0, 10000000 }; /* 0.01 s */
1.13 schwarze 91: struct msghdr msg;
92: struct iovec iov;
93: union {
94: struct cmsghdr cmsghdr;
95: char control[CMSG_SPACE(3 * sizeof(int))];
96: } cmsgu;
97: struct cmsghdr *cmsg;
98: int *walk;
1.15 schwarze 99: ssize_t sz;
1.13 schwarze 100: unsigned char dummy[1] = {'\0'};
101:
102: iov.iov_base = dummy;
103: iov.iov_len = sizeof(dummy);
104:
105: msg.msg_name = NULL;
106: msg.msg_namelen = 0;
107: msg.msg_iov = &iov;
108: msg.msg_iovlen = 1;
109:
110: msg.msg_control = cmsgu.control;
111: msg.msg_controllen = sizeof(cmsgu.control);
112:
113: cmsg = CMSG_FIRSTHDR(&msg);
114: cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int));
115: cmsg->cmsg_level = SOL_SOCKET;
116: cmsg->cmsg_type = SCM_RIGHTS;
117:
118: walk = (int *)CMSG_DATA(cmsg);
119: *(walk++) = fd0;
120: *(walk++) = fd1;
121: *(walk++) = fd2;
1.1 kristaps 122:
1.15 schwarze 123: /*
124: * It appears that on some systems, sendmsg(3)
125: * may return EAGAIN even in blocking mode.
126: * Seen for example on Oracle Solaris 11.2.
127: * The sleeping time was chosen by experimentation,
128: * to neither cause more than a handful of retries
129: * in normal operation nor unnecessary delays.
130: */
1.24 schwarze 131: while ((sz = sendmsg(fd, &msg, 0)) == -1) {
132: if (errno != EAGAIN) {
133: warn("FATAL: sendmsg");
1.15 schwarze 134: break;
1.24 schwarze 135: }
1.15 schwarze 136: nanosleep(&timeout, NULL);
137: }
138: return sz;
1.13 schwarze 139: }
1.1 kristaps 140:
141: int
1.13 schwarze 142: process_manpage(int srv_fd, int dstdir_fd, const char *path)
1.1 kristaps 143: {
1.13 schwarze 144: int in_fd, out_fd;
1.14 schwarze 145: int irc;
1.1 kristaps 146:
1.13 schwarze 147: if ((in_fd = open(path, O_RDONLY)) == -1) {
1.24 schwarze 148: warn("open %s for reading", path);
149: fflush(stderr);
1.14 schwarze 150: return 0;
1.13 schwarze 151: }
1.1 kristaps 152:
1.13 schwarze 153: if ((out_fd = openat(dstdir_fd, path,
154: O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC,
1.14 schwarze 155: S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
1.24 schwarze 156: warn("openat %s for writing", path);
157: fflush(stderr);
1.13 schwarze 158: close(in_fd);
1.14 schwarze 159: return 0;
1.1 kristaps 160: }
161:
1.14 schwarze 162: irc = sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO);
163:
164: close(in_fd);
165: close(out_fd);
166:
1.24 schwarze 167: return irc;
1.1 kristaps 168: }
169:
1.13 schwarze 170: int
171: process_tree(int srv_fd, int dstdir_fd)
1.1 kristaps 172: {
1.28 ! schwarze 173: const struct timespec timeout = { 0, 10000000 }; /* 0.01 s */
! 174: const int max_inflight = 16;
! 175:
1.13 schwarze 176: FTS *ftsp;
177: FTSENT *entry;
178: const char *argv[2];
179: const char *path;
1.28 ! schwarze 180: int inflight, irc, decr, fatal;
1.24 schwarze 181: int gooddirs, baddirs, goodfiles, badfiles;
1.28 ! schwarze 182: char dummy[1];
1.1 kristaps 183:
1.13 schwarze 184: argv[0] = ".";
185: argv[1] = (char *)NULL;
1.1 kristaps 186:
1.13 schwarze 187: if ((ftsp = fts_open((char * const *)argv,
188: FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) {
189: warn("fts_open");
190: return -1;
191: }
192:
1.28 ! schwarze 193: if (verbose_flag >= 2) {
! 194: warnx("allowing up to %d files in flight", max_inflight);
! 195: fflush(stderr);
! 196: }
! 197: inflight = fatal = gooddirs = baddirs = goodfiles = badfiles = 0;
1.27 schwarze 198: while (fatal == 0 && got_signal == 0 &&
199: (entry = fts_read(ftsp)) != NULL) {
1.28 ! schwarze 200: if (inflight >= max_inflight) {
! 201: while (recv(srv_fd, dummy, sizeof(dummy), 0) == -1) {
! 202: if (errno != EAGAIN) {
! 203: warn("FATAL: recv");
! 204: fatal = errno;
! 205: break;
! 206: }
! 207: nanosleep(&timeout, NULL);
! 208: }
! 209: if (fatal != 0)
! 210: break;
! 211: decr = 1;
! 212: while ((irc = recv(srv_fd, dummy, sizeof(dummy),
! 213: MSG_DONTWAIT)) > 0)
! 214: decr++;
! 215: assert(inflight >= decr);
! 216: if (verbose_flag >= 2 && decr > 1) {
! 217: warnx("files in flight: %d - %d = %d",
! 218: inflight, decr, inflight - decr);
! 219: fflush(stderr);
! 220: }
! 221: inflight -= decr;
! 222: if (irc == 0)
! 223: errno = ECONNRESET;
! 224: if (errno != EAGAIN) {
! 225: warn("FATAL: recv");
! 226: fatal = errno;
! 227: break;
! 228: }
! 229: }
1.13 schwarze 230: path = entry->fts_path + 2;
231: switch (entry->fts_info) {
232: case FTS_F:
1.24 schwarze 233: switch (process_manpage(srv_fd, dstdir_fd, path)) {
234: case -1:
235: fatal = errno;
236: break;
237: case 0:
238: badfiles++;
239: break;
240: default:
241: goodfiles++;
1.28 ! schwarze 242: inflight++;
1.24 schwarze 243: break;
1.14 schwarze 244: }
1.13 schwarze 245: break;
246: case FTS_D:
1.14 schwarze 247: if (*path != '\0' &&
248: mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP |
249: S_IXGRP | S_IROTH | S_IXOTH) == -1 &&
250: errno != EEXIST) {
1.24 schwarze 251: warn("mkdirat %s", path);
252: fflush(stderr);
1.14 schwarze 253: (void)fts_set(ftsp, entry, FTS_SKIP);
1.24 schwarze 254: baddirs++;
255: } else
256: gooddirs++;
1.14 schwarze 257: break;
1.13 schwarze 258: case FTS_DP:
259: break;
1.24 schwarze 260: case FTS_DNR:
261: warnx("directory %s unreadable: %s",
262: path, strerror(entry->fts_errno));
263: fflush(stderr);
264: baddirs++;
265: break;
266: case FTS_DC:
267: warnx("directory %s causes cycle", path);
268: fflush(stderr);
269: baddirs++;
270: break;
271: case FTS_ERR:
272: case FTS_NS:
273: warnx("file %s: %s",
274: path, strerror(entry->fts_errno));
275: fflush(stderr);
276: badfiles++;
277: break;
1.13 schwarze 278: default:
1.25 schwarze 279: warnx("file %s: not a regular file", path);
1.24 schwarze 280: fflush(stderr);
281: badfiles++;
1.13 schwarze 282: break;
1.1 kristaps 283: }
1.13 schwarze 284: }
1.27 schwarze 285: if (got_signal != 0) {
286: switch (got_signal) {
287: case SIGCHLD:
288: warnx("FATAL: mandocd child died: got SIGCHLD");
289: break;
290: case SIGPIPE:
291: warnx("FATAL: mandocd child died: got SIGPIPE");
292: break;
293: default:
294: warnx("FATAL: signal SIG%s", sys_signame[got_signal]);
295: break;
296: }
1.28 ! schwarze 297: inflight = -1;
1.27 schwarze 298: fatal = 1;
299: } else if (fatal == 0 && (fatal = errno) != 0)
1.24 schwarze 300: warn("FATAL: fts_read");
1.1 kristaps 301:
1.13 schwarze 302: fts_close(ftsp);
1.28 ! schwarze 303: if (verbose_flag >= 2 && inflight > 0) {
! 304: warnx("waiting for %d files in flight", inflight);
! 305: fflush(stderr);
! 306: }
! 307: while (inflight > 0) {
! 308: irc = recv(srv_fd, dummy, sizeof(dummy), 0);
! 309: if (irc > 0)
! 310: inflight--;
! 311: else if (irc == -1 && errno == EAGAIN)
! 312: nanosleep(&timeout, NULL);
! 313: else {
! 314: if (irc == 0)
! 315: errno = ECONNRESET;
! 316: warn("recv");
! 317: inflight = -1;
! 318: }
! 319: }
1.26 schwarze 320: if (verbose_flag)
321: warnx("processed %d files in %d directories",
322: goodfiles, gooddirs);
1.24 schwarze 323: if (baddirs > 0)
324: warnx("skipped %d %s due to errors", baddirs,
325: baddirs == 1 ? "directory" : "directories");
326: if (badfiles > 0)
327: warnx("skipped %d %s due to errors", badfiles,
328: badfiles == 1 ? "file" : "files");
329: if (fatal != 0) {
330: warnx("processing aborted due to fatal error, "
331: "results are probably incomplete");
1.28 ! schwarze 332: inflight = -1;
1.24 schwarze 333: }
1.28 ! schwarze 334: return inflight;
1.1 kristaps 335: }
336:
1.13 schwarze 337: int
338: main(int argc, char **argv)
1.1 kristaps 339: {
1.27 schwarze 340: struct sigaction sa;
1.14 schwarze 341: const char *defos, *outtype;
1.13 schwarze 342: int srv_fds[2];
343: int dstdir_fd;
344: int opt;
1.1 kristaps 345: pid_t pid;
346:
1.14 schwarze 347: defos = NULL;
1.13 schwarze 348: outtype = "ascii";
1.26 schwarze 349: while ((opt = getopt(argc, argv, "I:T:v")) != -1) {
1.13 schwarze 350: switch (opt) {
1.14 schwarze 351: case 'I':
352: defos = optarg;
353: break;
1.13 schwarze 354: case 'T':
355: outtype = optarg;
1.26 schwarze 356: break;
357: case 'v':
1.28 ! schwarze 358: verbose_flag += 1;
1.1 kristaps 359: break;
1.13 schwarze 360: default:
361: usage();
1.1 kristaps 362: }
1.13 schwarze 363: }
1.1 kristaps 364:
1.13 schwarze 365: if (argc > 0) {
366: argc -= optind;
367: argv += optind;
1.1 kristaps 368: }
1.25 schwarze 369: if (argc != 2) {
370: switch (argc) {
371: case 0:
372: warnx("missing arguments: srcdir and dstdir");
373: break;
374: case 1:
375: warnx("missing argument: dstdir");
376: break;
377: default:
378: warnx("too many arguments: %s", argv[2]);
379: break;
380: }
1.13 schwarze 381: usage();
1.25 schwarze 382: }
1.27 schwarze 383:
384: memset(&sa, 0, sizeof(sa));
385: sa.sa_handler = &signal_handler;
386: sa.sa_flags = SA_NOCLDWAIT;
387: if (sigfillset(&sa.sa_mask) == -1)
388: err(1, "sigfillset");
389: if (sigaction(SIGHUP, &sa, NULL) == -1)
390: err(1, "sigaction(SIGHUP)");
391: if (sigaction(SIGINT, &sa, NULL) == -1)
392: err(1, "sigaction(SIGINT)");
393: if (sigaction(SIGPIPE, &sa, NULL) == -1)
394: err(1, "sigaction(SIGPIPE)");
395: if (sigaction(SIGTERM, &sa, NULL) == -1)
396: err(1, "sigaction(SIGTERM)");
397: if (sigaction(SIGCHLD, &sa, NULL) == -1)
398: err(1, "sigaction(SIGCHLD)");
1.1 kristaps 399:
1.13 schwarze 400: if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1)
401: err(1, "socketpair");
1.1 kristaps 402:
1.13 schwarze 403: pid = fork();
404: switch (pid) {
405: case -1:
406: err(1, "fork");
407: case 0:
408: close(srv_fds[0]);
1.14 schwarze 409: run_mandocd(srv_fds[1], outtype, defos);
1.13 schwarze 410: default:
411: break;
1.1 kristaps 412: }
1.13 schwarze 413: close(srv_fds[1]);
1.1 kristaps 414:
1.13 schwarze 415: if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1)
1.24 schwarze 416: err(1, "open destination %s", argv[1]);
1.1 kristaps 417:
1.13 schwarze 418: if (chdir(argv[0]) == -1)
1.24 schwarze 419: err(1, "chdir to source %s", argv[0]);
1.1 kristaps 420:
1.13 schwarze 421: return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0;
1.1 kristaps 422: }
423:
1.13 schwarze 424: void
425: usage(void)
1.1 kristaps 426: {
1.19 schwarze 427: fprintf(stderr, "usage: %s [-I os=name] [-T output] "
428: "srcdir dstdir\n", BINM_CATMAN);
1.13 schwarze 429: exit(1);
1.1 kristaps 430: }
CVSweb