version 1.16, 2017/02/08 16:11:40 |
version 1.29, 2025/06/30 12:23:42 |
|
|
/* $Id$ */ |
/* $Id$ */ |
/* |
/* |
|
* Copyright (c) 2017, 2025 Ingo Schwarze <schwarze@openbsd.org> |
* Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org> |
* Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org> |
* Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org> |
|
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
|
|
*/ |
*/ |
#include "config.h" |
#include "config.h" |
|
|
#if HAVE_CMSG_XPG42 |
#if NEED_XPG4_2 |
#define _XPG4_2 |
#define _XPG4_2 |
#endif |
#endif |
|
|
|
|
#include <sys/socket.h> |
#include <sys/socket.h> |
#include <sys/stat.h> |
#include <sys/stat.h> |
|
|
|
#include <assert.h> |
#if HAVE_ERR |
#if HAVE_ERR |
#include <err.h> |
#include <err.h> |
#endif |
#endif |
|
|
#else |
#else |
#include "compat_fts.h" |
#include "compat_fts.h" |
#endif |
#endif |
|
#include <signal.h> |
|
#include <stdint.h> |
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
#include <time.h> |
#include <time.h> |
#include <unistd.h> |
#include <unistd.h> |
|
|
|
int verbose_flag = 0; |
|
sig_atomic_t got_signal = 0; |
|
|
int process_manpage(int, int, const char *); |
int process_manpage(int, int, const char *); |
int process_tree(int, int); |
int process_tree(int, int); |
void run_mandocd(int, const char *, const char *) |
void run_mandocd(int, const char *, const char *) |
__attribute__((noreturn)); |
__attribute__((__noreturn__)); |
|
void signal_handler(int); |
ssize_t sock_fd_write(int, int, int, int); |
ssize_t sock_fd_write(int, int, int, int); |
void usage(void) __attribute__((noreturn)); |
void usage(void) __attribute__((__noreturn__)); |
|
|
|
|
void |
void |
|
signal_handler(int signum) |
|
{ |
|
got_signal = signum; |
|
} |
|
|
|
void |
run_mandocd(int sockfd, const char *outtype, const char* defos) |
run_mandocd(int sockfd, const char *outtype, const char* defos) |
{ |
{ |
char sockfdstr[10]; |
char sockfdstr[10]; |
|
int len; |
|
|
if (snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd) == -1) |
len = snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd); |
|
if (len >= (int)sizeof(sockfdstr)) { |
|
errno = EOVERFLOW; |
|
len = -1; |
|
} |
|
if (len < 0) |
err(1, "snprintf"); |
err(1, "snprintf"); |
if (defos == NULL) |
if (defos == NULL) |
execlp("mandocd", "mandocd", "-T", outtype, sockfdstr, NULL); |
execlp("mandocd", "mandocd", "-T", outtype, |
|
sockfdstr, (char *)NULL); |
else |
else |
execlp("mandocd", "mandocd", "-T", outtype, |
execlp("mandocd", "mandocd", "-T", outtype, |
"-I", defos, sockfdstr, NULL); |
"-I", defos, sockfdstr, (char *)NULL); |
err(1, "exec"); |
err(1, "exec(mandocd)"); |
} |
} |
|
|
ssize_t |
ssize_t |
Line 108 sock_fd_write(int fd, int fd0, int fd1, int fd2) |
|
Line 128 sock_fd_write(int fd, int fd0, int fd1, int fd2) |
|
* to neither cause more than a handful of retries |
* to neither cause more than a handful of retries |
* in normal operation nor unnecessary delays. |
* in normal operation nor unnecessary delays. |
*/ |
*/ |
for (;;) { |
while ((sz = sendmsg(fd, &msg, 0)) == -1) { |
if ((sz = sendmsg(fd, &msg, 0)) != -1 || |
if (errno != EAGAIN) { |
errno != EAGAIN) |
warn("FATAL: sendmsg"); |
break; |
break; |
|
} |
nanosleep(&timeout, NULL); |
nanosleep(&timeout, NULL); |
} |
} |
return sz; |
return sz; |
Line 124 process_manpage(int srv_fd, int dstdir_fd, const char |
|
Line 145 process_manpage(int srv_fd, int dstdir_fd, const char |
|
int irc; |
int irc; |
|
|
if ((in_fd = open(path, O_RDONLY)) == -1) { |
if ((in_fd = open(path, O_RDONLY)) == -1) { |
warn("open(%s)", path); |
warn("open %s for reading", path); |
|
fflush(stderr); |
return 0; |
return 0; |
} |
} |
|
|
if ((out_fd = openat(dstdir_fd, path, |
if ((out_fd = openat(dstdir_fd, path, |
O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC, |
O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC, |
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { |
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { |
warn("openat(%s)", path); |
warn("openat %s for writing", path); |
|
fflush(stderr); |
close(in_fd); |
close(in_fd); |
return 0; |
return 0; |
} |
} |
Line 141 process_manpage(int srv_fd, int dstdir_fd, const char |
|
Line 164 process_manpage(int srv_fd, int dstdir_fd, const char |
|
close(in_fd); |
close(in_fd); |
close(out_fd); |
close(out_fd); |
|
|
if (irc < 0) { |
return irc; |
warn("sendmsg"); |
|
return -1; |
|
} |
|
return 0; |
|
} |
} |
|
|
int |
int |
process_tree(int srv_fd, int dstdir_fd) |
process_tree(int srv_fd, int dstdir_fd) |
{ |
{ |
|
const struct timespec timeout = { 0, 10000000 }; /* 0.01 s */ |
|
const int max_inflight = 16; |
|
|
FTS *ftsp; |
FTS *ftsp; |
FTSENT *entry; |
FTSENT *entry; |
const char *argv[2]; |
const char *argv[2]; |
const char *path; |
const char *path; |
|
int inflight, irc, decr, fatal; |
|
int gooddirs, baddirs, goodfiles, badfiles; |
|
char dummy[1]; |
|
|
argv[0] = "."; |
argv[0] = "."; |
argv[1] = (char *)NULL; |
argv[1] = (char *)NULL; |
Line 165 process_tree(int srv_fd, int dstdir_fd) |
|
Line 190 process_tree(int srv_fd, int dstdir_fd) |
|
return -1; |
return -1; |
} |
} |
|
|
while ((entry = fts_read(ftsp)) != NULL) { |
if (verbose_flag >= 2) { |
|
warnx("allowing up to %d files in flight", max_inflight); |
|
fflush(stderr); |
|
} |
|
inflight = fatal = gooddirs = baddirs = goodfiles = badfiles = 0; |
|
while (fatal == 0 && got_signal == 0 && |
|
(entry = fts_read(ftsp)) != NULL) { |
|
if (inflight >= max_inflight) { |
|
while (recv(srv_fd, dummy, sizeof(dummy), 0) == -1) { |
|
if (errno != EAGAIN) { |
|
warn("FATAL: recv"); |
|
fatal = errno; |
|
break; |
|
} |
|
nanosleep(&timeout, NULL); |
|
} |
|
if (fatal != 0) |
|
break; |
|
decr = 1; |
|
while ((irc = recv(srv_fd, dummy, sizeof(dummy), |
|
MSG_DONTWAIT)) > 0) |
|
decr++; |
|
assert(inflight >= decr); |
|
if (verbose_flag >= 2 && decr > 1) { |
|
warnx("files in flight: %d - %d = %d", |
|
inflight, decr, inflight - decr); |
|
fflush(stderr); |
|
} |
|
inflight -= decr; |
|
if (irc == 0) { |
|
errno = ECONNRESET; |
|
inflight = -1; |
|
} |
|
if (errno != EAGAIN) { |
|
warn("FATAL: recv"); |
|
fatal = errno; |
|
break; |
|
} |
|
} |
path = entry->fts_path + 2; |
path = entry->fts_path + 2; |
switch (entry->fts_info) { |
switch (entry->fts_info) { |
case FTS_F: |
case FTS_F: |
if (process_manpage(srv_fd, dstdir_fd, path) == -1) { |
switch (process_manpage(srv_fd, dstdir_fd, path)) { |
fts_close(ftsp); |
case -1: |
return -1; |
fatal = errno; |
|
break; |
|
case 0: |
|
badfiles++; |
|
break; |
|
default: |
|
goodfiles++; |
|
inflight++; |
|
break; |
} |
} |
break; |
break; |
case FTS_D: |
case FTS_D: |
Line 179 process_tree(int srv_fd, int dstdir_fd) |
|
Line 250 process_tree(int srv_fd, int dstdir_fd) |
|
mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP | |
mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP | |
S_IXGRP | S_IROTH | S_IXOTH) == -1 && |
S_IXGRP | S_IROTH | S_IXOTH) == -1 && |
errno != EEXIST) { |
errno != EEXIST) { |
warn("mkdirat(%s)", path); |
warn("mkdirat %s", path); |
|
fflush(stderr); |
(void)fts_set(ftsp, entry, FTS_SKIP); |
(void)fts_set(ftsp, entry, FTS_SKIP); |
} |
baddirs++; |
|
} else |
|
gooddirs++; |
break; |
break; |
case FTS_DP: |
case FTS_DP: |
break; |
break; |
|
case FTS_DNR: |
|
warnx("directory %s unreadable: %s", |
|
path, strerror(entry->fts_errno)); |
|
fflush(stderr); |
|
baddirs++; |
|
break; |
|
case FTS_DC: |
|
warnx("directory %s causes cycle", path); |
|
fflush(stderr); |
|
baddirs++; |
|
break; |
|
case FTS_ERR: |
|
case FTS_NS: |
|
warnx("file %s: %s", |
|
path, strerror(entry->fts_errno)); |
|
fflush(stderr); |
|
badfiles++; |
|
break; |
default: |
default: |
warnx("%s: not a regular file", path); |
warnx("file %s: not a regular file", path); |
|
fflush(stderr); |
|
badfiles++; |
break; |
break; |
} |
} |
} |
} |
|
if (got_signal != 0) { |
|
switch (got_signal) { |
|
case SIGCHLD: |
|
warnx("FATAL: mandocd child died: got SIGCHLD"); |
|
break; |
|
case SIGPIPE: |
|
warnx("FATAL: mandocd child died: got SIGPIPE"); |
|
break; |
|
default: |
|
warnx("FATAL: signal SIG%s", sys_signame[got_signal]); |
|
break; |
|
} |
|
inflight = -1; |
|
fatal = 1; |
|
} else if (fatal == 0 && (fatal = errno) != 0) |
|
warn("FATAL: fts_read"); |
|
|
fts_close(ftsp); |
fts_close(ftsp); |
return 0; |
if (verbose_flag >= 2 && inflight > 0) { |
|
warnx("waiting for %d files in flight", inflight); |
|
fflush(stderr); |
|
} |
|
while (inflight > 0) { |
|
irc = recv(srv_fd, dummy, sizeof(dummy), 0); |
|
if (irc > 0) |
|
inflight--; |
|
else if (irc == -1 && errno == EAGAIN) |
|
nanosleep(&timeout, NULL); |
|
else { |
|
if (irc == 0) |
|
errno = ECONNRESET; |
|
warn("recv"); |
|
inflight = -1; |
|
} |
|
} |
|
if (verbose_flag) |
|
warnx("processed %d files in %d directories", |
|
goodfiles, gooddirs); |
|
if (baddirs > 0) |
|
warnx("skipped %d %s due to errors", baddirs, |
|
baddirs == 1 ? "directory" : "directories"); |
|
if (badfiles > 0) |
|
warnx("skipped %d %s due to errors", badfiles, |
|
badfiles == 1 ? "file" : "files"); |
|
if (fatal != 0) { |
|
warnx("processing aborted due to fatal error, " |
|
"results are probably incomplete"); |
|
inflight = -1; |
|
} |
|
return inflight; |
} |
} |
|
|
int |
int |
main(int argc, char **argv) |
main(int argc, char **argv) |
{ |
{ |
|
struct sigaction sa; |
const char *defos, *outtype; |
const char *defos, *outtype; |
int srv_fds[2]; |
int srv_fds[2]; |
int dstdir_fd; |
int dstdir_fd; |
Line 206 main(int argc, char **argv) |
|
Line 348 main(int argc, char **argv) |
|
|
|
defos = NULL; |
defos = NULL; |
outtype = "ascii"; |
outtype = "ascii"; |
while ((opt = getopt(argc, argv, "I:T:")) != -1) { |
while ((opt = getopt(argc, argv, "I:T:v")) != -1) { |
switch (opt) { |
switch (opt) { |
case 'I': |
case 'I': |
defos = optarg; |
defos = optarg; |
Line 214 main(int argc, char **argv) |
|
Line 356 main(int argc, char **argv) |
|
case 'T': |
case 'T': |
outtype = optarg; |
outtype = optarg; |
break; |
break; |
|
case 'v': |
|
verbose_flag += 1; |
|
break; |
default: |
default: |
usage(); |
usage(); |
} |
} |
Line 223 main(int argc, char **argv) |
|
Line 368 main(int argc, char **argv) |
|
argc -= optind; |
argc -= optind; |
argv += optind; |
argv += optind; |
} |
} |
if (argc != 2) |
if (argc != 2) { |
|
switch (argc) { |
|
case 0: |
|
warnx("missing arguments: srcdir and dstdir"); |
|
break; |
|
case 1: |
|
warnx("missing argument: dstdir"); |
|
break; |
|
default: |
|
warnx("too many arguments: %s", argv[2]); |
|
break; |
|
} |
usage(); |
usage(); |
|
} |
|
|
|
memset(&sa, 0, sizeof(sa)); |
|
sa.sa_handler = &signal_handler; |
|
sa.sa_flags = SA_NOCLDWAIT; |
|
if (sigfillset(&sa.sa_mask) == -1) |
|
err(1, "sigfillset"); |
|
if (sigaction(SIGHUP, &sa, NULL) == -1) |
|
err(1, "sigaction(SIGHUP)"); |
|
if (sigaction(SIGINT, &sa, NULL) == -1) |
|
err(1, "sigaction(SIGINT)"); |
|
if (sigaction(SIGPIPE, &sa, NULL) == -1) |
|
err(1, "sigaction(SIGPIPE)"); |
|
if (sigaction(SIGTERM, &sa, NULL) == -1) |
|
err(1, "sigaction(SIGTERM)"); |
|
if (sigaction(SIGCHLD, &sa, NULL) == -1) |
|
err(1, "sigaction(SIGCHLD)"); |
|
|
if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1) |
if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1) |
err(1, "socketpair"); |
err(1, "socketpair"); |
|
|
Line 242 main(int argc, char **argv) |
|
Line 415 main(int argc, char **argv) |
|
close(srv_fds[1]); |
close(srv_fds[1]); |
|
|
if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1) |
if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1) |
err(1, "open(%s)", argv[1]); |
err(1, "open destination %s", argv[1]); |
|
|
if (chdir(argv[0]) == -1) |
if (chdir(argv[0]) == -1) |
err(1, "chdir(%s)", argv[0]); |
err(1, "chdir to source %s", argv[0]); |
|
|
return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0; |
return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0; |
} |
} |
Line 253 main(int argc, char **argv) |
|
Line 426 main(int argc, char **argv) |
|
void |
void |
usage(void) |
usage(void) |
{ |
{ |
fprintf(stderr, "usage: catman [-I os=name] [-T output] " |
fprintf(stderr, "usage: %s [-I os=name] [-T output] " |
"srcdir dstdir\n"); |
"srcdir dstdir\n", BINM_CATMAN); |
exit(1); |
exit(1); |
} |
} |