1/* $Vendor-Id: catman.c,v 1.10 2012/01/03 15:17:20 kristaps Exp $ */ 2/* 3 * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17#ifdef HAVE_CONFIG_H 18#include "config.h" 19#endif 20 21#include <sys/param.h> 22#include <sys/stat.h> 23#include <sys/wait.h> 24 25#include <assert.h> 26#include <errno.h> 27#include <fcntl.h> 28#include <getopt.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32#include <unistd.h> 33 34#ifdef __linux__ 35# include <db_185.h> 36#else 37# include <db.h> 38#endif 39 40#include "manpath.h" 41#include "mandocdb.h" 42 43#define xstrlcpy(_dst, _src, _sz) \ 44 do if (strlcpy((_dst), (_src), (_sz)) >= (_sz)) { \ 45 fprintf(stderr, "%s: Path too long", (_dst)); \ 46 exit(EXIT_FAILURE); \ 47 } while (/* CONSTCOND */0) 48 49#define xstrlcat(_dst, _src, _sz) \ 50 do if (strlcat((_dst), (_src), (_sz)) >= (_sz)) { \ 51 fprintf(stderr, "%s: Path too long", (_dst)); \ 52 exit(EXIT_FAILURE); \ 53 } while (/* CONSTCOND */0) 54 55static int indexhtml(char *, size_t, char *, size_t); 56static int manup(const struct manpaths *, char *); 57static int mkpath(char *, mode_t, mode_t); 58static int treecpy(char *, char *); 59static int update(char *, char *); 60static void usage(void); 61 62static const char *progname; 63static int verbose; 64static int force; 65 66int 67main(int argc, char *argv[]) 68{ 69 int ch; 70 char *aux, *base, *conf_file; 71 struct manpaths dirs; 72 char buf[MAXPATHLEN]; 73 extern char *optarg; 74 extern int optind; 75 76 progname = strrchr(argv[0], '/'); 77 if (progname == NULL) 78 progname = argv[0]; 79 else 80 ++progname; 81 82 aux = base = conf_file = NULL; 83 xstrlcpy(buf, "/var/www/cache/man.cgi", MAXPATHLEN); 84 85 while (-1 != (ch = getopt(argc, argv, "C:fm:M:o:v"))) 86 switch (ch) { 87 case ('C'): 88 conf_file = optarg; 89 break; 90 case ('f'): 91 force = 1; 92 break; 93 case ('m'): 94 aux = optarg; 95 break; 96 case ('M'): 97 base = optarg; 98 break; 99 case ('o'): 100 xstrlcpy(buf, optarg, MAXPATHLEN); 101 break; 102 case ('v'): 103 verbose++; 104 break; 105 default: 106 usage(); 107 return(EXIT_FAILURE); 108 } 109 110 argc -= optind; 111 argv += optind; 112 113 if (argc > 0) { 114 usage(); 115 return(EXIT_FAILURE); 116 } 117 118 memset(&dirs, 0, sizeof(struct manpaths)); 119 manpath_parse(&dirs, conf_file, base, aux); 120 ch = manup(&dirs, buf); 121 manpath_free(&dirs); 122 return(ch ? EXIT_SUCCESS : EXIT_FAILURE); 123} 124 125static void 126usage(void) 127{ 128 129 fprintf(stderr, "usage: %s " 130 "[-fv] " 131 "[-C file] " 132 "[-o path] " 133 "[-m manpath] " 134 "[-M manpath]\n", 135 progname); 136} 137 138/* 139 * If "src" file doesn't exist (errors out), return -1. Otherwise, 140 * return 1 if "src" is newer (which also happens "dst" doesn't exist) 141 * and 0 otherwise. 142 */ 143static int 144isnewer(const char *dst, const char *src) 145{ 146 struct stat s1, s2; 147 148 if (-1 == stat(src, &s1)) 149 return(-1); 150 if (force) 151 return(1); 152 153 return(-1 == stat(dst, &s2) ? 1 : s1.st_mtime > s2.st_mtime); 154} 155 156/* 157 * Copy the contents of one file into another. 158 * Returns 0 on failure, 1 on success. 159 */ 160static int 161filecpy(const char *dst, const char *src) 162{ 163 char buf[BUFSIZ]; 164 int sfd, dfd, rc; 165 ssize_t rsz, wsz; 166 167 sfd = dfd = -1; 168 rc = 0; 169 170 if (-1 == (dfd = open(dst, O_CREAT|O_TRUNC|O_WRONLY, 0644))) { 171 perror(dst); 172 goto out; 173 } else if (-1 == (sfd = open(src, O_RDONLY, 0))) { 174 perror(src); 175 goto out; 176 } 177 178 while ((rsz = read(sfd, buf, BUFSIZ)) > 0) 179 if (-1 == (wsz = write(dfd, buf, (size_t)rsz))) { 180 perror(dst); 181 goto out; 182 } else if (wsz < rsz) { 183 fprintf(stderr, "%s: Short write\n", dst); 184 goto out; 185 } 186 187 if (rsz < 0) 188 perror(src); 189 else 190 rc = 1; 191out: 192 if (-1 != sfd) 193 close(sfd); 194 if (-1 != dfd) 195 close(dfd); 196 197 return(rc); 198} 199 200/* 201 * Pass over the recno database and re-create HTML pages if they're 202 * found to be out of date. 203 * Returns -1 on fatal error, 1 on success. 204 */ 205static int 206indexhtml(char *src, size_t ssz, char *dst, size_t dsz) 207{ 208 DB *idx; 209 DBT key, val; 210 int c, rc; 211 unsigned int fl; 212 const char *f; 213 char *d; 214 char fname[MAXPATHLEN]; 215 pid_t pid; 216 217 pid = -1; 218 219 xstrlcpy(fname, dst, MAXPATHLEN); 220 xstrlcat(fname, "/", MAXPATHLEN); 221 xstrlcat(fname, MANDOC_IDX, MAXPATHLEN); 222 223 idx = dbopen(fname, O_RDONLY, 0, DB_RECNO, NULL); 224 if (NULL == idx) { 225 perror(fname); 226 return(-1); 227 } 228 229 fl = R_FIRST; 230 while (0 == (c = (*idx->seq)(idx, &key, &val, fl))) { 231 fl = R_NEXT; 232 /* 233 * If the record is zero-length, then it's unassigned. 234 * Skip past these. 235 */ 236 if (0 == val.size) 237 continue; 238 239 f = (const char *)val.data + 1; 240 if (NULL == memchr(f, '\0', val.size - 1)) 241 break; 242 243 src[(int)ssz] = dst[(int)dsz] = '\0'; 244 245 xstrlcat(dst, "/", MAXPATHLEN); 246 xstrlcat(dst, f, MAXPATHLEN); 247 248 xstrlcat(src, "/", MAXPATHLEN); 249 xstrlcat(src, f, MAXPATHLEN); 250 251 if (-1 == (rc = isnewer(dst, src))) { 252 fprintf(stderr, "%s: File missing\n", f); 253 break; 254 } else if (0 == rc) 255 continue; 256 257 d = strrchr(dst, '/'); 258 assert(NULL != d); 259 *d = '\0'; 260 261 if (-1 == mkpath(dst, 0755, 0755)) { 262 perror(dst); 263 break; 264 } 265 266 *d = '/'; 267 268 if ( ! filecpy(dst, src)) 269 break; 270 if (verbose) 271 printf("%s\n", dst); 272 } 273 274 (*idx->close)(idx); 275 276 if (c < 0) 277 perror(fname); 278 else if (0 == c) 279 fprintf(stderr, "%s: Corrupt index\n", fname); 280 281 return(1 == c ? 1 : -1); 282} 283 284/* 285 * Copy both recno and btree databases into the destination. 286 * Call in to begin recreating HTML files. 287 * Return -1 on fatal error and 1 if the update went well. 288 */ 289static int 290update(char *dst, char *src) 291{ 292 size_t dsz, ssz; 293 294 dsz = strlen(dst); 295 ssz = strlen(src); 296 297 xstrlcat(src, "/", MAXPATHLEN); 298 xstrlcat(dst, "/", MAXPATHLEN); 299 300 xstrlcat(src, MANDOC_DB, MAXPATHLEN); 301 xstrlcat(dst, MANDOC_DB, MAXPATHLEN); 302 303 if ( ! filecpy(dst, src)) 304 return(-1); 305 if (verbose) 306 printf("%s\n", dst); 307 308 dst[(int)dsz] = src[(int)ssz] = '\0'; 309 310 xstrlcat(src, "/", MAXPATHLEN); 311 xstrlcat(dst, "/", MAXPATHLEN); 312 313 xstrlcat(src, MANDOC_IDX, MAXPATHLEN); 314 xstrlcat(dst, MANDOC_IDX, MAXPATHLEN); 315 316 if ( ! filecpy(dst, src)) 317 return(-1); 318 if (verbose) 319 printf("%s\n", dst); 320 321 dst[(int)dsz] = src[(int)ssz] = '\0'; 322 323 return(indexhtml(src, ssz, dst, dsz)); 324} 325 326/* 327 * See if btree or recno databases in the destination are out of date 328 * with respect to a single manpath component. 329 * Return -1 on fatal error, 0 if the source is no longer valid (and 330 * shouldn't be listed), and 1 if the update went well. 331 */ 332static int 333treecpy(char *dst, char *src) 334{ 335 size_t dsz, ssz; 336 int rc; 337 338 dsz = strlen(dst); 339 ssz = strlen(src); 340 341 xstrlcat(src, "/", MAXPATHLEN); 342 xstrlcat(dst, "/", MAXPATHLEN); 343 344 xstrlcat(src, MANDOC_IDX, MAXPATHLEN); 345 xstrlcat(dst, MANDOC_IDX, MAXPATHLEN); 346 347 if (-1 == (rc = isnewer(dst, src))) 348 return(0); 349 350 dst[(int)dsz] = src[(int)ssz] = '\0'; 351 352 if (1 == rc) 353 return(update(dst, src)); 354 355 xstrlcat(src, "/", MAXPATHLEN); 356 xstrlcat(dst, "/", MAXPATHLEN); 357 358 xstrlcat(src, MANDOC_DB, MAXPATHLEN); 359 xstrlcat(dst, MANDOC_DB, MAXPATHLEN); 360 361 if (-1 == (rc = isnewer(dst, src))) 362 return(0); 363 else if (rc == 0) 364 return(1); 365 366 dst[(int)dsz] = src[(int)ssz] = '\0'; 367 368 return(update(dst, src)); 369} 370 371/* 372 * Update the destination's file-tree with respect to changes in the 373 * source manpath components. 374 * "Change" is defined by an updated index or btree database. 375 * Returns 1 on success, 0 on failure. 376 */ 377static int 378manup(const struct manpaths *dirs, char *base) 379{ 380 char dst[MAXPATHLEN], 381 src[MAXPATHLEN]; 382 const char *path; 383 int i, c; 384 size_t sz; 385 FILE *f; 386 387 /* Create the path and file for the catman.conf file. */ 388 389 sz = strlen(base); 390 xstrlcpy(dst, base, MAXPATHLEN); 391 xstrlcat(dst, "/etc", MAXPATHLEN); 392 if (-1 == mkpath(dst, 0755, 0755)) { 393 perror(dst); 394 return(0); 395 } 396 397 xstrlcat(dst, "/catman.conf", MAXPATHLEN); 398 if (NULL == (f = fopen(dst, "w"))) { 399 perror(dst); 400 return(0); 401 } else if (verbose) 402 printf("%s\n", dst); 403 404 for (i = 0; i < dirs->sz; i++) { 405 path = dirs->paths[i]; 406 dst[(int)sz] = '\0'; 407 xstrlcat(dst, path, MAXPATHLEN); 408 if (-1 == mkpath(dst, 0755, 0755)) { 409 perror(dst); 410 break; 411 } 412 413 xstrlcpy(src, path, MAXPATHLEN); 414 if (-1 == (c = treecpy(dst, src))) 415 break; 416 else if (0 == c) 417 continue; 418 419 /* 420 * We want to use a relative path here because manpath.h 421 * will realpath() when invoked with man.cgi, and we'll 422 * make sure to chdir() into the cache directory before. 423 * 424 * This allows the cache directory to be in an arbitrary 425 * place, working in both chroot() and non-chroot() 426 * "safe" modes. 427 */ 428 assert('/' == path[0]); 429 fprintf(f, "_whatdb %s/whatis.db\n", path + 1); 430 } 431 432 fclose(f); 433 return(i == dirs->sz); 434} 435 436/* 437 * Copyright (c) 1983, 1992, 1993 438 * The Regents of the University of California. All rights reserved. 439 * 440 * Redistribution and use in source and binary forms, with or without 441 * modification, are permitted provided that the following conditions 442 * are met: 443 * 1. Redistributions of source code must retain the above copyright 444 * notice, this list of conditions and the following disclaimer. 445 * 2. Redistributions in binary form must reproduce the above copyright 446 * notice, this list of conditions and the following disclaimer in the 447 * documentation and/or other materials provided with the distribution. 448 * 3. Neither the name of the University nor the names of its contributors 449 * may be used to endorse or promote products derived from this software 450 * without specific prior written permission. 451 * 452 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 453 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 454 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 455 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 456 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 457 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 458 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 459 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 460 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 461 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 462 * SUCH DAMAGE. 463 */ 464static int 465mkpath(char *path, mode_t mode, mode_t dir_mode) 466{ 467 struct stat sb; 468 char *slash; 469 int done, exists; 470 471 slash = path; 472 473 for (;;) { 474 /* LINTED */ 475 slash += strspn(slash, "/"); 476 /* LINTED */ 477 slash += strcspn(slash, "/"); 478 479 done = (*slash == '\0'); 480 *slash = '\0'; 481 482 /* skip existing path components */ 483 exists = !stat(path, &sb); 484 if (!done && exists && S_ISDIR(sb.st_mode)) { 485 *slash = '/'; 486 continue; 487 } 488 489 if (mkdir(path, done ? mode : dir_mode) == 0) { 490 if (mode > 0777 && chmod(path, mode) < 0) 491 return (-1); 492 } else { 493 if (!exists) { 494 /* Not there */ 495 return (-1); 496 } 497 if (!S_ISDIR(sb.st_mode)) { 498 /* Is there, but isn't a directory */ 499 errno = ENOTDIR; 500 return (-1); 501 } 502 } 503 504 if (done) 505 break; 506 507 *slash = '/'; 508 } 509 510 return (0); 511} 512