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