1/*-
2 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 */
28
29#include <sys/types.h>
30#include <sys/stat.h>
31
32#include <errno.h>
33#include <fcntl.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38
39#include "config.h"
40#include "globtree.h"
41#include "keyword.h"
42#include "misc.h"
43#include "parse.h"
44#include "stream.h"
45#include "token.h"
46
47static int		 config_parse_refusefiles(struct coll *);
48static int		 config_parse_refusefile(struct coll *, char *);
49
50extern FILE *yyin;
51
52/* These are globals because I can't think of a better way with yacc. */
53static STAILQ_HEAD(, coll) colls;
54static struct coll *cur_coll;
55static struct coll *defaults;
56static struct coll *ovcoll;
57static int ovmask;
58static const char *cfgfile;
59
60/*
61 * Extract all the configuration information from the config
62 * file and some command line parameters.
63 */
64struct config *
65config_init(const char *file, struct coll *override, int overridemask)
66{
67	struct config *config;
68	struct coll *coll;
69	size_t slen;
70	char *prefix;
71	int error;
72	mode_t mask;
73
74	config = xmalloc(sizeof(struct config));
75	memset(config, 0, sizeof(struct config));
76	STAILQ_INIT(&colls);
77
78	defaults = coll_new(NULL);
79	/* Set the default umask. */
80	mask = umask(0);
81	umask(mask);
82	defaults->co_umask = mask;
83	ovcoll = override;
84	ovmask = overridemask;
85
86	/* Extract a list of collections from the configuration file. */
87	cur_coll = coll_new(defaults);
88	yyin = fopen(file, "r");
89	if (yyin == NULL) {
90		lprintf(-1, "Cannot open \"%s\": %s\n", file, strerror(errno));
91		goto bad;
92	}
93	cfgfile = file;
94	error = yyparse();
95	fclose(yyin);
96	if (error)
97		goto bad;
98
99	memcpy(&config->colls, &colls, sizeof(colls));
100	if (STAILQ_EMPTY(&config->colls)) {
101		lprintf(-1, "Empty supfile\n");
102		goto bad;
103	}
104
105	/* Fixup the list of collections. */
106	STAILQ_FOREACH(coll, &config->colls, co_next) {
107 		if (coll->co_base == NULL)
108			coll->co_base = xstrdup("/usr/local/etc/cvsup");
109		if (coll->co_colldir == NULL)
110			coll->co_colldir = "sup";
111		if (coll->co_prefix == NULL) {
112			coll->co_prefix = xstrdup(coll->co_base);
113		/*
114		 * If prefix is not an absolute pathname, it is
115		 * interpreted relative to base.
116		 */
117		} else if (coll->co_prefix[0] != '/') {
118			slen = strlen(coll->co_base);
119			if (slen > 0 && coll->co_base[slen - 1] != '/')
120				xasprintf(&prefix, "%s/%s", coll->co_base,
121				    coll->co_prefix);
122			else
123				xasprintf(&prefix, "%s%s", coll->co_base,
124				    coll->co_prefix);
125			free(coll->co_prefix);
126			coll->co_prefix = prefix;
127		}
128		coll->co_prefixlen = strlen(coll->co_prefix);
129		/* Determine whether to checksum RCS files or not. */
130		if (coll->co_options & CO_EXACTRCS)
131			coll->co_options |= CO_CHECKRCS;
132		else
133			coll->co_options &= ~CO_CHECKRCS;
134		/* In recent versions, we always try to set the file modes. */
135		coll->co_options |= CO_SETMODE;
136		coll->co_options |= CO_NORSYNC;
137		error = config_parse_refusefiles(coll);
138		if (error)
139			goto bad;
140	}
141
142	coll_free(cur_coll);
143	coll_free(defaults);
144	config->host = STAILQ_FIRST(&config->colls)->co_host;
145	return (config);
146bad:
147	coll_free(cur_coll);
148	coll_free(defaults);
149	config_free(config);
150	return (NULL);
151}
152
153int
154config_checkcolls(struct config *config)
155{
156	char linkname[4];
157	struct stat sb;
158	struct coll *coll;
159	int error, numvalid, ret;
160
161	numvalid = 0;
162	STAILQ_FOREACH(coll, &config->colls, co_next) {
163		error = stat(coll->co_prefix, &sb);
164		if (error || !S_ISDIR(sb.st_mode)) {
165			/* Skip this collection, and warn about it unless its
166			   prefix is a symbolic link pointing to "SKIP". */
167			coll->co_options |= CO_SKIP;
168			ret = readlink(coll->co_prefix, linkname,
169			    sizeof(linkname));
170			if (ret != 4 || memcmp(linkname, "SKIP", 4) != 0) {
171				lprintf(-1,"Nonexistent prefix \"%s\" for "
172				    "%s/%s\n", coll->co_prefix, coll->co_name,
173				    coll->co_release);
174			}
175			continue;
176		}
177		numvalid++;
178	}
179	return (numvalid);
180}
181
182static int
183config_parse_refusefiles(struct coll *coll)
184{
185	char *collstem, *suffix, *supdir, *path;
186	int error;
187
188	if (coll->co_colldir[0] == '/')
189		supdir = xstrdup(coll->co_colldir);
190	else
191		xasprintf(&supdir, "%s/%s", coll->co_base, coll->co_colldir);
192
193	/* First, the global refuse file that applies to all collections. */
194	xasprintf(&path, "%s/refuse", supdir);
195	error = config_parse_refusefile(coll, path);
196	free(path);
197	if (error) {
198		free(supdir);
199		return (error);
200	}
201
202	/* Next the per-collection refuse files that applies to all release/tag
203	   combinations. */
204	xasprintf(&collstem, "%s/%s/refuse", supdir, coll->co_name);
205	free(supdir);
206	error = config_parse_refusefile(coll, collstem);
207	if (error) {
208		free(collstem);
209		return (error);
210	}
211
212	/* Finally, the per-release and per-tag refuse file. */
213	suffix = coll_statussuffix(coll);
214	if (suffix != NULL) {
215		xasprintf(&path, "%s%s", collstem, suffix);
216		free(suffix);
217		error = config_parse_refusefile(coll, path);
218		free(path);
219	}
220	free(collstem);
221	return (error);
222}
223
224/*
225 * Parses a "refuse" file, and records the relevant information in
226 * coll->co_refusals.  If the file does not exist, it is silently
227 * ignored.
228 */
229static int
230config_parse_refusefile(struct coll *coll, char *path)
231{
232	struct stream *rd;
233	char *cp, *line, *pat;
234
235	rd = stream_open_file(path, O_RDONLY);
236	if (rd == NULL)
237		return (0);
238	while ((line = stream_getln(rd, NULL)) != NULL) {
239		pat = line;
240		for (;;) {
241			/* Trim leading whitespace. */
242			pat += strspn(pat, " \t");
243			if (pat[0] == '\0')
244				break;
245			cp = strpbrk(pat, " \t");
246			if (cp != NULL)
247				*cp = '\0';
248			pattlist_add(coll->co_refusals, pat);
249			if (cp == NULL)
250				break;
251			pat = cp + 1;
252		}
253	}
254	if (!stream_eof(rd)) {
255		stream_close(rd);
256		lprintf(-1, "Read failure from \"%s\": %s\n", path,
257		    strerror(errno));
258		return (-1);
259	}
260	stream_close(rd);
261	return (0);
262}
263
264void
265config_free(struct config *config)
266{
267	struct coll *coll;
268
269	while (!STAILQ_EMPTY(&config->colls)) {
270		coll = STAILQ_FIRST(&config->colls);
271		STAILQ_REMOVE_HEAD(&config->colls, co_next);
272		coll_free(coll);
273	}
274	if (config->server != NULL)
275		stream_close(config->server);
276	if (config->laddr != NULL)
277		free(config->laddr);
278	free(config);
279}
280
281/* Create a new collection, inheriting options from the default collection. */
282struct coll *
283coll_new(struct coll *def)
284{
285	struct coll *new;
286
287	new = xmalloc(sizeof(struct coll));
288	memset(new, 0, sizeof(struct coll));
289	if (def != NULL) {
290		new->co_options = def->co_options;
291		new->co_umask = def->co_umask;
292		if (def->co_host != NULL)
293			new->co_host = xstrdup(def->co_host);
294		if (def->co_base != NULL)
295			new->co_base = xstrdup(def->co_base);
296		if (def->co_date != NULL)
297			new->co_date = xstrdup(def->co_date);
298		if (def->co_prefix != NULL)
299			new->co_prefix = xstrdup(def->co_prefix);
300		if (def->co_release != NULL)
301			new->co_release = xstrdup(def->co_release);
302		if (def->co_tag != NULL)
303			new->co_tag = xstrdup(def->co_tag);
304		if (def->co_listsuffix != NULL)
305			new->co_listsuffix = xstrdup(def->co_listsuffix);
306	} else {
307		new->co_tag = xstrdup(".");
308		new->co_date = xstrdup(".");
309	}
310	new->co_keyword = keyword_new();
311	new->co_accepts = pattlist_new();
312	new->co_refusals = pattlist_new();
313	new->co_attrignore = FA_DEV | FA_INODE;
314	return (new);
315}
316
317void
318coll_override(struct coll *coll, struct coll *from, int mask)
319{
320	size_t i;
321	int newoptions, oldoptions;
322
323	newoptions = from->co_options & mask;
324	oldoptions = coll->co_options & (CO_MASK & ~mask);
325
326	if (from->co_release != NULL) {
327		if (coll->co_release != NULL)
328			free(coll->co_release);
329		coll->co_release = xstrdup(from->co_release);
330	}
331	if (from->co_host != NULL) {
332		if (coll->co_host != NULL)
333			free(coll->co_host);
334		coll->co_host = xstrdup(from->co_host);
335	}
336	if (from->co_base != NULL) {
337		if (coll->co_base != NULL)
338			free(coll->co_base);
339		coll->co_base = xstrdup(from->co_base);
340	}
341	if (from->co_colldir != NULL)
342		coll->co_colldir = from->co_colldir;
343	if (from->co_prefix != NULL) {
344		if (coll->co_prefix != NULL)
345			free(coll->co_prefix);
346		coll->co_prefix = xstrdup(from->co_prefix);
347	}
348	if (newoptions & CO_CHECKOUTMODE) {
349		if (from->co_tag != NULL) {
350			if (coll->co_tag != NULL)
351				free(coll->co_tag);
352			coll->co_tag = xstrdup(from->co_tag);
353		}
354		if (from->co_date != NULL) {
355			if (coll->co_date != NULL)
356				free(coll->co_date);
357			coll->co_date = xstrdup(from->co_date);
358		}
359	}
360	if (from->co_listsuffix != NULL) {
361		if (coll->co_listsuffix != NULL)
362			free(coll->co_listsuffix);
363		coll->co_listsuffix = xstrdup(from->co_listsuffix);
364	}
365	for (i = 0; i < pattlist_size(from->co_accepts); i++) {
366		pattlist_add(coll->co_accepts,
367		    pattlist_get(from->co_accepts, i));
368	}
369	for (i = 0; i < pattlist_size(from->co_refusals); i++) {
370		pattlist_add(coll->co_refusals,
371		    pattlist_get(from->co_refusals, i));
372	}
373	coll->co_options = oldoptions | newoptions;
374}
375
376char *
377coll_statussuffix(struct coll *coll)
378{
379	const char *tag;
380	char *suffix;
381
382	if (coll->co_listsuffix != NULL) {
383		xasprintf(&suffix, ".%s", coll->co_listsuffix);
384	} else if (coll->co_options & CO_USERELSUFFIX) {
385		if (coll->co_tag == NULL)
386			tag = ".";
387		else
388			tag = coll->co_tag;
389		if (coll->co_release != NULL) {
390			if (coll->co_options & CO_CHECKOUTMODE) {
391				xasprintf(&suffix, ".%s:%s",
392				    coll->co_release, tag);
393			} else {
394				xasprintf(&suffix, ".%s", coll->co_release);
395			}
396		} else if (coll->co_options & CO_CHECKOUTMODE) {
397			xasprintf(&suffix, ":%s", tag);
398		}
399	} else
400		suffix = NULL;
401	return (suffix);
402}
403
404char *
405coll_statuspath(struct coll *coll)
406{
407	char *path, *suffix;
408
409	suffix = coll_statussuffix(coll);
410	if (suffix != NULL) {
411		if (coll->co_colldir[0] == '/')
412			xasprintf(&path, "%s/%s/checkouts%s", coll->co_colldir,
413			    coll->co_name, suffix);
414		else
415			xasprintf(&path, "%s/%s/%s/checkouts%s", coll->co_base,
416			    coll->co_colldir, coll->co_name, suffix);
417	} else {
418		if (coll->co_colldir[0] == '/')
419			xasprintf(&path, "%s/%s/checkouts", coll->co_colldir,
420			    coll->co_name);
421		else
422			xasprintf(&path, "%s/%s/%s/checkouts", coll->co_base,
423			    coll->co_colldir, coll->co_name);
424	}
425	free(suffix);
426	return (path);
427}
428
429void
430coll_add(char *name)
431{
432	struct coll *coll;
433
434	cur_coll->co_name = name;
435	coll_override(cur_coll, ovcoll, ovmask);
436	if (cur_coll->co_release == NULL) {
437		lprintf(-1, "Release not specified for collection "
438		    "\"%s\"\n", cur_coll->co_name);
439		exit(1);
440	}
441	if (cur_coll->co_host == NULL) {
442		lprintf(-1, "Host not specified for collection "
443		    "\"%s\"\n", cur_coll->co_name);
444		exit(1);
445	}
446	if (!STAILQ_EMPTY(&colls)) {
447		coll = STAILQ_LAST(&colls, coll, co_next);
448		if (strcmp(coll->co_host, cur_coll->co_host) != 0) {
449			lprintf(-1, "All \"host\" fields in the supfile "
450			    "must be the same\n");
451			exit(1);
452		}
453	}
454	STAILQ_INSERT_TAIL(&colls, cur_coll, co_next);
455	cur_coll = coll_new(defaults);
456}
457
458void
459coll_free(struct coll *coll)
460{
461
462	if (coll == NULL)
463		return;
464	if (coll->co_host != NULL)
465		free(coll->co_host);
466	if (coll->co_base != NULL)
467		free(coll->co_base);
468	if (coll->co_date != NULL)
469		free(coll->co_date);
470	if (coll->co_prefix != NULL)
471		free(coll->co_prefix);
472	if (coll->co_release != NULL)
473		free(coll->co_release);
474	if (coll->co_tag != NULL)
475		free(coll->co_tag);
476	if (coll->co_cvsroot != NULL)
477		free(coll->co_cvsroot);
478	if (coll->co_name != NULL)
479		free(coll->co_name);
480	if (coll->co_listsuffix != NULL)
481		free(coll->co_listsuffix);
482	keyword_free(coll->co_keyword);
483	if (coll->co_dirfilter != NULL)
484		globtree_free(coll->co_dirfilter);
485	if (coll->co_dirfilter != NULL)
486		globtree_free(coll->co_filefilter);
487	if (coll->co_norsync != NULL)
488		globtree_free(coll->co_norsync);
489	if (coll->co_accepts != NULL)
490		pattlist_free(coll->co_accepts);
491	if (coll->co_refusals != NULL)
492		pattlist_free(coll->co_refusals);
493	free(coll);
494}
495
496void
497coll_setopt(int opt, char *value)
498{
499	struct coll *coll;
500	int error, mask;
501
502	coll = cur_coll;
503	switch (opt) {
504	case PT_HOST:
505		if (coll->co_host != NULL)
506			free(coll->co_host);
507		coll->co_host = value;
508		break;
509	case PT_BASE:
510		if (coll->co_base != NULL)
511			free(coll->co_base);
512		coll->co_base = value;
513		break;
514	case PT_DATE:
515		if (coll->co_date != NULL)
516			free(coll->co_date);
517		coll->co_date = value;
518		coll->co_options |= CO_CHECKOUTMODE;
519		break;
520	case PT_PREFIX:
521		if (coll->co_prefix != NULL)
522			free(coll->co_prefix);
523		coll->co_prefix = value;
524		break;
525	case PT_RELEASE:
526		if (coll->co_release != NULL)
527			free(coll->co_release);
528		coll->co_release = value;
529		break;
530	case PT_TAG:
531		if (coll->co_tag != NULL)
532			free(coll->co_tag);
533		coll->co_tag = value;
534		coll->co_options |= CO_CHECKOUTMODE;
535		break;
536	case PT_LIST:
537		if (strchr(value, '/') != NULL) {
538			lprintf(-1, "Parse error in \"%s\": \"list\" suffix "
539			    "must not contain slashes\n", cfgfile);
540			exit(1);
541		}
542		if (coll->co_listsuffix != NULL)
543			free(coll->co_listsuffix);
544		coll->co_listsuffix = value;
545		break;
546	case PT_UMASK:
547		error = asciitoint(value, &mask, 8);
548		free(value);
549		if (error) {
550			lprintf(-1, "Parse error in \"%s\": Invalid "
551			    "umask value\n", cfgfile);
552			exit(1);
553		}
554		coll->co_umask = mask;
555		break;
556	case PT_USE_REL_SUFFIX:
557		coll->co_options |= CO_USERELSUFFIX;
558		break;
559	case PT_DELETE:
560		coll->co_options |= CO_DELETE | CO_EXACTRCS;
561		break;
562	case PT_COMPRESS:
563		coll->co_options |= CO_COMPRESS;
564		break;
565	case PT_NORSYNC:
566		coll->co_options |= CO_NORSYNC;
567		break;
568	}
569}
570
571/* Set "coll" as being the default collection. */
572void
573coll_setdef(void)
574{
575
576	coll_free(defaults);
577	defaults = cur_coll;
578	cur_coll = coll_new(defaults);
579}
580