config.c revision 156702
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: head/contrib/csup/config.c 156702 2006-03-14 03:51:13Z mux $
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		/* XXX We don't support the rsync updating algorithm yet. */
137		coll->co_options |= CO_NORSYNC;
138		error = config_parse_refusefiles(coll);
139		if (error)
140			goto bad;
141	}
142
143	coll_free(cur_coll);
144	coll_free(defaults);
145	config->host = STAILQ_FIRST(&config->colls)->co_host;
146	return (config);
147bad:
148	coll_free(cur_coll);
149	coll_free(defaults);
150	config_free(config);
151	return (NULL);
152}
153
154int
155config_checkcolls(struct config *config)
156{
157	char linkname[4];
158	struct stat sb;
159	struct coll *coll;
160	int error, numvalid, ret;
161
162	numvalid = 0;
163	STAILQ_FOREACH(coll, &config->colls, co_next) {
164		error = stat(coll->co_prefix, &sb);
165		if (error || !S_ISDIR(sb.st_mode)) {
166			/* Skip this collection, and warn about it unless its
167			   prefix is a symbolic link pointing to "SKIP". */
168			coll->co_options |= CO_SKIP;
169			ret = readlink(coll->co_prefix, linkname,
170			    sizeof(linkname));
171			if (ret != 4 || memcmp(linkname, "SKIP", 4) != 0) {
172				lprintf(-1,"Nonexistent prefix \"%s\" for "
173				    "%s/%s\n", coll->co_prefix, coll->co_name,
174				    coll->co_release);
175			}
176			continue;
177		}
178		numvalid++;
179	}
180	return (numvalid);
181}
182
183static int
184config_parse_refusefiles(struct coll *coll)
185{
186	char *collstem, *suffix, *supdir, *path;
187	int error;
188
189	if (coll->co_colldir[0] == '/')
190		supdir = xstrdup(coll->co_colldir);
191	else
192		xasprintf(&supdir, "%s/%s", coll->co_base, coll->co_colldir);
193
194	/* First, the global refuse file that applies to all collections. */
195	xasprintf(&path, "%s/refuse", supdir);
196	error = config_parse_refusefile(coll, path);
197	free(path);
198	if (error) {
199		free(supdir);
200		return (error);
201	}
202
203	/* Next the per-collection refuse files that applies to all release/tag
204	   combinations. */
205	xasprintf(&collstem, "%s/%s/refuse", supdir, coll->co_name);
206	free(supdir);
207	error = config_parse_refusefile(coll, collstem);
208	if (error) {
209		free(collstem);
210		return (error);
211	}
212
213	/* Finally, the per-release and per-tag refuse file. */
214	suffix = coll_statussuffix(coll);
215	if (suffix != NULL) {
216		xasprintf(&path, "%s%s", collstem, suffix);
217		free(suffix);
218		error = config_parse_refusefile(coll, path);
219		free(path);
220	}
221	free(collstem);
222	return (error);
223}
224
225/*
226 * Parses a "refuse" file, and records the relevant information in
227 * coll->co_refusals.  If the file does not exist, it is silently
228 * ignored.
229 */
230static int
231config_parse_refusefile(struct coll *coll, char *path)
232{
233	struct stream *rd;
234	char *cp, *line, *pat;
235
236	rd = stream_open_file(path, O_RDONLY);
237	if (rd == NULL)
238		return (0);
239	while ((line = stream_getln(rd, NULL)) != NULL) {
240		pat = line;
241		for (;;) {
242			/* Trim leading whitespace. */
243			pat += strspn(pat, " \t");
244			if (pat[0] == '\0')
245				break;
246			cp = strpbrk(pat, " \t");
247			if (cp != NULL)
248				*cp = '\0';
249			pattlist_add(coll->co_refusals, pat);
250			if (cp == NULL)
251				break;
252			pat = cp + 1;
253		}
254	}
255	if (!stream_eof(rd)) {
256		stream_close(rd);
257		lprintf(-1, "Read failure from \"%s\": %s\n", path,
258		    strerror(errno));
259		return (-1);
260	}
261	stream_close(rd);
262	return (0);
263}
264
265void
266config_free(struct config *config)
267{
268	struct coll *coll;
269
270	while (!STAILQ_EMPTY(&config->colls)) {
271		coll = STAILQ_FIRST(&config->colls);
272		STAILQ_REMOVE_HEAD(&config->colls, co_next);
273		coll_free(coll);
274	}
275	if (config->server != NULL)
276		stream_close(config->server);
277	if (config->laddr != NULL)
278		free(config->laddr);
279	free(config);
280}
281
282/* Create a new collection, inheriting options from the default collection. */
283struct coll *
284coll_new(struct coll *def)
285{
286	struct coll *new;
287
288	new = xmalloc(sizeof(struct coll));
289	memset(new, 0, sizeof(struct coll));
290	if (def != NULL) {
291		new->co_options = def->co_options;
292		new->co_umask = def->co_umask;
293		if (def->co_host != NULL)
294			new->co_host = xstrdup(def->co_host);
295		if (def->co_base != NULL)
296			new->co_base = xstrdup(def->co_base);
297		if (def->co_date != NULL)
298			new->co_date = xstrdup(def->co_date);
299		if (def->co_prefix != NULL)
300			new->co_prefix = xstrdup(def->co_prefix);
301		if (def->co_release != NULL)
302			new->co_release = xstrdup(def->co_release);
303		if (def->co_tag != NULL)
304			new->co_tag = xstrdup(def->co_tag);
305		if (def->co_listsuffix != NULL)
306			new->co_listsuffix = xstrdup(def->co_listsuffix);
307	} else {
308		new->co_tag = xstrdup(".");
309		new->co_date = xstrdup(".");
310	}
311	new->co_keyword = keyword_new();
312	new->co_accepts = pattlist_new();
313	new->co_refusals = pattlist_new();
314	new->co_attrignore = FA_DEV | FA_INODE;
315	return (new);
316}
317
318void
319coll_override(struct coll *coll, struct coll *from, int mask)
320{
321	size_t i;
322	int newoptions, oldoptions;
323
324	newoptions = from->co_options & mask;
325	oldoptions = coll->co_options & (CO_MASK & ~mask);
326
327	if (from->co_release != NULL) {
328		if (coll->co_release != NULL)
329			free(coll->co_release);
330		coll->co_release = xstrdup(from->co_release);
331	}
332	if (from->co_host != NULL) {
333		if (coll->co_host != NULL)
334			free(coll->co_host);
335		coll->co_host = xstrdup(from->co_host);
336	}
337	if (from->co_base != NULL) {
338		if (coll->co_base != NULL)
339			free(coll->co_base);
340		coll->co_base = xstrdup(from->co_base);
341	}
342	if (from->co_colldir != NULL)
343		coll->co_colldir = from->co_colldir;
344	if (from->co_prefix != NULL) {
345		if (coll->co_prefix != NULL)
346			free(coll->co_prefix);
347		coll->co_prefix = xstrdup(from->co_prefix);
348	}
349	if (newoptions & CO_CHECKOUTMODE) {
350		if (from->co_tag != NULL) {
351			if (coll->co_tag != NULL)
352				free(coll->co_tag);
353			coll->co_tag = xstrdup(from->co_tag);
354		}
355		if (from->co_date != NULL) {
356			if (coll->co_date != NULL)
357				free(coll->co_date);
358			coll->co_date = xstrdup(from->co_date);
359		}
360	}
361	if (from->co_listsuffix != NULL) {
362		if (coll->co_listsuffix != NULL)
363			free(coll->co_listsuffix);
364		coll->co_listsuffix = xstrdup(from->co_listsuffix);
365	}
366	for (i = 0; i < pattlist_size(from->co_accepts); i++) {
367		pattlist_add(coll->co_accepts,
368		    pattlist_get(from->co_accepts, i));
369	}
370	for (i = 0; i < pattlist_size(from->co_refusals); i++) {
371		pattlist_add(coll->co_refusals,
372		    pattlist_get(from->co_refusals, i));
373	}
374	coll->co_options = oldoptions | newoptions;
375}
376
377char *
378coll_statussuffix(struct coll *coll)
379{
380	const char *tag;
381	char *suffix;
382
383	if (coll->co_listsuffix != NULL) {
384		xasprintf(&suffix, ".%s", coll->co_listsuffix);
385	} else if (coll->co_options & CO_USERELSUFFIX) {
386		if (coll->co_tag == NULL)
387			tag = ".";
388		else
389			tag = coll->co_tag;
390		if (coll->co_release != NULL) {
391			if (coll->co_options & CO_CHECKOUTMODE) {
392				xasprintf(&suffix, ".%s:%s",
393				    coll->co_release, tag);
394			} else {
395				xasprintf(&suffix, ".%s", coll->co_release);
396			}
397		} else if (coll->co_options & CO_CHECKOUTMODE) {
398			xasprintf(&suffix, ":%s", tag);
399		}
400	} else
401		suffix = NULL;
402	return (suffix);
403}
404
405char *
406coll_statuspath(struct coll *coll)
407{
408	char *path, *suffix;
409
410	suffix = coll_statussuffix(coll);
411	if (suffix != NULL) {
412		if (coll->co_colldir[0] == '/')
413			xasprintf(&path, "%s/%s/checkouts%s", coll->co_colldir,
414			    coll->co_name, suffix);
415		else
416			xasprintf(&path, "%s/%s/%s/checkouts%s", coll->co_base,
417			    coll->co_colldir, coll->co_name, suffix);
418	} else {
419		if (coll->co_colldir[0] == '/')
420			xasprintf(&path, "%s/%s/checkouts", coll->co_colldir,
421			    coll->co_name);
422		else
423			xasprintf(&path, "%s/%s/%s/checkouts", coll->co_base,
424			    coll->co_colldir, coll->co_name);
425	}
426	free(suffix);
427	return (path);
428}
429
430void
431coll_add(char *name)
432{
433	struct coll *coll;
434
435	cur_coll->co_name = name;
436	coll_override(cur_coll, ovcoll, ovmask);
437	if (cur_coll->co_release == NULL) {
438		lprintf(-1, "Release not specified for collection "
439		    "\"%s\"\n", cur_coll->co_name);
440		exit(1);
441	}
442	if (cur_coll->co_host == NULL) {
443		lprintf(-1, "Host not specified for collection "
444		    "\"%s\"\n", cur_coll->co_name);
445		exit(1);
446	}
447	if (!(cur_coll->co_options & CO_CHECKOUTMODE)) {
448		lprintf(-1, "Client only supports checkout mode\n");
449		exit(1);
450	}
451	if (!STAILQ_EMPTY(&colls)) {
452		coll = STAILQ_LAST(&colls, coll, co_next);
453		if (strcmp(coll->co_host, cur_coll->co_host) != 0) {
454			lprintf(-1, "All \"host\" fields in the supfile "
455			    "must be the same\n");
456			exit(1);
457		}
458	}
459	STAILQ_INSERT_TAIL(&colls, cur_coll, co_next);
460	cur_coll = coll_new(defaults);
461}
462
463void
464coll_free(struct coll *coll)
465{
466
467	if (coll == NULL)
468		return;
469	if (coll->co_host != NULL)
470		free(coll->co_host);
471	if (coll->co_base != NULL)
472		free(coll->co_base);
473	if (coll->co_date != NULL)
474		free(coll->co_date);
475	if (coll->co_prefix != NULL)
476		free(coll->co_prefix);
477	if (coll->co_release != NULL)
478		free(coll->co_release);
479	if (coll->co_tag != NULL)
480		free(coll->co_tag);
481	if (coll->co_cvsroot != NULL)
482		free(coll->co_cvsroot);
483	if (coll->co_name != NULL)
484		free(coll->co_name);
485	if (coll->co_listsuffix != NULL)
486		free(coll->co_listsuffix);
487	keyword_free(coll->co_keyword);
488	if (coll->co_dirfilter != NULL)
489		globtree_free(coll->co_dirfilter);
490	if (coll->co_dirfilter != NULL)
491		globtree_free(coll->co_filefilter);
492	if (coll->co_norsync != NULL)
493		globtree_free(coll->co_norsync);
494	if (coll->co_accepts != NULL)
495		pattlist_free(coll->co_accepts);
496	if (coll->co_refusals != NULL)
497		pattlist_free(coll->co_refusals);
498	free(coll);
499}
500
501void
502coll_setopt(int opt, char *value)
503{
504	struct coll *coll;
505	int error, mask;
506
507	coll = cur_coll;
508	switch (opt) {
509	case PT_HOST:
510		if (coll->co_host != NULL)
511			free(coll->co_host);
512		coll->co_host = value;
513		break;
514	case PT_BASE:
515		if (coll->co_base != NULL)
516			free(coll->co_base);
517		coll->co_base = value;
518		break;
519	case PT_DATE:
520		if (coll->co_date != NULL)
521			free(coll->co_date);
522		coll->co_date = value;
523		coll->co_options |= CO_CHECKOUTMODE;
524		break;
525	case PT_PREFIX:
526		if (coll->co_prefix != NULL)
527			free(coll->co_prefix);
528		coll->co_prefix = value;
529		break;
530	case PT_RELEASE:
531		if (coll->co_release != NULL)
532			free(coll->co_release);
533		coll->co_release = value;
534		break;
535	case PT_TAG:
536		if (coll->co_tag != NULL)
537			free(coll->co_tag);
538		coll->co_tag = value;
539		coll->co_options |= CO_CHECKOUTMODE;
540		break;
541	case PT_LIST:
542		if (strchr(value, '/') != NULL) {
543			lprintf(-1, "Parse error in \"%s\": \"list\" suffix "
544			    "must not contain slashes\n", cfgfile);
545			exit(1);
546		}
547		if (coll->co_listsuffix != NULL)
548			free(coll->co_listsuffix);
549		coll->co_listsuffix = value;
550		break;
551	case PT_UMASK:
552		error = asciitoint(value, &mask, 8);
553		free(value);
554		if (error) {
555			lprintf(-1, "Parse error in \"%s\": Invalid "
556			    "umask value\n", cfgfile);
557			exit(1);
558		}
559		coll->co_umask = mask;
560		break;
561	case PT_USE_REL_SUFFIX:
562		coll->co_options |= CO_USERELSUFFIX;
563		break;
564	case PT_DELETE:
565		coll->co_options |= CO_DELETE | CO_EXACTRCS;
566		break;
567	case PT_COMPRESS:
568		coll->co_options |= CO_COMPRESS;
569		break;
570	case PT_NORSYNC:
571		coll->co_options |= CO_NORSYNC;
572		break;
573	}
574}
575
576/* Set "coll" as being the default collection. */
577void
578coll_setdef(void)
579{
580
581	coll_free(defaults);
582	defaults = cur_coll;
583	cur_coll = coll_new(defaults);
584}
585