1*6d38604fSBaptiste Daroussin /* $Id: catman.c,v 1.22 2020/06/14 23:40:31 schwarze Exp $ */ 261d06d6bSBaptiste Daroussin /* 361d06d6bSBaptiste Daroussin * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org> 461d06d6bSBaptiste Daroussin * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org> 561d06d6bSBaptiste Daroussin * 661d06d6bSBaptiste Daroussin * Permission to use, copy, modify, and distribute this software for any 761d06d6bSBaptiste Daroussin * purpose with or without fee is hereby granted, provided that the above 861d06d6bSBaptiste Daroussin * copyright notice and this permission notice appear in all copies. 961d06d6bSBaptiste Daroussin * 1061d06d6bSBaptiste Daroussin * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 1161d06d6bSBaptiste Daroussin * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1261d06d6bSBaptiste Daroussin * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 1361d06d6bSBaptiste Daroussin * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1461d06d6bSBaptiste Daroussin * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1561d06d6bSBaptiste Daroussin * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1661d06d6bSBaptiste Daroussin * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1761d06d6bSBaptiste Daroussin */ 1861d06d6bSBaptiste Daroussin #include "config.h" 1961d06d6bSBaptiste Daroussin 20*6d38604fSBaptiste Daroussin #if NEED_XPG4_2 2161d06d6bSBaptiste Daroussin #define _XPG4_2 2261d06d6bSBaptiste Daroussin #endif 2361d06d6bSBaptiste Daroussin 2461d06d6bSBaptiste Daroussin #include <sys/types.h> 2561d06d6bSBaptiste Daroussin #include <sys/socket.h> 2661d06d6bSBaptiste Daroussin #include <sys/stat.h> 2761d06d6bSBaptiste Daroussin 2861d06d6bSBaptiste Daroussin #if HAVE_ERR 2961d06d6bSBaptiste Daroussin #include <err.h> 3061d06d6bSBaptiste Daroussin #endif 3161d06d6bSBaptiste Daroussin #include <errno.h> 3261d06d6bSBaptiste Daroussin #include <fcntl.h> 3361d06d6bSBaptiste Daroussin #if HAVE_FTS 3461d06d6bSBaptiste Daroussin #include <fts.h> 3561d06d6bSBaptiste Daroussin #else 3661d06d6bSBaptiste Daroussin #include "compat_fts.h" 3761d06d6bSBaptiste Daroussin #endif 3861d06d6bSBaptiste Daroussin #include <stdio.h> 3961d06d6bSBaptiste Daroussin #include <stdlib.h> 4061d06d6bSBaptiste Daroussin #include <string.h> 4161d06d6bSBaptiste Daroussin #include <time.h> 4261d06d6bSBaptiste Daroussin #include <unistd.h> 4361d06d6bSBaptiste Daroussin 4461d06d6bSBaptiste Daroussin int process_manpage(int, int, const char *); 4561d06d6bSBaptiste Daroussin int process_tree(int, int); 4661d06d6bSBaptiste Daroussin void run_mandocd(int, const char *, const char *) 4761d06d6bSBaptiste Daroussin __attribute__((__noreturn__)); 4861d06d6bSBaptiste Daroussin ssize_t sock_fd_write(int, int, int, int); 4961d06d6bSBaptiste Daroussin void usage(void) __attribute__((__noreturn__)); 5061d06d6bSBaptiste Daroussin 5161d06d6bSBaptiste Daroussin 5261d06d6bSBaptiste Daroussin void 5361d06d6bSBaptiste Daroussin run_mandocd(int sockfd, const char *outtype, const char* defos) 5461d06d6bSBaptiste Daroussin { 5561d06d6bSBaptiste Daroussin char sockfdstr[10]; 5661d06d6bSBaptiste Daroussin 5761d06d6bSBaptiste Daroussin if (snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd) == -1) 5861d06d6bSBaptiste Daroussin err(1, "snprintf"); 5961d06d6bSBaptiste Daroussin if (defos == NULL) 6061d06d6bSBaptiste Daroussin execlp("mandocd", "mandocd", "-T", outtype, 6161d06d6bSBaptiste Daroussin sockfdstr, (char *)NULL); 6261d06d6bSBaptiste Daroussin else 6361d06d6bSBaptiste Daroussin execlp("mandocd", "mandocd", "-T", outtype, 6461d06d6bSBaptiste Daroussin "-I", defos, sockfdstr, (char *)NULL); 6561d06d6bSBaptiste Daroussin err(1, "exec"); 6661d06d6bSBaptiste Daroussin } 6761d06d6bSBaptiste Daroussin 6861d06d6bSBaptiste Daroussin ssize_t 6961d06d6bSBaptiste Daroussin sock_fd_write(int fd, int fd0, int fd1, int fd2) 7061d06d6bSBaptiste Daroussin { 7161d06d6bSBaptiste Daroussin const struct timespec timeout = { 0, 10000000 }; /* 0.01 s */ 7261d06d6bSBaptiste Daroussin struct msghdr msg; 7361d06d6bSBaptiste Daroussin struct iovec iov; 7461d06d6bSBaptiste Daroussin union { 7561d06d6bSBaptiste Daroussin struct cmsghdr cmsghdr; 7661d06d6bSBaptiste Daroussin char control[CMSG_SPACE(3 * sizeof(int))]; 7761d06d6bSBaptiste Daroussin } cmsgu; 7861d06d6bSBaptiste Daroussin struct cmsghdr *cmsg; 7961d06d6bSBaptiste Daroussin int *walk; 8061d06d6bSBaptiste Daroussin ssize_t sz; 8161d06d6bSBaptiste Daroussin unsigned char dummy[1] = {'\0'}; 8261d06d6bSBaptiste Daroussin 8361d06d6bSBaptiste Daroussin iov.iov_base = dummy; 8461d06d6bSBaptiste Daroussin iov.iov_len = sizeof(dummy); 8561d06d6bSBaptiste Daroussin 8661d06d6bSBaptiste Daroussin msg.msg_name = NULL; 8761d06d6bSBaptiste Daroussin msg.msg_namelen = 0; 8861d06d6bSBaptiste Daroussin msg.msg_iov = &iov; 8961d06d6bSBaptiste Daroussin msg.msg_iovlen = 1; 9061d06d6bSBaptiste Daroussin 9161d06d6bSBaptiste Daroussin msg.msg_control = cmsgu.control; 9261d06d6bSBaptiste Daroussin msg.msg_controllen = sizeof(cmsgu.control); 9361d06d6bSBaptiste Daroussin 9461d06d6bSBaptiste Daroussin cmsg = CMSG_FIRSTHDR(&msg); 9561d06d6bSBaptiste Daroussin cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int)); 9661d06d6bSBaptiste Daroussin cmsg->cmsg_level = SOL_SOCKET; 9761d06d6bSBaptiste Daroussin cmsg->cmsg_type = SCM_RIGHTS; 9861d06d6bSBaptiste Daroussin 9961d06d6bSBaptiste Daroussin walk = (int *)CMSG_DATA(cmsg); 10061d06d6bSBaptiste Daroussin *(walk++) = fd0; 10161d06d6bSBaptiste Daroussin *(walk++) = fd1; 10261d06d6bSBaptiste Daroussin *(walk++) = fd2; 10361d06d6bSBaptiste Daroussin 10461d06d6bSBaptiste Daroussin /* 10561d06d6bSBaptiste Daroussin * It appears that on some systems, sendmsg(3) 10661d06d6bSBaptiste Daroussin * may return EAGAIN even in blocking mode. 10761d06d6bSBaptiste Daroussin * Seen for example on Oracle Solaris 11.2. 10861d06d6bSBaptiste Daroussin * The sleeping time was chosen by experimentation, 10961d06d6bSBaptiste Daroussin * to neither cause more than a handful of retries 11061d06d6bSBaptiste Daroussin * in normal operation nor unnecessary delays. 11161d06d6bSBaptiste Daroussin */ 11261d06d6bSBaptiste Daroussin for (;;) { 11361d06d6bSBaptiste Daroussin if ((sz = sendmsg(fd, &msg, 0)) != -1 || 11461d06d6bSBaptiste Daroussin errno != EAGAIN) 11561d06d6bSBaptiste Daroussin break; 11661d06d6bSBaptiste Daroussin nanosleep(&timeout, NULL); 11761d06d6bSBaptiste Daroussin } 11861d06d6bSBaptiste Daroussin return sz; 11961d06d6bSBaptiste Daroussin } 12061d06d6bSBaptiste Daroussin 12161d06d6bSBaptiste Daroussin int 12261d06d6bSBaptiste Daroussin process_manpage(int srv_fd, int dstdir_fd, const char *path) 12361d06d6bSBaptiste Daroussin { 12461d06d6bSBaptiste Daroussin int in_fd, out_fd; 12561d06d6bSBaptiste Daroussin int irc; 12661d06d6bSBaptiste Daroussin 12761d06d6bSBaptiste Daroussin if ((in_fd = open(path, O_RDONLY)) == -1) { 12861d06d6bSBaptiste Daroussin warn("open(%s)", path); 12961d06d6bSBaptiste Daroussin return 0; 13061d06d6bSBaptiste Daroussin } 13161d06d6bSBaptiste Daroussin 13261d06d6bSBaptiste Daroussin if ((out_fd = openat(dstdir_fd, path, 13361d06d6bSBaptiste Daroussin O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC, 13461d06d6bSBaptiste Daroussin S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { 13561d06d6bSBaptiste Daroussin warn("openat(%s)", path); 13661d06d6bSBaptiste Daroussin close(in_fd); 13761d06d6bSBaptiste Daroussin return 0; 13861d06d6bSBaptiste Daroussin } 13961d06d6bSBaptiste Daroussin 14061d06d6bSBaptiste Daroussin irc = sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO); 14161d06d6bSBaptiste Daroussin 14261d06d6bSBaptiste Daroussin close(in_fd); 14361d06d6bSBaptiste Daroussin close(out_fd); 14461d06d6bSBaptiste Daroussin 14561d06d6bSBaptiste Daroussin if (irc < 0) { 14661d06d6bSBaptiste Daroussin warn("sendmsg"); 14761d06d6bSBaptiste Daroussin return -1; 14861d06d6bSBaptiste Daroussin } 14961d06d6bSBaptiste Daroussin return 0; 15061d06d6bSBaptiste Daroussin } 15161d06d6bSBaptiste Daroussin 15261d06d6bSBaptiste Daroussin int 15361d06d6bSBaptiste Daroussin process_tree(int srv_fd, int dstdir_fd) 15461d06d6bSBaptiste Daroussin { 15561d06d6bSBaptiste Daroussin FTS *ftsp; 15661d06d6bSBaptiste Daroussin FTSENT *entry; 15761d06d6bSBaptiste Daroussin const char *argv[2]; 15861d06d6bSBaptiste Daroussin const char *path; 15961d06d6bSBaptiste Daroussin 16061d06d6bSBaptiste Daroussin argv[0] = "."; 16161d06d6bSBaptiste Daroussin argv[1] = (char *)NULL; 16261d06d6bSBaptiste Daroussin 16361d06d6bSBaptiste Daroussin if ((ftsp = fts_open((char * const *)argv, 16461d06d6bSBaptiste Daroussin FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) { 16561d06d6bSBaptiste Daroussin warn("fts_open"); 16661d06d6bSBaptiste Daroussin return -1; 16761d06d6bSBaptiste Daroussin } 16861d06d6bSBaptiste Daroussin 16961d06d6bSBaptiste Daroussin while ((entry = fts_read(ftsp)) != NULL) { 17061d06d6bSBaptiste Daroussin path = entry->fts_path + 2; 17161d06d6bSBaptiste Daroussin switch (entry->fts_info) { 17261d06d6bSBaptiste Daroussin case FTS_F: 17361d06d6bSBaptiste Daroussin if (process_manpage(srv_fd, dstdir_fd, path) == -1) { 17461d06d6bSBaptiste Daroussin fts_close(ftsp); 17561d06d6bSBaptiste Daroussin return -1; 17661d06d6bSBaptiste Daroussin } 17761d06d6bSBaptiste Daroussin break; 17861d06d6bSBaptiste Daroussin case FTS_D: 17961d06d6bSBaptiste Daroussin if (*path != '\0' && 18061d06d6bSBaptiste Daroussin mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP | 18161d06d6bSBaptiste Daroussin S_IXGRP | S_IROTH | S_IXOTH) == -1 && 18261d06d6bSBaptiste Daroussin errno != EEXIST) { 18361d06d6bSBaptiste Daroussin warn("mkdirat(%s)", path); 18461d06d6bSBaptiste Daroussin (void)fts_set(ftsp, entry, FTS_SKIP); 18561d06d6bSBaptiste Daroussin } 18661d06d6bSBaptiste Daroussin break; 18761d06d6bSBaptiste Daroussin case FTS_DP: 18861d06d6bSBaptiste Daroussin break; 18961d06d6bSBaptiste Daroussin default: 19061d06d6bSBaptiste Daroussin warnx("%s: not a regular file", path); 19161d06d6bSBaptiste Daroussin break; 19261d06d6bSBaptiste Daroussin } 19361d06d6bSBaptiste Daroussin } 19461d06d6bSBaptiste Daroussin 19561d06d6bSBaptiste Daroussin fts_close(ftsp); 19661d06d6bSBaptiste Daroussin return 0; 19761d06d6bSBaptiste Daroussin } 19861d06d6bSBaptiste Daroussin 19961d06d6bSBaptiste Daroussin int 20061d06d6bSBaptiste Daroussin main(int argc, char **argv) 20161d06d6bSBaptiste Daroussin { 20261d06d6bSBaptiste Daroussin const char *defos, *outtype; 20361d06d6bSBaptiste Daroussin int srv_fds[2]; 20461d06d6bSBaptiste Daroussin int dstdir_fd; 20561d06d6bSBaptiste Daroussin int opt; 20661d06d6bSBaptiste Daroussin pid_t pid; 20761d06d6bSBaptiste Daroussin 20861d06d6bSBaptiste Daroussin defos = NULL; 20961d06d6bSBaptiste Daroussin outtype = "ascii"; 21061d06d6bSBaptiste Daroussin while ((opt = getopt(argc, argv, "I:T:")) != -1) { 21161d06d6bSBaptiste Daroussin switch (opt) { 21261d06d6bSBaptiste Daroussin case 'I': 21361d06d6bSBaptiste Daroussin defos = optarg; 21461d06d6bSBaptiste Daroussin break; 21561d06d6bSBaptiste Daroussin case 'T': 21661d06d6bSBaptiste Daroussin outtype = optarg; 21761d06d6bSBaptiste Daroussin break; 21861d06d6bSBaptiste Daroussin default: 21961d06d6bSBaptiste Daroussin usage(); 22061d06d6bSBaptiste Daroussin } 22161d06d6bSBaptiste Daroussin } 22261d06d6bSBaptiste Daroussin 22361d06d6bSBaptiste Daroussin if (argc > 0) { 22461d06d6bSBaptiste Daroussin argc -= optind; 22561d06d6bSBaptiste Daroussin argv += optind; 22661d06d6bSBaptiste Daroussin } 22761d06d6bSBaptiste Daroussin if (argc != 2) 22861d06d6bSBaptiste Daroussin usage(); 22961d06d6bSBaptiste Daroussin 23061d06d6bSBaptiste Daroussin if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1) 23161d06d6bSBaptiste Daroussin err(1, "socketpair"); 23261d06d6bSBaptiste Daroussin 23361d06d6bSBaptiste Daroussin pid = fork(); 23461d06d6bSBaptiste Daroussin switch (pid) { 23561d06d6bSBaptiste Daroussin case -1: 23661d06d6bSBaptiste Daroussin err(1, "fork"); 23761d06d6bSBaptiste Daroussin case 0: 23861d06d6bSBaptiste Daroussin close(srv_fds[0]); 23961d06d6bSBaptiste Daroussin run_mandocd(srv_fds[1], outtype, defos); 24061d06d6bSBaptiste Daroussin default: 24161d06d6bSBaptiste Daroussin break; 24261d06d6bSBaptiste Daroussin } 24361d06d6bSBaptiste Daroussin close(srv_fds[1]); 24461d06d6bSBaptiste Daroussin 24561d06d6bSBaptiste Daroussin if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1) 24661d06d6bSBaptiste Daroussin err(1, "open(%s)", argv[1]); 24761d06d6bSBaptiste Daroussin 24861d06d6bSBaptiste Daroussin if (chdir(argv[0]) == -1) 24961d06d6bSBaptiste Daroussin err(1, "chdir(%s)", argv[0]); 25061d06d6bSBaptiste Daroussin 25161d06d6bSBaptiste Daroussin return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0; 25261d06d6bSBaptiste Daroussin } 25361d06d6bSBaptiste Daroussin 25461d06d6bSBaptiste Daroussin void 25561d06d6bSBaptiste Daroussin usage(void) 25661d06d6bSBaptiste Daroussin { 25761d06d6bSBaptiste Daroussin fprintf(stderr, "usage: %s [-I os=name] [-T output] " 25861d06d6bSBaptiste Daroussin "srcdir dstdir\n", BINM_CATMAN); 25961d06d6bSBaptiste Daroussin exit(1); 26061d06d6bSBaptiste Daroussin } 261