version 1.5, 2011/12/12 02:00:49 |
version 1.14, 2017/02/06 19:02:37 |
|
|
/* $Id$ */ |
/* $Id$ */ |
/* |
/* |
* Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
* 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 |
* copyright notice and this permission notice appear in all copies. |
* copyright notice and this permission notice appear in all copies. |
* |
* |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
*/ |
*/ |
#ifdef HAVE_CONFIG_H |
|
#include "config.h" |
#include "config.h" |
#endif |
|
|
|
#include <sys/param.h> |
#include <sys/types.h> |
|
#include <sys/socket.h> |
#include <sys/stat.h> |
#include <sys/stat.h> |
#include <sys/wait.h> |
|
|
|
#include <assert.h> |
#if HAVE_ERR |
|
#include <err.h> |
|
#endif |
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <getopt.h> |
#if HAVE_FTS |
|
#include <fts.h> |
|
#else |
|
#include "compat_fts.h" |
|
#endif |
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
#include <unistd.h> |
#include <unistd.h> |
|
|
#ifdef __linux__ |
int process_manpage(int, int, const char *); |
# include <db_185.h> |
int process_tree(int, int); |
#else |
void run_mandocd(int, const char *, const char *) |
# include <db.h> |
__attribute__((noreturn)); |
#endif |
ssize_t sock_fd_write(int, int, int, int); |
|
void usage(void) __attribute__((noreturn)); |
|
|
#include "manpath.h" |
|
|
|
#define xstrlcpy(_dst, _src, _sz) \ |
void |
do if (strlcpy((_dst), (_src), (_sz)) >= (_sz)) { \ |
run_mandocd(int sockfd, const char *outtype, const char* defos) |
fprintf(stderr, "%s: Path too long", (_dst)); \ |
|
exit(EXIT_FAILURE); \ |
|
} while (/* CONSTCOND */0) |
|
|
|
#define xstrlcat(_dst, _src, _sz) \ |
|
do if (strlcat((_dst), (_src), (_sz)) >= (_sz)) { \ |
|
fprintf(stderr, "%s: Path too long", (_dst)); \ |
|
exit(EXIT_FAILURE); \ |
|
} while (/* CONSTCOND */0) |
|
|
|
static int indexhtml(char *, char *); |
|
static int manup(const struct manpaths *, char *); |
|
static int mkpath(char *, mode_t, mode_t); |
|
static int treecpy(char *, char *, char *); |
|
static int update(char *, char *, char *); |
|
static void usage(void); |
|
|
|
static const char *progname; |
|
static int verbose; |
|
static int force; |
|
|
|
int |
|
main(int argc, char *argv[]) |
|
{ |
{ |
int ch; |
char sockfdstr[10]; |
char *aux, *base; |
|
struct manpaths dirs; |
|
char buf[MAXPATHLEN]; |
|
extern char *optarg; |
|
extern int optind; |
|
|
|
progname = strrchr(argv[0], '/'); |
if (snprintf(sockfdstr, sizeof(sockfdstr), "%d", sockfd) == -1) |
if (progname == NULL) |
err(1, "snprintf"); |
progname = argv[0]; |
if (defos == NULL) |
|
execlp("mandocd", "mandocd", "-T", outtype, sockfdstr, NULL); |
else |
else |
++progname; |
execlp("mandocd", "mandocd", "-T", outtype, |
|
"-I", defos, sockfdstr, NULL); |
|
err(1, "exec"); |
|
} |
|
|
aux = base = NULL; |
ssize_t |
xstrlcpy(buf, "/var/www/cache/man.cgi", MAXPATHLEN); |
sock_fd_write(int fd, int fd0, int fd1, int fd2) |
|
{ |
|
struct msghdr msg; |
|
struct iovec iov; |
|
union { |
|
struct cmsghdr cmsghdr; |
|
char control[CMSG_SPACE(3 * sizeof(int))]; |
|
} cmsgu; |
|
struct cmsghdr *cmsg; |
|
int *walk; |
|
unsigned char dummy[1] = {'\0'}; |
|
|
while (-1 != (ch = getopt(argc, argv, "fm:M:o:v"))) |
iov.iov_base = dummy; |
switch (ch) { |
iov.iov_len = sizeof(dummy); |
case ('f'): |
|
force = 1; |
|
break; |
|
case ('m'): |
|
aux = optarg; |
|
break; |
|
case ('M'): |
|
base = optarg; |
|
break; |
|
case ('o'): |
|
xstrlcpy(buf, optarg, MAXPATHLEN); |
|
break; |
|
case ('v'): |
|
verbose++; |
|
break; |
|
default: |
|
usage(); |
|
return(EXIT_FAILURE); |
|
} |
|
|
|
argc -= optind; |
msg.msg_name = NULL; |
argv += optind; |
msg.msg_namelen = 0; |
|
msg.msg_iov = &iov; |
|
msg.msg_iovlen = 1; |
|
|
if (argc > 0) { |
msg.msg_control = cmsgu.control; |
usage(); |
msg.msg_controllen = sizeof(cmsgu.control); |
return(EXIT_FAILURE); |
|
} |
|
|
|
memset(&dirs, 0, sizeof(struct manpaths)); |
cmsg = CMSG_FIRSTHDR(&msg); |
manpath_parse(&dirs, NULL, base, aux); |
cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int)); |
ch = manup(&dirs, buf); |
cmsg->cmsg_level = SOL_SOCKET; |
manpath_free(&dirs); |
cmsg->cmsg_type = SCM_RIGHTS; |
return(ch ? EXIT_SUCCESS : EXIT_FAILURE); |
|
} |
|
|
|
static void |
walk = (int *)CMSG_DATA(cmsg); |
usage(void) |
*(walk++) = fd0; |
{ |
*(walk++) = fd1; |
|
*(walk++) = fd2; |
fprintf(stderr, "usage: %s " |
|
"[-fv] " |
|
"[-o path] " |
|
"[-m manpath] " |
|
"[-M manpath]\n", |
|
progname); |
|
} |
|
|
|
/* |
return sendmsg(fd, &msg, 0); |
* If "src" file doesn't exist (errors out), return -1. Otherwise, |
|
* return 1 if "src" is newer (which also happens "dst" doesn't exist) |
|
* and 0 otherwise. |
|
*/ |
|
static int |
|
isnewer(const char *dst, const char *src) |
|
{ |
|
struct stat s1, s2; |
|
|
|
if (-1 == stat(src, &s1)) |
|
return(-1); |
|
if (force) |
|
return(1); |
|
|
|
return(-1 == stat(dst, &s2) ? 1 : s1.st_mtime > s2.st_mtime); |
|
} |
} |
|
|
/* |
int |
* Copy the contents of one file into another. |
process_manpage(int srv_fd, int dstdir_fd, const char *path) |
* Returns 0 on failure, 1 on success. |
|
*/ |
|
static int |
|
filecpy(const char *dst, const char *src) |
|
{ |
{ |
char buf[BUFSIZ]; |
int in_fd, out_fd; |
int sfd, dfd, rc; |
int irc; |
ssize_t rsz, wsz; |
|
|
|
sfd = dfd = -1; |
if ((in_fd = open(path, O_RDONLY)) == -1) { |
rc = 0; |
warn("open(%s)", path); |
|
return 0; |
|
} |
|
|
if (-1 == (dfd = open(dst, O_CREAT|O_TRUNC|O_WRONLY, 0644))) { |
if ((out_fd = openat(dstdir_fd, path, |
perror(dst); |
O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC, |
goto out; |
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { |
} else if (-1 == (sfd = open(src, O_RDONLY, 0))) { |
warn("openat(%s)", path); |
perror(src); |
close(in_fd); |
goto out; |
return 0; |
} |
} |
|
|
while ((rsz = read(sfd, buf, BUFSIZ)) > 0) |
irc = sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO); |
if (-1 == (wsz = write(dfd, buf, (size_t)rsz))) { |
|
perror(dst); |
|
goto out; |
|
} else if (wsz < rsz) { |
|
fprintf(stderr, "%s: Short write\n", dst); |
|
goto out; |
|
} |
|
|
|
if (rsz < 0) |
|
perror(src); |
|
else |
|
rc = 1; |
|
out: |
|
if (-1 != sfd) |
|
close(sfd); |
|
if (-1 != dfd) |
|
close(dfd); |
|
|
|
return(rc); |
close(in_fd); |
|
close(out_fd); |
|
|
|
if (irc < 0) { |
|
warn("sendmsg"); |
|
return -1; |
|
} |
|
return 0; |
} |
} |
|
|
/* |
int |
* Pass over the recno database and re-create HTML pages if they're |
process_tree(int srv_fd, int dstdir_fd) |
* found to be out of date. |
|
* Returns -1 on fatal error, 1 on success. |
|
*/ |
|
static int |
|
indexhtml(char *base, char *dst) |
|
{ |
{ |
DB *idx; |
FTS *ftsp; |
DBT key, val; |
FTSENT *entry; |
size_t sz; |
const char *argv[2]; |
int c, rc; |
const char *path; |
unsigned int fl; |
|
const char *f, *cp; |
|
char *d; |
|
char fname[MAXPATHLEN]; |
|
pid_t pid; |
|
|
|
sz = strlen(base); |
argv[0] = "."; |
pid = -1; |
argv[1] = (char *)NULL; |
|
|
xstrlcpy(fname, dst, MAXPATHLEN); |
if ((ftsp = fts_open((char * const *)argv, |
xstrlcat(fname, "/mandoc.index", MAXPATHLEN); |
FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) { |
|
warn("fts_open"); |
idx = dbopen(fname, O_RDONLY, 0, DB_RECNO, NULL); |
return -1; |
if (NULL == idx) { |
|
perror(fname); |
|
return(-1); |
|
} |
} |
|
|
fl = R_FIRST; |
while ((entry = fts_read(ftsp)) != NULL) { |
while (0 == (c = (*idx->seq)(idx, &key, &val, fl))) { |
path = entry->fts_path + 2; |
fl = R_NEXT; |
switch (entry->fts_info) { |
cp = (const char *)val.data; |
case FTS_F: |
if (0 == val.size) |
if (process_manpage(srv_fd, dstdir_fd, path) == -1) { |
continue; |
fts_close(ftsp); |
if (NULL == (f = memchr(cp, '\0', val.size))) |
return -1; |
|
} |
break; |
break; |
if (++f - cp >= (int)val.size) |
case FTS_D: |
|
if (*path != '\0' && |
|
mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP | |
|
S_IXGRP | S_IROTH | S_IXOTH) == -1 && |
|
errno != EEXIST) { |
|
warn("mkdirat(%s)", path); |
|
(void)fts_set(ftsp, entry, FTS_SKIP); |
|
} |
break; |
break; |
if (NULL == memchr(f, '\0', val.size - (f - cp))) |
case FTS_DP: |
break; |
break; |
|
default: |
base[(int)sz] = '\0'; |
warnx("%s: not a regular file", path); |
|
|
xstrlcat(base, "/", MAXPATHLEN); |
|
xstrlcat(base, f, MAXPATHLEN); |
|
|
|
if (-1 == (rc = isnewer(base, f))) { |
|
fprintf(stderr, "%s: File missing\n", f); |
|
break; |
break; |
} else if (0 == rc) |
|
continue; |
|
|
|
d = strrchr(base, '/'); |
|
assert(NULL != d); |
|
*d = '\0'; |
|
|
|
if (-1 == mkpath(base, 0755, 0755)) { |
|
perror(base); |
|
break; |
|
} |
} |
|
|
*d = '/'; |
|
|
|
if ( ! filecpy(base, f)) |
|
break; |
|
if (verbose) |
|
printf("%s\n", base); |
|
} |
} |
|
|
(*idx->close)(idx); |
fts_close(ftsp); |
|
return 0; |
if (c < 0) |
|
perror(fname); |
|
else if (0 == c) |
|
fprintf(stderr, "%s: Corrupt index\n", fname); |
|
|
|
return(1 == c ? 1 : -1); |
|
} |
} |
|
|
/* |
int |
* Copy both recno and btree databases into the destination. |
main(int argc, char **argv) |
* Call in to begin recreating HTML files. |
|
* Return -1 on fatal error and 1 if the update went well. |
|
*/ |
|
static int |
|
update(char *base, char *dst, char *src) |
|
{ |
{ |
size_t dsz, ssz; |
const char *defos, *outtype; |
|
int srv_fds[2]; |
|
int dstdir_fd; |
|
int opt; |
|
pid_t pid; |
|
|
dsz = strlen(dst); |
defos = NULL; |
ssz = strlen(src); |
outtype = "ascii"; |
|
while ((opt = getopt(argc, argv, "I:T:")) != -1) { |
xstrlcat(src, "/mandoc.db", MAXPATHLEN); |
switch (opt) { |
xstrlcat(dst, "/mandoc.db", MAXPATHLEN); |
case 'I': |
|
defos = optarg; |
if ( ! filecpy(dst, src)) |
|
return(-1); |
|
if (verbose) |
|
printf("%s\n", dst); |
|
|
|
dst[(int)dsz] = src[(int)ssz] = '\0'; |
|
|
|
xstrlcat(src, "/mandoc.index", MAXPATHLEN); |
|
xstrlcat(dst, "/mandoc.index", MAXPATHLEN); |
|
|
|
if ( ! filecpy(dst, src)) |
|
return(-1); |
|
if (verbose) |
|
printf("%s\n", dst); |
|
|
|
dst[(int)dsz] = '\0'; |
|
|
|
return(indexhtml(base, dst)); |
|
} |
|
|
|
/* |
|
* See if btree or recno databases in the destination are out of date |
|
* with respect to a single manpath component. |
|
* Return -1 on fatal error, 0 if the source is no longer valid (and |
|
* shouldn't be listed), and 1 if the update went well. |
|
*/ |
|
static int |
|
treecpy(char *base, char *dst, char *src) |
|
{ |
|
size_t dsz, ssz; |
|
int rc; |
|
|
|
dsz = strlen(dst); |
|
ssz = strlen(src); |
|
|
|
xstrlcat(src, "/mandoc.index", MAXPATHLEN); |
|
xstrlcat(dst, "/mandoc.index", MAXPATHLEN); |
|
|
|
if (-1 == (rc = isnewer(dst, src))) |
|
return(0); |
|
|
|
dst[(int)dsz] = src[(int)ssz] = '\0'; |
|
|
|
if (1 == rc) |
|
return(update(base, dst, src)); |
|
|
|
xstrlcat(src, "/mandoc.db", MAXPATHLEN); |
|
xstrlcat(dst, "/mandoc.db", MAXPATHLEN); |
|
|
|
if (-1 == (rc = isnewer(dst, src))) |
|
return(0); |
|
else if (rc == 0) |
|
return(1); |
|
|
|
dst[(int)dsz] = src[(int)ssz] = '\0'; |
|
|
|
return(update(base, dst, src)); |
|
} |
|
|
|
/* |
|
* Update the destination's file-tree with respect to changes in the |
|
* source manpath components. |
|
* "Change" is defined by an updated index or btree database. |
|
* Returns 1 on success, 0 on failure. |
|
*/ |
|
static int |
|
manup(const struct manpaths *dirs, char *base) |
|
{ |
|
char dst[MAXPATHLEN], |
|
src[MAXPATHLEN]; |
|
const char *path; |
|
int i, c; |
|
size_t sz; |
|
FILE *f; |
|
|
|
/* Create the path and file for the catman.conf file. */ |
|
|
|
sz = strlen(base); |
|
xstrlcpy(dst, base, MAXPATHLEN); |
|
xstrlcat(dst, "/etc", MAXPATHLEN); |
|
if (-1 == mkpath(dst, 0755, 0755)) { |
|
perror(dst); |
|
return(0); |
|
} |
|
|
|
xstrlcat(dst, "/catman.conf", MAXPATHLEN); |
|
if (NULL == (f = fopen(dst, "w"))) { |
|
perror(dst); |
|
return(0); |
|
} else if (verbose) |
|
printf("%s\n", dst); |
|
|
|
for (i = 0; i < dirs->sz; i++) { |
|
path = dirs->paths[i]; |
|
dst[(int)sz] = base[(int)sz] = '\0'; |
|
xstrlcat(dst, path, MAXPATHLEN); |
|
if (-1 == mkpath(dst, 0755, 0755)) { |
|
perror(dst); |
|
break; |
break; |
|
case 'T': |
|
outtype = optarg; |
|
break; |
|
default: |
|
usage(); |
} |
} |
|
} |
|
|
xstrlcpy(src, path, MAXPATHLEN); |
if (argc > 0) { |
if (-1 == (c = treecpy(base, dst, src))) |
argc -= optind; |
break; |
argv += optind; |
else if (0 == c) |
|
continue; |
|
|
|
/* |
|
* We want to use a relative path here because manpath.h |
|
* will realpath() when invoked with man.cgi, and we'll |
|
* make sure to chdir() into the cache directory before. |
|
* |
|
* This allows the cache directory to be in an arbitrary |
|
* place, working in both chroot() and non-chroot() |
|
* "safe" modes. |
|
*/ |
|
assert('/' == path[0]); |
|
fprintf(f, "_whatdb %s/whatis.db\n", path + 1); |
|
} |
} |
|
if (argc != 2) |
|
usage(); |
|
|
fclose(f); |
if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1) |
return(i == dirs->sz); |
err(1, "socketpair"); |
} |
|
|
|
/* |
pid = fork(); |
* Copyright (c) 1983, 1992, 1993 |
switch (pid) { |
* The Regents of the University of California. All rights reserved. |
case -1: |
* |
err(1, "fork"); |
* Redistribution and use in source and binary forms, with or without |
case 0: |
* modification, are permitted provided that the following conditions |
close(srv_fds[0]); |
* are met: |
run_mandocd(srv_fds[1], outtype, defos); |
* 1. Redistributions of source code must retain the above copyright |
default: |
* notice, this list of conditions and the following disclaimer. |
break; |
* 2. Redistributions in binary form must reproduce the above copyright |
} |
* notice, this list of conditions and the following disclaimer in the |
close(srv_fds[1]); |
* documentation and/or other materials provided with the distribution. |
|
* 3. Neither the name of the University nor the names of its contributors |
|
* may be used to endorse or promote products derived from this software |
|
* without specific prior written permission. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
* SUCH DAMAGE. |
|
*/ |
|
static int |
|
mkpath(char *path, mode_t mode, mode_t dir_mode) |
|
{ |
|
struct stat sb; |
|
char *slash; |
|
int done, exists; |
|
|
|
slash = path; |
if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1) |
|
err(1, "open(%s)", argv[1]); |
|
|
for (;;) { |
if (chdir(argv[0]) == -1) |
/* LINTED */ |
err(1, "chdir(%s)", argv[0]); |
slash += strspn(slash, "/"); |
|
/* LINTED */ |
|
slash += strcspn(slash, "/"); |
|
|
|
done = (*slash == '\0'); |
return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0; |
*slash = '\0'; |
} |
|
|
/* skip existing path components */ |
void |
exists = !stat(path, &sb); |
usage(void) |
if (!done && exists && S_ISDIR(sb.st_mode)) { |
{ |
*slash = '/'; |
fprintf(stderr, "usage: catman [-I os=name] [-T output] " |
continue; |
"srcdir dstdir\n"); |
} |
exit(1); |
|
|
if (mkdir(path, done ? mode : dir_mode) == 0) { |
|
if (mode > 0777 && chmod(path, mode) < 0) |
|
return (-1); |
|
} else { |
|
if (!exists) { |
|
/* Not there */ |
|
return (-1); |
|
} |
|
if (!S_ISDIR(sb.st_mode)) { |
|
/* Is there, but isn't a directory */ |
|
errno = ENOTDIR; |
|
return (-1); |
|
} |
|
} |
|
|
|
if (done) |
|
break; |
|
|
|
*slash = '/'; |
|
} |
|
|
|
return (0); |
|
} |
} |