]> git.cameronkatri.com Git - mandoc.git/blob - catman.c
Don't make man.cgi default font-size dictate manual style.
[mandoc.git] / catman.c
1 /* $Id: catman.c,v 1.5 2011/12/12 02:00:49 schwarze 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
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 *, char *);
55 static int manup(const struct manpaths *, char *);
56 static int mkpath(char *, mode_t, mode_t);
57 static int treecpy(char *, char *, char *);
58 static int update(char *, char *, char *);
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;
71 char buf[MAXPATHLEN];
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;
82 xstrlcpy(buf, "/var/www/cache/man.cgi", MAXPATHLEN);
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'):
96 xstrlcpy(buf, optarg, MAXPATHLEN);
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));
115 manpath_parse(&dirs, NULL, base, aux);
116 ch = manup(&dirs, buf);
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
201 indexhtml(char *base, char *dst)
202 {
203 DB *idx;
204 DBT key, val;
205 size_t sz;
206 int c, rc;
207 unsigned int fl;
208 const char *f, *cp;
209 char *d;
210 char fname[MAXPATHLEN];
211 pid_t pid;
212
213 sz = strlen(base);
214 pid = -1;
215
216 xstrlcpy(fname, dst, MAXPATHLEN);
217 xstrlcat(fname, "/mandoc.index", MAXPATHLEN);
218
219 idx = dbopen(fname, O_RDONLY, 0, DB_RECNO, NULL);
220 if (NULL == idx) {
221 perror(fname);
222 return(-1);
223 }
224
225 fl = R_FIRST;
226 while (0 == (c = (*idx->seq)(idx, &key, &val, fl))) {
227 fl = R_NEXT;
228 cp = (const char *)val.data;
229 if (0 == val.size)
230 continue;
231 if (NULL == (f = memchr(cp, '\0', val.size)))
232 break;
233 if (++f - cp >= (int)val.size)
234 break;
235 if (NULL == memchr(f, '\0', val.size - (f - cp)))
236 break;
237
238 base[(int)sz] = '\0';
239
240 xstrlcat(base, "/", MAXPATHLEN);
241 xstrlcat(base, f, MAXPATHLEN);
242
243 if (-1 == (rc = isnewer(base, f))) {
244 fprintf(stderr, "%s: File missing\n", f);
245 break;
246 } else if (0 == rc)
247 continue;
248
249 d = strrchr(base, '/');
250 assert(NULL != d);
251 *d = '\0';
252
253 if (-1 == mkpath(base, 0755, 0755)) {
254 perror(base);
255 break;
256 }
257
258 *d = '/';
259
260 if ( ! filecpy(base, f))
261 break;
262 if (verbose)
263 printf("%s\n", base);
264 }
265
266 (*idx->close)(idx);
267
268 if (c < 0)
269 perror(fname);
270 else if (0 == c)
271 fprintf(stderr, "%s: Corrupt index\n", fname);
272
273 return(1 == c ? 1 : -1);
274 }
275
276 /*
277 * Copy both recno and btree databases into the destination.
278 * Call in to begin recreating HTML files.
279 * Return -1 on fatal error and 1 if the update went well.
280 */
281 static int
282 update(char *base, char *dst, char *src)
283 {
284 size_t dsz, ssz;
285
286 dsz = strlen(dst);
287 ssz = strlen(src);
288
289 xstrlcat(src, "/mandoc.db", MAXPATHLEN);
290 xstrlcat(dst, "/mandoc.db", MAXPATHLEN);
291
292 if ( ! filecpy(dst, src))
293 return(-1);
294 if (verbose)
295 printf("%s\n", dst);
296
297 dst[(int)dsz] = src[(int)ssz] = '\0';
298
299 xstrlcat(src, "/mandoc.index", MAXPATHLEN);
300 xstrlcat(dst, "/mandoc.index", MAXPATHLEN);
301
302 if ( ! filecpy(dst, src))
303 return(-1);
304 if (verbose)
305 printf("%s\n", dst);
306
307 dst[(int)dsz] = '\0';
308
309 return(indexhtml(base, dst));
310 }
311
312 /*
313 * See if btree or recno databases in the destination are out of date
314 * with respect to a single manpath component.
315 * Return -1 on fatal error, 0 if the source is no longer valid (and
316 * shouldn't be listed), and 1 if the update went well.
317 */
318 static int
319 treecpy(char *base, char *dst, char *src)
320 {
321 size_t dsz, ssz;
322 int rc;
323
324 dsz = strlen(dst);
325 ssz = strlen(src);
326
327 xstrlcat(src, "/mandoc.index", MAXPATHLEN);
328 xstrlcat(dst, "/mandoc.index", MAXPATHLEN);
329
330 if (-1 == (rc = isnewer(dst, src)))
331 return(0);
332
333 dst[(int)dsz] = src[(int)ssz] = '\0';
334
335 if (1 == rc)
336 return(update(base, dst, src));
337
338 xstrlcat(src, "/mandoc.db", MAXPATHLEN);
339 xstrlcat(dst, "/mandoc.db", MAXPATHLEN);
340
341 if (-1 == (rc = isnewer(dst, src)))
342 return(0);
343 else if (rc == 0)
344 return(1);
345
346 dst[(int)dsz] = src[(int)ssz] = '\0';
347
348 return(update(base, dst, src));
349 }
350
351 /*
352 * Update the destination's file-tree with respect to changes in the
353 * source manpath components.
354 * "Change" is defined by an updated index or btree database.
355 * Returns 1 on success, 0 on failure.
356 */
357 static int
358 manup(const struct manpaths *dirs, char *base)
359 {
360 char dst[MAXPATHLEN],
361 src[MAXPATHLEN];
362 const char *path;
363 int i, c;
364 size_t sz;
365 FILE *f;
366
367 /* Create the path and file for the catman.conf file. */
368
369 sz = strlen(base);
370 xstrlcpy(dst, base, MAXPATHLEN);
371 xstrlcat(dst, "/etc", MAXPATHLEN);
372 if (-1 == mkpath(dst, 0755, 0755)) {
373 perror(dst);
374 return(0);
375 }
376
377 xstrlcat(dst, "/catman.conf", MAXPATHLEN);
378 if (NULL == (f = fopen(dst, "w"))) {
379 perror(dst);
380 return(0);
381 } else if (verbose)
382 printf("%s\n", dst);
383
384 for (i = 0; i < dirs->sz; i++) {
385 path = dirs->paths[i];
386 dst[(int)sz] = base[(int)sz] = '\0';
387 xstrlcat(dst, path, MAXPATHLEN);
388 if (-1 == mkpath(dst, 0755, 0755)) {
389 perror(dst);
390 break;
391 }
392
393 xstrlcpy(src, path, MAXPATHLEN);
394 if (-1 == (c = treecpy(base, dst, src)))
395 break;
396 else if (0 == c)
397 continue;
398
399 /*
400 * We want to use a relative path here because manpath.h
401 * will realpath() when invoked with man.cgi, and we'll
402 * make sure to chdir() into the cache directory before.
403 *
404 * This allows the cache directory to be in an arbitrary
405 * place, working in both chroot() and non-chroot()
406 * "safe" modes.
407 */
408 assert('/' == path[0]);
409 fprintf(f, "_whatdb %s/whatis.db\n", path + 1);
410 }
411
412 fclose(f);
413 return(i == dirs->sz);
414 }
415
416 /*
417 * Copyright (c) 1983, 1992, 1993
418 * The Regents of the University of California. All rights reserved.
419 *
420 * Redistribution and use in source and binary forms, with or without
421 * modification, are permitted provided that the following conditions
422 * are met:
423 * 1. Redistributions of source code must retain the above copyright
424 * notice, this list of conditions and the following disclaimer.
425 * 2. Redistributions in binary form must reproduce the above copyright
426 * notice, this list of conditions and the following disclaimer in the
427 * documentation and/or other materials provided with the distribution.
428 * 3. Neither the name of the University nor the names of its contributors
429 * may be used to endorse or promote products derived from this software
430 * without specific prior written permission.
431 *
432 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
433 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
434 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
435 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
436 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
437 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
438 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
439 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
440 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
441 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
442 * SUCH DAMAGE.
443 */
444 static int
445 mkpath(char *path, mode_t mode, mode_t dir_mode)
446 {
447 struct stat sb;
448 char *slash;
449 int done, exists;
450
451 slash = path;
452
453 for (;;) {
454 /* LINTED */
455 slash += strspn(slash, "/");
456 /* LINTED */
457 slash += strcspn(slash, "/");
458
459 done = (*slash == '\0');
460 *slash = '\0';
461
462 /* skip existing path components */
463 exists = !stat(path, &sb);
464 if (!done && exists && S_ISDIR(sb.st_mode)) {
465 *slash = '/';
466 continue;
467 }
468
469 if (mkdir(path, done ? mode : dir_mode) == 0) {
470 if (mode > 0777 && chmod(path, mode) < 0)
471 return (-1);
472 } else {
473 if (!exists) {
474 /* Not there */
475 return (-1);
476 }
477 if (!S_ISDIR(sb.st_mode)) {
478 /* Is there, but isn't a directory */
479 errno = ENOTDIR;
480 return (-1);
481 }
482 }
483
484 if (done)
485 break;
486
487 *slash = '/';
488 }
489
490 return (0);
491 }