[BACK]Return to catman.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / mandoc

Annotation of mandoc/catman.c, Revision 1.27

1.27    ! schwarze    1: /* $Id: catman.c,v 1.26 2025/06/29 23:51:40 schwarze Exp $ */
1.1       kristaps    2: /*
1.26      schwarze    3:  * Copyright (c) 2017, 2025 Ingo Schwarze <schwarze@openbsd.org>
1.13      schwarze    4:  * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
1.1       kristaps    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:  *
1.13      schwarze   10:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.1       kristaps   11:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.13      schwarze   12:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.1       kristaps   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"
1.16      schwarze   19:
1.22      schwarze   20: #if NEED_XPG4_2
1.16      schwarze   21: #define _XPG4_2
                     22: #endif
1.1       kristaps   23:
1.13      schwarze   24: #include <sys/types.h>
                     25: #include <sys/socket.h>
1.1       kristaps   26: #include <sys/stat.h>
                     27:
1.13      schwarze   28: #if HAVE_ERR
                     29: #include <err.h>
                     30: #endif
1.14      schwarze   31: #include <errno.h>
1.1       kristaps   32: #include <fcntl.h>
1.13      schwarze   33: #if HAVE_FTS
                     34: #include <fts.h>
                     35: #else
                     36: #include "compat_fts.h"
                     37: #endif
1.27    ! schwarze   38: #include <signal.h>
        !            39: #include <stdint.h>
1.1       kristaps   40: #include <stdio.h>
                     41: #include <stdlib.h>
                     42: #include <string.h>
1.15      schwarze   43: #include <time.h>
1.1       kristaps   44: #include <unistd.h>
                     45:
1.26      schwarze   46: int            verbose_flag = 0;
1.27    ! schwarze   47: sig_atomic_t   got_signal = 0;
1.26      schwarze   48:
1.13      schwarze   49: int     process_manpage(int, int, const char *);
                     50: int     process_tree(int, int);
1.14      schwarze   51: void    run_mandocd(int, const char *, const char *)
1.20      schwarze   52:                __attribute__((__noreturn__));
1.27    ! schwarze   53: void    signal_handler(int);
1.13      schwarze   54: ssize_t         sock_fd_write(int, int, int, int);
1.20      schwarze   55: void    usage(void) __attribute__((__noreturn__));
1.13      schwarze   56:
                     57:
                     58: void
1.27    ! schwarze   59: signal_handler(int signum)
        !            60: {
        !            61:        got_signal = signum;
        !            62: }
        !            63:
        !            64: void
1.14      schwarze   65: run_mandocd(int sockfd, const char *outtype, const char* defos)
1.13      schwarze   66: {
                     67:        char     sockfdstr[10];
1.24      schwarze   68:        int      len;
1.13      schwarze   69:
1.24      schwarze   70:        len = snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd);
                     71:        if (len >= (int)sizeof(sockfdstr)) {
                     72:                errno = EOVERFLOW;
                     73:                len = -1;
                     74:        }
                     75:        if (len < 0)
1.13      schwarze   76:                err(1, "snprintf");
1.14      schwarze   77:        if (defos == NULL)
1.18      schwarze   78:                execlp("mandocd", "mandocd", "-T", outtype,
                     79:                    sockfdstr, (char *)NULL);
1.14      schwarze   80:        else
                     81:                execlp("mandocd", "mandocd", "-T", outtype,
1.18      schwarze   82:                    "-I", defos, sockfdstr, (char *)NULL);
1.23      schwarze   83:        err(1, "exec(mandocd)");
1.13      schwarze   84: }
                     85:
                     86: ssize_t
                     87: sock_fd_write(int fd, int fd0, int fd1, int fd2)
                     88: {
1.15      schwarze   89:        const struct timespec timeout = { 0, 10000000 };  /* 0.01 s */
1.13      schwarze   90:        struct msghdr    msg;
                     91:        struct iovec     iov;
                     92:        union {
                     93:                struct cmsghdr   cmsghdr;
                     94:                char             control[CMSG_SPACE(3 * sizeof(int))];
                     95:        } cmsgu;
                     96:        struct cmsghdr  *cmsg;
                     97:        int             *walk;
