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