Annotation of mandoc/catman.c, Revision 1.26
1.26 ! schwarze 1: /* $Id: catman.c,v 1.25 2025/06/29 23:21:07 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.1 kristaps 38: #include <stdio.h>
39: #include <stdlib.h>
40: #include <string.h>
1.15 schwarze 41: #include <time.h>
1.1 kristaps 42: #include <unistd.h>
43:
1.26 ! schwarze 44: int verbose_flag = 0;
! 45:
1.13 schwarze 46: int process_manpage(int, int, const char *);
47: int process_tree(int, int);
1.14 schwarze 48: void run_mandocd(int, const char *, const char *)
1.20 schwarze 49: __attribute__((__noreturn__));
1.13 schwarze 50: ssize_t sock_fd_write(int, int, int, int);
1.20 schwarze 51: void usage(void) __attribute__((__noreturn__));
1.13 schwarze 52:
53:
54: void
1.14 schwarze 55: run_mandocd(int sockfd, const char *outtype, const char* defos)
1.13 schwarze 56: {
57: char sockfdstr[10];
1.24 schwarze 58: int len;
1.13 schwarze 59:
1.24 schwarze 60: len = snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd);
61: if (len >= (int)sizeof(sockfdstr)) {
62: errno = EOVERFLOW;
63: len = -1;
64: }
65: if (len < 0)
1.13 schwarze 66: err(1, "snprintf");
1.14 schwarze 67: if (defos == NULL)
1.18 schwarze 68: execlp("mandocd", "mandocd", "-T", outtype,
69: sockfdstr, (char *)NULL);
1.14 schwarze 70: else
71: execlp("mandocd", "mandocd", "-T", outtype,
1.18 schwarze 72: "-I", defos, sockfdstr, (char *)NULL);
1.23 schwarze 73: err(1, "exec(mandocd)");
1.13 schwarze 74: }
75:
76: ssize_t
77: sock_fd_write(int fd, int fd0, int fd1, int fd2)
78: {
1.15 schwarze 79: const struct timespec timeout = { 0, 10000000 }; /* 0.01 s */
1.13 schwarze 80: struct msghdr msg;
81: struct iovec iov;
82: union {
83: struct cmsghdr cmsghdr;
84: char control[CMSG_SPACE(3 * sizeof(int))];
85: } cmsgu;
86: struct cmsghdr *cmsg;
87: int *walk;
1.15 schwarze 88: ssize_t sz;
1.13 schwarze 89: unsigned char dummy[1] = {'\0'};
90:
91: iov.iov_base = dummy;
92: iov.iov_len = sizeof(dummy);
93:
94: msg.msg_name = NULL;
95: msg.msg_namelen = 0;
96: msg.msg_iov = &iov;
97: msg.msg_iovlen = 1;
98:
99: msg.msg_control = cmsgu.control;
100: msg.msg_controllen = sizeof(cmsgu.control);
101:
102: cmsg = CMSG_FIRSTHDR(&msg);
103: cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int));
104: cmsg->cmsg_level = SOL_SOCKET;
105: cmsg->cmsg_type = SCM_RIGHTS;
106:
107: walk = (int *)CMSG_DATA(cmsg);
108: *(walk++) = fd0;
109: *(walk++) = fd1;
110: *(walk++) = fd2;
1.1 kristaps 111:
1.15 schwarze 112: /*
113: * It appears that on some systems, sendmsg(3)
114: * may return EAGAIN even in blocking mode.
115: * Seen for example on Oracle Solaris 11.2.
116: * The sleeping time was chosen by experimentation,
117: * to neither cause more than a handful of retries
118: * in normal operation nor unnecessary delays.
119: */
1.24 schwarze 120: while ((sz = sendmsg(fd, &msg, 0)) == -1) {
121: if (errno != EAGAIN) {
122: warn("FATAL: sendmsg");
1.15 schwarze 123: break;
1.24 schwarze 124: }
1.15 schwarze 125: nanosleep(&timeout, NULL);
126: }
127: return sz;
1.13 schwarze 128: }
1.1 kristaps 129:
130: int
1.13 schwarze 131: process_manpage(int srv_fd, int dstdir_fd, const char *path)
1.1 kristaps 132: {
1.13 schwarze 133: int in_fd, out_fd;
1.14 schwarze 134: int irc;
1.1 kristaps 135:
1.13 schwarze 136: if ((in_fd = open(path, O_RDONLY)) == -1) {
1.24 schwarze 137: warn("open %s for reading", path);
138: fflush(stderr);
1.14 schwarze 139: return 0;
1.13 schwarze 140: }
1.1 kristaps 141:
1.13 schwarze 142: if ((out_fd = openat(dstdir_fd, path,
143: O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC,
1.14 schwarze 144: S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
1.24 schwarze 145: warn("openat %s for writing", path);
146: fflush(stderr);
1.13 schwarze 147: close(in_fd);
1.14 schwarze 148: return 0;
1.1 kristaps 149: }
150:
1.14 schwarze 151: irc = sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO);
152:
153: close(in_fd);
154: close(out_fd);
155:
1.24 schwarze 156: return irc;
1.1 kristaps 157: }
158:
1.13 schwarze 159: int
160: process_tree(int srv_fd, int dstdir_fd)
1.1 kristaps 161: {
1.13 schwarze 162: FTS *ftsp;
163: FTSENT *entry;
164: const char *argv[2];
165: const char *path;
1.24 schwarze 166: int fatal;
167: int gooddirs, baddirs, goodfiles, badfiles;
1.1 kristaps 168:
1.13 schwarze 169: argv[0] = ".";
170: argv[1] = (char *)NULL;
1.1 kristaps 171:
1.13 schwarze 172: if ((ftsp = fts_open((char * const *)argv,
173: FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) {
174: warn("fts_open");
175: return -1;
176: }
177:
1.24 schwarze 178: fatal = 0;
179: gooddirs = baddirs = goodfiles = badfiles = 0;
180: while (fatal == 0 && (entry = fts_read(ftsp)) != NULL) {
1.13 schwarze 181: path = entry->fts_path + 2;
182: switch (entry->fts_info) {
183: case FTS_F:
1.24 schwarze 184: switch (process_manpage(srv_fd, dstdir_fd, path)) {
185: case -1:
186: fatal = errno;
187: break;
188: case 0:
189: badfiles++;
190: break;
191: default:
192: goodfiles++;
193: break;
1.14 schwarze 194: }
1.13 schwarze 195: break;
196: case FTS_D:
1.14 schwarze 197: if (*path != '\0' &&
198: mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP |
199: S_IXGRP | S_IROTH | S_IXOTH) == -1 &&
200: errno != EEXIST) {
1.24 schwarze 201: warn("mkdirat %s", path);
202: fflush(stderr);
1.14 schwarze 203: (void)fts_set(ftsp, entry, FTS_SKIP);
1.24 schwarze 204: baddirs++;
205: } else
206: gooddirs++;
1.14 schwarze 207: break;
1.13 schwarze 208: case FTS_DP:
209: break;
1.24 schwarze 210: case FTS_DNR:
211: warnx("directory %s unreadable: %s",
212: path, strerror(entry->fts_errno));
213: fflush(stderr);
214: baddirs++;
215: break;
216: case FTS_DC:
217: warnx("directory %s causes cycle", path);
218: fflush(stderr);
219: baddirs++;
220: break;
221: case FTS_ERR:
222: case FTS_NS:
223: warnx("file %s: %s",
224: path, strerror(entry->fts_errno));
225: fflush(stderr);
226: badfiles++;
227: break;
1.13 schwarze 228: default:
1.25 schwarze 229: warnx("file %s: not a regular file", path);
1.24 schwarze 230: fflush(stderr);
231: badfiles++;
1.13 schwarze 232: break;
1.1 kristaps 233: }
1.13 schwarze 234: }
1.24 schwarze 235: if (fatal == 0 && (fatal = errno) != 0)
236: warn("FATAL: fts_read");
1.1 kristaps 237:
1.13 schwarze 238: fts_close(ftsp);
1.26 ! schwarze 239: if (verbose_flag)
! 240: warnx("processed %d files in %d directories",
! 241: goodfiles, gooddirs);
1.24 schwarze 242: if (baddirs > 0)
243: warnx("skipped %d %s due to errors", baddirs,
244: baddirs == 1 ? "directory" : "directories");
245: if (badfiles > 0)
246: warnx("skipped %d %s due to errors", badfiles,
247: badfiles == 1 ? "file" : "files");
248: if (fatal != 0) {
249: warnx("processing aborted due to fatal error, "
250: "results are probably incomplete");
251: }
1.13 schwarze 252: return 0;
1.1 kristaps 253: }
254:
1.13 schwarze 255: int
256: main(int argc, char **argv)
1.1 kristaps 257: {
1.14 schwarze 258: const char *defos, *outtype;
1.13 schwarze 259: int srv_fds[2];
260: int dstdir_fd;
261: int opt;
1.1 kristaps 262: pid_t pid;
263:
1.14 schwarze 264: defos = NULL;
1.13 schwarze 265: outtype = "ascii";
1.26 ! schwarze 266: while ((opt = getopt(argc, argv, "I:T:v")) != -1) {
1.13 schwarze 267: switch (opt) {
1.14 schwarze 268: case 'I':
269: defos = optarg;
270: break;
1.13 schwarze 271: case 'T':
272: outtype = optarg;
1.26 ! schwarze 273: break;
! 274: case 'v':
! 275: verbose_flag = 1;
1.1 kristaps 276: break;
1.13 schwarze 277: default:
278: usage();
1.1 kristaps 279: }
1.13 schwarze 280: }
1.1 kristaps 281:
1.13 schwarze 282: if (argc > 0) {
283: argc -= optind;
284: argv += optind;
1.1 kristaps 285: }
1.25 schwarze 286: if (argc != 2) {
287: switch (argc) {
288: case 0:
289: warnx("missing arguments: srcdir and dstdir");
290: break;
291: case 1:
292: warnx("missing argument: dstdir");
293: break;
294: default:
295: warnx("too many arguments: %s", argv[2]);
296: break;
297: }
1.13 schwarze 298: usage();
1.25 schwarze 299: }
1.1 kristaps 300:
1.13 schwarze 301: if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1)
302: err(1, "socketpair");
1.1 kristaps 303:
1.13 schwarze 304: pid = fork();
305: switch (pid) {
306: case -1:
307: err(1, "fork");
308: case 0:
309: close(srv_fds[0]);
1.14 schwarze 310: run_mandocd(srv_fds[1], outtype, defos);
1.13 schwarze 311: default:
312: break;
1.1 kristaps 313: }
1.13 schwarze 314: close(srv_fds[1]);
1.1 kristaps 315:
1.13 schwarze 316: if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1)
1.24 schwarze 317: err(1, "open destination %s", argv[1]);
1.1 kristaps 318:
1.13 schwarze 319: if (chdir(argv[0]) == -1)
1.24 schwarze 320: err(1, "chdir to source %s", argv[0]);
1.1 kristaps 321:
1.13 schwarze 322: return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0;
1.1 kristaps 323: }
324:
1.13 schwarze 325: void
326: usage(void)
1.1 kristaps 327: {
1.19 schwarze 328: fprintf(stderr, "usage: %s [-I os=name] [-T output] "
329: "srcdir dstdir\n", BINM_CATMAN);
1.13 schwarze 330: exit(1);
1.1 kristaps 331: }
CVSweb