version 1.10, 2012/01/03 15:17:20 |
version 1.17, 2017/02/09 18:46:44 |
|
|
/* $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" |
|
|
|
#if HAVE_CMSG_XPG42 |
|
#define _XPG4_2 |
#endif |
#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 <time.h> |
#include <unistd.h> |
#include <unistd.h> |
|
|
#ifdef __linux__ |
#ifndef O_DIRECTORY |
# include <db_185.h> |
#define O_DIRECTORY 0 |
#else |
|
# include <db.h> |
|
#endif |
#endif |
|
|
#include "manpath.h" |
int process_manpage(int, int, const char *); |
#include "mandocdb.h" |
int process_tree(int, int); |
|
void run_mandocd(int, const char *, const char *) |
|
__attribute__((noreturn)); |
|
ssize_t sock_fd_write(int, int, int, int); |
|
void usage(void) __attribute__((noreturn)); |
|
|
#define xstrlcpy(_dst, _src, _sz) \ |
|
do if (strlcpy((_dst), (_src), (_sz)) >= (_sz)) { \ |
|
fprintf(stderr, "%s: Path too long", (_dst)); \ |
|
exit(EXIT_FAILURE); \ |
|
} while (/* CONSTCOND */0) |
|
|
|
#define xstrlcat(_dst, _src, _sz) \ |
void |
do if (strlcat((_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) |
|
|
|
static int indexhtml(char *, size_t, char *, size_t); |
|
static int manup(const struct manpaths *, char *); |
|
static int mkpath(char *, mode_t, mode_t); |
|
static int treecpy(char *, char *); |
|
static int update(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, *conf_file; |
|
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); |
aux = base = conf_file = NULL; |
err(1, "exec"); |
xstrlcpy(buf, "/var/www/cache/man.cgi", MAXPATHLEN); |
|
|
|
while (-1 != (ch = getopt(argc, argv, "C:fm:M:o:v"))) |
|
switch (ch) { |
|
case ('C'): |
|
conf_file = optarg; |
|
break; |
|
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; |
|
argv += optind; |
|
|
|
if (argc > 0) { |
|
usage(); |
|
return(EXIT_FAILURE); |
|
} |
|
|
|
memset(&dirs, 0, sizeof(struct manpaths)); |
|
manpath_parse(&dirs, conf_file, base, aux); |
|
ch = manup(&dirs, buf); |
|
manpath_free(&dirs); |
|
return(ch ? EXIT_SUCCESS : EXIT_FAILURE); |
|
} |
} |
|
|
static void |
ssize_t |
usage(void) |
sock_fd_write(int fd, int fd0, int fd1, int fd2) |
{ |
{ |
|
const struct timespec timeout = { 0, 10000000 }; /* 0.01 s */ |
fprintf(stderr, "usage: %s " |
struct msghdr msg; |
"[-fv] " |
struct iovec iov; |
"[-C file] " |
union { |
"[-o path] " |
struct cmsghdr cmsghdr; |
"[-m manpath] " |
char control[CMSG_SPACE(3 * sizeof(int))]; |
"[-M manpath]\n", |
} cmsgu; |
progname); |
struct cmsghdr *cmsg; |
} |
int *walk; |
|
ssize_t sz; |
|
unsigned char dummy[1] = {'\0'}; |
|
|
/* |
iov.iov_base = dummy; |
* If "src" file doesn't exist (errors out), return -1. Otherwise, |
iov.iov_len = sizeof(dummy); |
* 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)) |
msg.msg_name = NULL; |
return(-1); |
msg.msg_namelen = 0; |
if (force) |
msg.msg_iov = &iov; |
return(1); |
msg.msg_iovlen = 1; |
|
|
return(-1 == stat(dst, &s2) ? 1 : s1.st_mtime > s2.st_mtime); |
msg.msg_control = cmsgu.control; |
} |
msg.msg_controllen = sizeof(cmsgu.control); |
|
|
/* |
cmsg = CMSG_FIRSTHDR(&msg); |
* Copy the contents of one file into another. |
cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int)); |
* Returns 0 on failure, 1 on success. |
cmsg->cmsg_level = SOL_SOCKET; |
*/ |
cmsg->cmsg_type = SCM_RIGHTS; |
static int |
|
filecpy(const char *dst, const char *src) |
|
{ |
|
char buf[BUFSIZ]; |
|
int sfd, dfd, rc; |
|
ssize_t rsz, wsz; |
|
|
|
sfd = dfd = -1; |
walk = (int *)CMSG_DATA(cmsg); |
rc = 0; |
*(walk++) = fd0; |
|
*(walk++) = fd1; |
|
*(walk++) = fd2; |
|
|
if (-1 == (dfd = open(dst, O_CREAT|O_TRUNC|O_WRONLY, 0644))) { |
/* |
perror(dst); |
* It appears that on some systems, sendmsg(3) |
goto out; |
* may return EAGAIN even in blocking mode. |
} else if (-1 == (sfd = open(src, O_RDONLY, 0))) { |
* Seen for example on Oracle Solaris 11.2. |
perror(src); |
* The sleeping time was chosen by experimentation, |
goto out; |
* to neither cause more than a handful of retries |
} |
* in normal operation nor unnecessary delays. |
|
*/ |
while ((rsz = read(sfd, buf, BUFSIZ)) > 0) |
for (;;) { |
if (-1 == (wsz = write(dfd, buf, (size_t)rsz))) { |
if ((sz = sendmsg(fd, &msg, 0)) != -1 || |
perror(dst); |
errno != EAGAIN) |
goto out; |
break; |
} else if (wsz < rsz) { |
nanosleep(&timeout, NULL); |
fprintf(stderr, "%s: Short write\n", dst); |
} |
goto out; |
return sz; |
} |
|
|
|
if (rsz < 0) |
|
perror(src); |
|
else |
|
rc = 1; |
|
out: |
|
if (-1 != sfd) |
|
close(sfd); |
|
if (-1 != dfd) |
|
close(dfd); |
|
|
|
return(rc); |
|
} |
} |
|
|
/* |
int |
* Pass over the recno database and re-create HTML pages if they're |
process_manpage(int srv_fd, int dstdir_fd, const char *path) |
* found to be out of date. |
|
* Returns -1 on fatal error, 1 on success. |
|
*/ |
|
static int |
|
indexhtml(char *src, size_t ssz, char *dst, size_t dsz) |
|
{ |
{ |
DB *idx; |
int in_fd, out_fd; |
DBT key, val; |
int irc; |
int c, rc; |
|
unsigned int fl; |
|
const char *f; |
|
char *d; |
|
char fname[MAXPATHLEN]; |
|
pid_t pid; |
|
|
|
pid = -1; |
if ((in_fd = open(path, O_RDONLY)) == -1) { |
|
warn("open(%s)", path); |
xstrlcpy(fname, dst, MAXPATHLEN); |
return 0; |
xstrlcat(fname, "/", MAXPATHLEN); |
|
xstrlcat(fname, MANDOC_IDX, MAXPATHLEN); |
|
|
|
idx = dbopen(fname, O_RDONLY, 0, DB_RECNO, NULL); |
|
if (NULL == idx) { |
|
perror(fname); |
|
return(-1); |
|
} |
} |
|
|
fl = R_FIRST; |
if ((out_fd = openat(dstdir_fd, path, |
while (0 == (c = (*idx->seq)(idx, &key, &val, fl))) { |
O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC, |
fl = R_NEXT; |
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { |
/* |
warn("openat(%s)", path); |
* If the record is zero-length, then it's unassigned. |
close(in_fd); |
* Skip past these. |
return 0; |
*/ |
|
if (0 == val.size) |
|
continue; |
|
|
|
f = (const char *)val.data + 1; |
|
if (NULL == memchr(f, '\0', val.size - 1)) |
|
break; |
|
|
|
src[(int)ssz] = dst[(int)dsz] = '\0'; |
|
|
|
xstrlcat(dst, "/", MAXPATHLEN); |
|
xstrlcat(dst, f, MAXPATHLEN); |
|
|
|
xstrlcat(src, "/", MAXPATHLEN); |
|
xstrlcat(src, f, MAXPATHLEN); |
|
|
|
if (-1 == (rc = isnewer(dst, src))) { |
|
fprintf(stderr, "%s: File missing\n", f); |
|
break; |
|
} else if (0 == rc) |
|
continue; |
|
|
|
d = strrchr(dst, '/'); |
|
assert(NULL != d); |
|
*d = '\0'; |
|
|
|
if (-1 == mkpath(dst, 0755, 0755)) { |
|
perror(dst); |
|
break; |
|
} |
|
|
|
*d = '/'; |
|
|
|
if ( ! filecpy(dst, src)) |
|
break; |
|
if (verbose) |
|
printf("%s\n", dst); |
|
} |
} |
|
|
(*idx->close)(idx); |
irc = sock_fd_write(srv_fd, in_fd, out_fd, STDERR_FILENO); |
|
|
if (c < 0) |
close(in_fd); |
perror(fname); |
close(out_fd); |
else if (0 == c) |
|
fprintf(stderr, "%s: Corrupt index\n", fname); |
|
|
|
return(1 == c ? 1 : -1); |
if (irc < 0) { |
|
warn("sendmsg"); |
|
return -1; |
|
} |
|
return 0; |
} |
} |
|
|
/* |
int |
* Copy both recno and btree databases into the destination. |
process_tree(int srv_fd, int dstdir_fd) |
* Call in to begin recreating HTML files. |
|
* Return -1 on fatal error and 1 if the update went well. |
|
*/ |
|
static int |
|
update(char *dst, char *src) |
|
{ |
{ |
size_t dsz, ssz; |
FTS *ftsp; |
|
FTSENT *entry; |
dsz = strlen(dst); |
const char *argv[2]; |
ssz = strlen(src); |
|
|
|
xstrlcat(src, "/", MAXPATHLEN); |
|
xstrlcat(dst, "/", MAXPATHLEN); |
|
|
|
xstrlcat(src, MANDOC_DB, MAXPATHLEN); |
|
xstrlcat(dst, MANDOC_DB, MAXPATHLEN); |
|
|
|
if ( ! filecpy(dst, src)) |
|
return(-1); |
|
if (verbose) |
|
printf("%s\n", dst); |
|
|
|
dst[(int)dsz] = src[(int)ssz] = '\0'; |
|
|
|
xstrlcat(src, "/", MAXPATHLEN); |
|
xstrlcat(dst, "/", MAXPATHLEN); |
|
|
|
xstrlcat(src, MANDOC_IDX, MAXPATHLEN); |
|
xstrlcat(dst, MANDOC_IDX, MAXPATHLEN); |
|
|
|
if ( ! filecpy(dst, src)) |
|
return(-1); |
|
if (verbose) |
|
printf("%s\n", dst); |
|
|
|
dst[(int)dsz] = src[(int)ssz] = '\0'; |
|
|
|
return(indexhtml(src, ssz, dst, dsz)); |
|
} |
|
|
|
/* |
|
* 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 *dst, char *src) |
|
{ |
|
size_t dsz, ssz; |
|
int rc; |
|
|
|
dsz = strlen(dst); |
|
ssz = strlen(src); |
|
|
|
xstrlcat(src, "/", MAXPATHLEN); |
|
xstrlcat(dst, "/", MAXPATHLEN); |
|
|
|
xstrlcat(src, MANDOC_IDX, MAXPATHLEN); |
|
xstrlcat(dst, MANDOC_IDX, MAXPATHLEN); |
|
|
|
if (-1 == (rc = isnewer(dst, src))) |
|
return(0); |
|
|
|
dst[(int)dsz] = src[(int)ssz] = '\0'; |
|
|
|
if (1 == rc) |
|
return(update(dst, src)); |
|
|
|
xstrlcat(src, "/", MAXPATHLEN); |
|
xstrlcat(dst, "/", MAXPATHLEN); |
|
|
|
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(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; |
const char *path; |
int i, c; |
|
size_t sz; |
|
FILE *f; |
|
|
|
/* Create the path and file for the catman.conf file. */ |
argv[0] = "."; |
|
argv[1] = (char *)NULL; |
|
|
sz = strlen(base); |
if ((ftsp = fts_open((char * const *)argv, |
xstrlcpy(dst, base, MAXPATHLEN); |
FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) { |
xstrlcat(dst, "/etc", MAXPATHLEN); |
warn("fts_open"); |
if (-1 == mkpath(dst, 0755, 0755)) { |
return -1; |
perror(dst); |
|
return(0); |
|
} |
} |
|
|
xstrlcat(dst, "/catman.conf", MAXPATHLEN); |
while ((entry = fts_read(ftsp)) != NULL) { |
if (NULL == (f = fopen(dst, "w"))) { |
path = entry->fts_path + 2; |
perror(dst); |
switch (entry->fts_info) { |
return(0); |
case FTS_F: |
} else if (verbose) |
if (process_manpage(srv_fd, dstdir_fd, path) == -1) { |
printf("%s\n", dst); |
fts_close(ftsp); |
|
return -1; |
for (i = 0; i < dirs->sz; i++) { |
} |
path = dirs->paths[i]; |
|
dst[(int)sz] = '\0'; |
|
xstrlcat(dst, path, MAXPATHLEN); |
|
if (-1 == mkpath(dst, 0755, 0755)) { |
|
perror(dst); |
|
break; |
break; |
} |
case FTS_D: |
|
if (*path != '\0' && |
xstrlcpy(src, path, MAXPATHLEN); |
mkdirat(dstdir_fd, path, S_IRWXU | S_IRGRP | |
if (-1 == (c = treecpy(dst, src))) |
S_IXGRP | S_IROTH | S_IXOTH) == -1 && |
|
errno != EEXIST) { |
|
warn("mkdirat(%s)", path); |
|
(void)fts_set(ftsp, entry, FTS_SKIP); |
|
} |
break; |
break; |
else if (0 == c) |
case FTS_DP: |
continue; |
break; |
|
default: |
/* |
warnx("%s: not a regular file", path); |
* We want to use a relative path here because manpath.h |
break; |
* 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); |
|
} |
} |
|
|
fclose(f); |
fts_close(ftsp); |
return(i == dirs->sz); |
return 0; |
} |
} |
|
|
/* |
int |
* Copyright (c) 1983, 1992, 1993 |
main(int argc, char **argv) |
* The Regents of the University of California. All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* 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; |
const char *defos, *outtype; |
char *slash; |
int srv_fds[2]; |
int done, exists; |
int dstdir_fd; |
|
int opt; |
|
pid_t pid; |
|
|
slash = path; |
defos = NULL; |
|
outtype = "ascii"; |
|
while ((opt = getopt(argc, argv, "I:T:")) != -1) { |
|
switch (opt) { |
|
case 'I': |
|
defos = optarg; |
|
break; |
|
case 'T': |
|
outtype = optarg; |
|
break; |
|
default: |
|
usage(); |
|
} |
|
} |
|
|
for (;;) { |
if (argc > 0) { |
/* LINTED */ |
argc -= optind; |
slash += strspn(slash, "/"); |
argv += optind; |
/* LINTED */ |
} |
slash += strcspn(slash, "/"); |
if (argc != 2) |
|
usage(); |
|
|
done = (*slash == '\0'); |
if (socketpair(AF_LOCAL, SOCK_STREAM, AF_UNSPEC, srv_fds) == -1) |
*slash = '\0'; |
err(1, "socketpair"); |
|
|
/* skip existing path components */ |
pid = fork(); |
exists = !stat(path, &sb); |
switch (pid) { |
if (!done && exists && S_ISDIR(sb.st_mode)) { |
case -1: |
*slash = '/'; |
err(1, "fork"); |
continue; |
case 0: |
} |
close(srv_fds[0]); |
|
run_mandocd(srv_fds[1], outtype, defos); |
|
default: |
|
break; |
|
} |
|
close(srv_fds[1]); |
|
|
if (mkdir(path, done ? mode : dir_mode) == 0) { |
if ((dstdir_fd = open(argv[1], O_RDONLY | O_DIRECTORY)) == -1) |
if (mode > 0777 && chmod(path, mode) < 0) |
err(1, "open(%s)", argv[1]); |
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) |
if (chdir(argv[0]) == -1) |
break; |
err(1, "chdir(%s)", argv[0]); |
|
|
*slash = '/'; |
return process_tree(srv_fds[0], dstdir_fd) == -1 ? 1 : 0; |
} |
} |
|
|
return (0); |
void |
|
usage(void) |
|
{ |
|
fprintf(stderr, "usage: catman [-I os=name] [-T output] " |
|
"srcdir dstdir\n"); |
|
exit(1); |
} |
} |