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