1/*	$Vendor-Id: catman.c,v 1.10 2012/01/03 15:17:20 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
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	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 */
289static int
290update(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 */
332static int
333treecpy(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 */
377static int
378manup(const struct manpaths *dirs, char *base)
379{
380	char		 dst[MAXPATHLEN],
381			 src[MAXPATHLEN];
382	const char	*path;
383	int		 i, c;
384	size_t		 sz;
385	FILE		*f;
386
387	/* Create the path and file for the catman.conf file. */
388
389	sz = strlen(base);
390	xstrlcpy(dst, base, MAXPATHLEN);
391	xstrlcat(dst, "/etc", MAXPATHLEN);
392	if (-1 == mkpath(dst, 0755, 0755)) {
393		perror(dst);
394		return(0);
395	}
396
397	xstrlcat(dst, "/catman.conf", MAXPATHLEN);
398	if (NULL == (f = fopen(dst, "w"))) {
399		perror(dst);
400		return(0);
401	} else if (verbose)
402		printf("%s\n", dst);
403
404	for (i = 0; i < dirs->sz; i++) {
405		path = dirs->paths[i];
406		dst[(int)sz] = '\0';
407		xstrlcat(dst, path, MAXPATHLEN);
408		if (-1 == mkpath(dst, 0755, 0755)) {
409			perror(dst);
410			break;
411		}
412
413		xstrlcpy(src, path, MAXPATHLEN);
414		if (-1 == (c = treecpy(dst, src)))
415			break;
416		else if (0 == c)
417			continue;
418
419		/*
420		 * We want to use a relative path here because manpath.h
421		 * will realpath() when invoked with man.cgi, and we'll
422		 * make sure to chdir() into the cache directory before.
423		 *
424		 * This allows the cache directory to be in an arbitrary
425		 * place, working in both chroot() and non-chroot()
426		 * "safe" modes.
427		 */
428		assert('/' == path[0]);
429		fprintf(f, "_whatdb %s/whatis.db\n", path + 1);
430	}
431
432	fclose(f);
433	return(i == dirs->sz);
434}
435
436/*
437 * Copyright (c) 1983, 1992, 1993
438 *	The Regents of the University of California.  All rights reserved.
439 *
440 * Redistribution and use in source and binary forms, with or without
441 * modification, are permitted provided that the following conditions
442 * are met:
443 * 1. Redistributions of source code must retain the above copyright
444 *    notice, this list of conditions and the following disclaimer.
445 * 2. Redistributions in binary form must reproduce the above copyright
446 *    notice, this list of conditions and the following disclaimer in the
447 *    documentation and/or other materials provided with the distribution.
448 * 3. Neither the name of the University nor the names of its contributors
449 *    may be used to endorse or promote products derived from this software
450 *    without specific prior written permission.
451 *
452 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
453 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
454 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
455 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
456 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
457 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
458 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
459 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
460 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
461 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
462 * SUCH DAMAGE.
463 */
464static int
465mkpath(char *path, mode_t mode, mode_t dir_mode)
466{
467	struct stat sb;
468	char *slash;
469	int done, exists;
470
471	slash = path;
472
473	for (;;) {
474		/* LINTED */
475		slash += strspn(slash, "/");
476		/* LINTED */
477		slash += strcspn(slash, "/");
478
479		done = (*slash == '\0');
480		*slash = '\0';
481
482		/* skip existing path components */
483		exists = !stat(path, &sb);
484		if (!done && exists && S_ISDIR(sb.st_mode)) {
485			*slash = '/';
486			continue;
487		}
488
489		if (mkdir(path, done ? mode : dir_mode) == 0) {
490			if (mode > 0777 && chmod(path, mode) < 0)
491				return (-1);
492		} else {
493			if (!exists) {
494				/* Not there */
495				return (-1);
496			}
497			if (!S_ISDIR(sb.st_mode)) {
498				/* Is there, but isn't a directory */
499				errno = ENOTDIR;
500				return (-1);
501			}
502		}
503
504		if (done)
505			break;
506
507		*slash = '/';
508	}
509
510	return (0);
511}
512