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