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