1.15      schwarze   98:        ssize_t          sz;
1.13      schwarze   99:        unsigned char    dummy[1] = {'\0'};
                    100:
                    101:        iov.iov_base = dummy;
                    102:        iov.iov_len = sizeof(dummy);
                    103:
                    104:        msg.msg_name = NULL;
                    105:        msg.msg_namelen = 0;
                    106:        msg.msg_iov = &iov;
                    107:        msg.msg_iovlen = 1;
                    108:
                    109:        msg.msg_control = cmsgu.control;
                    110:        msg.msg_controllen = sizeof(cmsgu.control);
                    111:
                    112:        cmsg = CMSG_FIRSTHDR(&msg);
                    113:        cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int));
                    114:        cmsg->cmsg_level = SOL_SOCKET;
                    115:        cmsg->cmsg_type = SCM_RIGHTS;
                    116:
                    117:        walk = (int *)CMSG_DATA(cmsg);
                    118:        *(walk++) = fd0;
                    119:        *(walk++) = fd1;
                    120:        *(walk++) = fd2;
1.1       kristaps  121:
1.15      schwarze  122:        /*
                    123:         * It appears that on some systems, sendmsg(3)
                    124:         * may return EAGAIN even in blocking mode.
                    125:         * Seen for example on Oracle Solaris 11.2.
                    126:         * The sleeping time was chosen by experimentation,
                    127:         * to neither cause more than a handful of retries
                    128:         * in normal operation nor unnecessary delays.
                    129:         */
1.24      schwarze  130:        while ((sz = sendmsg(fd, &msg, 0)) == -1) {
                    131:                if (errno != EAGAIN) {
                    132:                        warn("FATAL: sendmsg");
1.15      schwarze  133:                        break;
1.24      schwarze  134:                }
1.15      schwarze  135:                nanosleep(&timeout, NULL);
                    136:        }
                    137:        return sz;
1.13      schwarze  138: }
1.1       kristaps  139:
                    140: int
