xref: /freebsd/contrib/mandoc/catman.c (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
1 /*	$Id: catman.c,v 1.21 2017/02/18 12:24:24 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 HAVE_CMSG_XPG42
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 
44 int	 process_manpage(int, int, const char *);
45 int	 process_tree(int, int);
46 void	 run_mandocd(int, const char *, const char *)
47 		__attribute__((__noreturn__));
48 ssize_t	 sock_fd_write(int, int, int, int);
49 void	 usage(void) __attribute__((__noreturn__));
50 
51 
52 void
53 run_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 
68 ssize_t
69 sock_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 
121 int
122 process_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 
152 int
153 process_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 
199 int
200 main(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 
254 void
255 usage(void)
256 {
257 	fprintf(stderr, "usage: %s [-I os=name] [-T output] "
258 	    "srcdir dstdir\n", BINM_CATMAN);
259 	exit(1);
260 }
261