1/*	$Id: catman.c,v 1.22 2020/06/14 23:40:31 schwarze Exp $ */
2/*
3 * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
4 * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org>
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 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
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"
19
20#if NEED_XPG4_2
21#define _XPG4_2
22#endif
23
24#include <sys/types.h>
25#include <sys/socket.h>
26#include <sys/stat.h>
27
28#if HAVE_ERR
29#include <err.h>
30#endif
31#include <errno.h>
32#include <fcntl.h>
33#if HAVE_FTS
34#include <fts.h>
35#else
36#include "compat_fts.h"
37#endif
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <time.h>
42#include <unistd.h>
43
44int	 process_manpage(int, int, const char *);
45int	 process_tree(int, int);
46void	 run_mandocd(int, const char *, const char *)
47		__attribute__((__noreturn__));
48ssize_t	 sock_fd_write(int, int, int, int);
49void	 usage(void) __attribute__((__noreturn__));
50
51
52void
53run_mandocd(int sockfd, const char *outtype, const char* defos)
54{
55	char	 sockfdstr[10];
56
57	if (snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd) == -1)
58		err(1, "snprintf");
59	if (defos == NULL)
60		execlp("mandocd", "mandocd", "-T", outtype,
61		    sockfdstr, (char *)NULL);
62	else
63		execlp("mandocd", "mandocd", "-T", outtype,
64		    "-I", defos, sockfdstr, (char *)NULL);
65	err(1, "exec");
66}
67
68ssize_t
69sock_fd_write(int fd, int fd0, int fd1, int fd2)
70{
71	const struct timespec timeout = { 0, 10000000 };  /* 0.01 s */
72	struct msghdr	 msg;
73	struct iovec	 iov;
74	union {
75		struct cmsghdr	 cmsghdr;
76		char		 control[CMSG_SPACE(3 * sizeof(int))];
77	} cmsgu;
78	struct cmsghdr	*cmsg;
79	int		*walk;
80	ssize_t		 sz;
81	unsigned char	 dummy[1] = {'\0'};
82
83	iov.iov_base = dummy;
84	iov.iov_len = sizeof(dummy);
85
86	msg.msg_name = NULL;
87	msg.msg_namelen = 0;
88	msg.msg_iov = &iov;
89	msg.msg_iovlen = 1;
90
91	msg.msg_control = cmsgu.control;
92	msg.msg_controllen = sizeof(cmsgu.control);
93
94	cmsg = CMSG_FIRSTHDR(&msg);
95	cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int));
96	cmsg->cmsg_level = SOL_SOCKET;
97	cmsg->cmsg_type = SCM_RIGHTS;
98
99	walk = (int *)CMSG_DATA(cmsg);
100	*(walk++) = fd0;
101	*(walk++) = fd1;
102	*(walk++) = fd2;
103
104	/*
105	 * It appears that on some systems, sendmsg(3)
106	 * may return EAGAIN even in blocking mode.
107	 * Seen for example on Oracle Solaris 11.2.
108	 * The sleeping time was chosen by experimentation,
109	 * to neither cause more than a handful of retries
110	 * in normal operation nor unnecessary delays.
111	 */
112	for (;;) {
113		if ((sz = sendmsg(fd, &msg, 0)) != -1 ||
114		    errno != EAGAIN)
115			break;
116		nanosleep(&timeout, NULL);
117	}
118	return sz;
119}
120
121int
122process_manpage(int srv_fd, int dstdir_fd, const char *path)
123{
124	int	 in_fd, out_fd;
125	int	 irc;
126
127	if ((in_fd = open(path, O_RDONLY)) == -1) {
128		warn("open(%s)", path);
129		return 0;
130	}
131
132	if ((out_fd = openat(dstdir_fd, path,
133	    O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC,
134	    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
135		warn("openat(%s)", path);
136		close(in_fd);
137		return 0;
138	}
139
140	irc = sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO);
141
142	close(in_fd);
143	close(out_fd);
144
145	if (irc < 0) {
146		warn("sendmsg");
147		return -1;
148	}
149	return 0;
150}
151
152int
153process_tree(int srv_fd, int dstdir_fd)
154{
155	FTS		*ftsp;
156	FTSENT		*entry;
157	const char	*argv[2];
158	const char	*path;
159
160	argv[0] = ".";
161	argv[1] = (char *)NULL;
162
163	if ((ftsp = fts_open((char * const *)argv,
164	    FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) {
165		warn("fts_open");
166		return -1;
167	}
168
169	while ((entry = fts_read(ftsp)) != NULL) {
170		path = entry->fts_path + 2;
171		switch (entry->fts_info) {
172		case FTS_F:
173			if (process_manpage(srv_fd, dstdir_fd, path) == -1) {
174				fts_close(ftsp);
175				return -1;
176			}
177			break;
178		case FTS_D:
179			if (*path != '\0' &&
180			    mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP |
181			      S_IXGRP | S_IROTH | S_IXOTH) == -1 &&
182			    errno != EEXIST) {
183				warn("mkdirat(%s)", path);
184				(void)fts_set(ftsp, entry, FTS_SKIP);
185			}
186			break;
187		case FTS_DP:
188			break;
189		default:
190			warnx("%s: not a regular file", path);
191			break;
192		}
193	}
194
195	fts_close(ftsp);
196	return 0;
197}
198
199int
200main(int argc, char **argv)
201{
202	const char	*defos, *outtype;
203	int		 srv_fds[2];
204	int		 dstdir_fd;
205	int		 opt;
206	pid_t		 pid;
207
208	defos = NULL;
209	outtype = "ascii";
210	while ((opt = getopt(argc, argv, "I:T:")) != -1) {
211		switch (opt) {
212		case 'I':
213			defos = optarg;
214			break;
215		case 'T':
216			outtype = optarg;
217			break;
218		default:
219			usage();
220		}
221	}
222
223	if (argc > 0) {
224		argc -= optind;
225		argv += optind;
226	}
227	if (argc != 2)
228		usage();
229
230	if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1)
231		err(1, "socketpair");
232
233	pid = fork();
234	switch (pid) {
235	case -1:
236		err(1, "fork");
237	case 0:
238		close(srv_fds[0]);
239		run_mandocd(srv_fds[1], outtype, defos);
240	default:
241		break;
242	}
243	close(srv_fds[1]);
244
245	if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1)
246		err(1, "open(%s)", argv[1]);
247
248	if (chdir(argv[0]) == -1)
249		err(1, "chdir(%s)", argv[0]);
250
251	return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0;
252}
253
254void
255usage(void)
256{
257	fprintf(stderr, "usage: %s [-I os=name] [-T output] "
258	    "srcdir dstdir\n", BINM_CATMAN);
259	exit(1);
260}
261