1.13      schwarze  141: process_manpage(int srv_fd, int dstdir_fd, const char *path)
1.1       kristaps  142: {
1.13      schwarze  143:        int      in_fd, out_fd;
1.14      schwarze  144:        int      irc;
1.1       kristaps  145:
1.13      schwarze  146:        if ((in_fd = open(path, O_RDONLY)) == -1) {
1.24      schwarze  147:                warn("open %s for reading", path);
                    148:                fflush(stderr);
1.14      schwarze  149:                return 0;
1.13      schwarze  150:        }
1.1       kristaps  151:
1.13      schwarze  152:        if ((out_fd = openat(dstdir_fd, path,
                    153:            O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC,
1.14      schwarze  154:            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
1.24      schwarze  155:                warn("openat %s for writing", path);
                    156:                fflush(stderr);
1.13      schwarze  157:                close(in_fd);
1.14      schwarze  158:                return 0;
1.1       kristaps  159:        }
                    160:
1.14      schwarze  161:        irc = sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO);
                    162:
                    163:        close(in_fd);
                    164:        close(out_fd);
                    165:
1.24      schwarze  166:        return irc;
1.1       kristaps  167: }
                    168:
1.13      schwarze  169: int
                    170: process_tree(int srv_fd, int dstdir_fd)
1.1       kristaps  171: {
1.13      schwarze  172:        FTS             *ftsp;
                    173:        FTSENT          *entry;
                    174:        const char      *argv[2];
                    175:        const char      *path;
1.24      schwarze  176:        int              fatal;
                    177:        int              gooddirs, baddirs, goodfiles, badfiles;
1.1       kristaps  178:
1.13      schwarze  179:        argv[0] = ".";
                    180:        argv[1] = (char *)NULL;
1.1       kristaps  181:
1.13      schwarze  182:        if ((ftsp = fts_open((char * const *)argv,
                    183:            FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) {
                    184:                warn("fts_open");
                    185:                return -1;
                    186:        }
                    187:
1.24      schwarze  188:        fatal = 0;
                    189:        gooddirs = baddirs = goodfiles = badfiles = 0;
1.27    ! schwarze  190:        while (fatal == 0 && got_signal == 0 &&
        !           191:            (entry = fts_read(ftsp)) != NULL) {
1.13      schwarze  192:                path = entry->fts_path + 2;
                    193:                switch (entry->fts_info) {
                    194:                case FTS_F:
1.24      schwarze  195:                        switch (process_manpage(srv_fd, dstdir_fd, path)) {
                    196:                        case -1:
                    197:                                fatal = errno;
                    198:                                break;
                    199:                        case 0:
                    200:                                badfiles++;
                    201:                                break;
                    202:                        default:
                    203:                                goodfiles++;
                    204:                                break;
1.14      schwarze  205:                        }
1.13      schwarze  206:                        break;
                    207:                case FTS_D:
1.14      schwarze  208:                        if (*path != '\0' &&
                    209:                            mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP |
                    210:                              S_IXGRP | S_IROTH | S_IXOTH) == -1 &&
                    211:                            errno != EEXIST) {
1.24      schwarze  212:                                warn("mkdirat %s", path);
                    213:                                fflush(stderr);
1.14      schwarze  214:                                (void)fts_set(ftsp, entry, FTS_SKIP);
1.24      schwarze  215:                                baddirs++;
                    216:                        } else
                    217:                                gooddirs++;
1.14      schwarze  218:                        break;
1.13      schwarze  219:                case FTS_DP:
                    220:                        break;
1.24      schwarze  221:                case FTS_DNR:
                    222:                        warnx("directory %s unreadable: %s",
                    223:                            path, strerror(entry->fts_errno));
                    224:                        fflush(stderr);
                    225:                        baddirs++;
                    226:                        break;
                    227:                case FTS_DC:
                    228:                        warnx("directory %s causes cycle", path);
                    229:                        fflush(stderr);
                    230:                        baddirs++;
                    231:                        break;
                    232:                case FTS_ERR:
                    233:                case FTS_NS:
                    234:                        warnx("file %s: %s",
                    235:                            path, strerror(entry->fts_errno));
                    236:                        fflush(stderr);
                    237:                        badfiles++;
                    238:                        break;
1.13      schwarze  239:                default:
1.25      schwarze  240:                        warnx("file %s: not a regular file", path);
1.24      schwarze  241:                        fflush(stderr);
                    242:                        badfiles++;
1.13      schwarze  243:                        break;
1.1       kristaps  244:                }
1.13      schwarze  245:        }
1.27    ! schwarze  246:        if (got_signal != 0) {
        !           247:                switch (got_signal) {
        !           248:                case SIGCHLD:
        !           249:                        warnx("FATAL: mandocd child died: got SIGCHLD");
        !           250:                        break;
        !           251:                case SIGPIPE:
        !           252:                        warnx("FATAL: mandocd child died: got SIGPIPE");
        !           253:                        break;
        !           254:                default:
        !           255:                        warnx("FATAL: signal SIG%s", sys_signame[got_signal]);
        !           256:                        break;
        !           257:                }
        !           258:                fatal = 1;
        !           259:        } else if (fatal == 0 && (fatal = errno) != 0)
1.24      schwarze  260:                warn("FATAL: fts_read");
1.1       kristaps  261:
1.13      schwarze  262:        fts_close(ftsp);
1.26      schwarze  263:        if (verbose_flag)
                    264:                warnx("processed %d files in %d directories",
                    265:                    goodfiles, gooddirs);
1.24      schwarze  266:        if (baddirs > 0)
                    267:                warnx("skipped %d %s due to errors", baddirs,
                    268:                    baddirs == 1 ? "directory" : "directories");
                    269:        if (badfiles > 0)
                    270:                warnx("skipped %d %s due to errors", badfiles,
                    271:                    badfiles == 1 ? "file" : "files");
                    272:        if (fatal != 0) {
                    273:                warnx("processing aborted due to fatal error, "
                    274:                    "results are probably incomplete");
                    275:        }
1.13      schwarze  276:        return 0;
1.1       kristaps  277: }
                    278:
1.13      schwarze  279: int
                    280: main(int argc, char **argv)
1.1       kristaps  281: {
1.27    ! schwarze  282:        struct sigaction sa;
1.14      schwarze  283:        const char      *defos, *outtype;
1.13      schwarze  284:        int              srv_fds[2];
                    285:        int              dstdir_fd;
                    286:        int              opt;
1.1       kristaps  287:        pid_t            pid;
                    288:
1.14      schwarze  289:        defos = NULL;
1.13      schwarze  290:        outtype = "ascii";
1.26      schwarze  291:        while ((opt = getopt(argc, argv, "I:T:v")) != -1) {
1.13      schwarze  292:                switch (opt) {
1.14      schwarze  293:                case 'I':
                    294:                        defos = optarg;
                    295:                        break;
1.13      schwarze  296:                case 'T':
                    297:                        outtype = optarg;
1.26      schwarze  298:                        break;
                    299:                case 'v':
                    300:                        verbose_flag = 1;
1.1       kristaps  301:                        break;
1.13      schwarze  302:                default:
                    303:                        usage();
1.1       kristaps  304:                }
1.13      schwarze  305:        }
1.1       kristaps  306:
1.13      schwarze  307:        if (argc > 0) {
                    308:                argc -= optind;
                    309:                argv += optind;
1.1       kristaps  310:        }
1.25      schwarze  311:        if (argc != 2) {
                    312:                switch (argc) {
                    313:                case 0:
                    314:                        warnx("missing arguments: srcdir and dstdir");
                    315:                        break;
                    316:                case 1:
                    317:                        warnx("missing argument: dstdir");
                    318:                        break;
                    319:                default:
                    320:                        warnx("too many arguments: %s", argv[2]);
                    321:                        break;
                    322:                }
1.13      schwarze  323:                usage();
1.25      schwarze  324:        }
1.27    ! schwarze  325:
        !           326:        memset(&sa, 0, sizeof(sa));
        !           327:        sa.sa_handler = &signal_handler;
        !           328:        sa.sa_flags = SA_NOCLDWAIT;
        !           329:        if (sigfillset(&sa.sa_mask) == -1)
        !           330:                err(1, "sigfillset");
        !           331:        if (sigaction(SIGHUP, &sa, NULL) == -1)
        !           332:                err(1, "sigaction(SIGHUP)");
        !           333:        if (sigaction(SIGINT, &sa, NULL) == -1)
        !           334:                err(1, "sigaction(SIGINT)");
        !           335:        if (sigaction(SIGPIPE, &sa, NULL) == -1)
        !           336:                err(1, "sigaction(SIGPIPE)");
        !           337:        if (sigaction(SIGTERM, &sa, NULL) == -1)
        !           338:                err(1, "sigaction(SIGTERM)");
        !           339:        if (sigaction(SIGCHLD, &sa, NULL) == -1)
        !           340:                err(1, "sigaction(SIGCHLD)");
1.1       kristaps  341:
1.13      schwarze  342:        if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1)
                    343:                err(1, "socketpair");
1.1       kristaps  344:
1.13      schwarze  345:        pid = fork();
                    346:        switch (pid) {
                    347:        case -1:
                    348:                err(1, "fork");
                    349:        case 0:
                    350:                close(srv_fds[0]);
1.14      schwarze  351:                run_mandocd(srv_fds[1], outtype, defos);
1.13      schwarze  352:        default:
                    353:                break;
1.1       kristaps  354:        }
1.13      schwarze  355:        close(srv_fds[1]);
1.1       kristaps  356:
1.13      schwarze  357:        if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1)
1.24      schwarze  358:                err(1, "open destination %s", argv[1]);
1.1       kristaps  359:
1.13      schwarze  360:        if (chdir(argv[0]) == -1)
1.24      schwarze  361:                err(1, "chdir to source %s", argv[0]);
1.1       kristaps  362:
1.13      schwarze  363:        return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0;
1.1       kristaps  364: }
                    365:
1.13      schwarze  366: void
                    367: usage(void)
1.1       kristaps  368: {
1.19      schwarze  369:        fprintf(stderr, "usage: %s [-I os=name] [-T output] "
                    370:            "srcdir dstdir\n", BINM_CATMAN);
1.13      schwarze  371:        exit(1);
1.1       kristaps  372: }

CVSweb