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