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