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