/*-
 * Copyright (c) 2000-2005 MAEKAWA Masahide <maekawa@cvsync.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include "compat_stdbool.h"
#include "compat_stdint.h"
#include "compat_stdio.h"
#include "compat_inttypes.h"
#include "compat_limits.h"
#include "compat_strings.h"

#include "attribute.h"
#include "collection.h"
#include "cvsync.h"
#include "hash.h"
#include "logmsg.h"
#include "network.h"
#include "token.h"

#include "config_common.h"
#include "defs.h"

#ifndef CVSYNCD_DEFAULT_MAXCLIENTS
#define	CVSYNCD_DEFAULT_MAXCLIENTS	16
#endif /* CVSYNCD_DEFAULT_MAXCLIENTS */

#define	CVSYNCD_MIN_MAXCLIENTS		1
#define	CVSYNCD_MAX_MAXCLIENTS		2048

enum {
	TOK_ACL,
	TOK_BASE,
	TOK_BASE_PREFIX,
	TOK_COLLECTION,
	TOK_COMMENT,
	TOK_CONFIG,
	TOK_DISTFILE,
	TOK_ERRORMODE,
	TOK_HALTFILE,
	TOK_HASH,
	TOK_LBRACE,
	TOK_LISTEN,
	TOK_LOOSE,
	TOK_MAXCLIENTS,
	TOK_NAME,
	TOK_NOFOLLOW,
	TOK_PIDFILE,
	TOK_PORT,
	TOK_PREFIX,
	TOK_RBRACE,
	TOK_RELEASE,
	TOK_SCANFILE,
	TOK_SUPER,
	TOK_UMASK,

	TOK_UNKNOWN
};

static const struct token_keyword config_keywords[] = {
	{ "{",			1,	TOK_LBRACE },
	{ "}",			1,	TOK_RBRACE },
	{ "access",		6,	TOK_ACL },
	{ "base",		4,	TOK_BASE },
	{ "base-prefix",	11,	TOK_BASE_PREFIX },
	{ "collection",		10,	TOK_COLLECTION },
	{ "comment",		7,	TOK_COMMENT },
	{ "config",		6,	TOK_CONFIG },
	{ "distfile",		8,	TOK_DISTFILE },
	{ "errormode",		9,	TOK_ERRORMODE },
	{ "haltfile",		8,	TOK_HALTFILE },
	{ "hash",		4,	TOK_HASH },
	{ "listen",		6,	TOK_LISTEN },
	{ "loose",		5,	TOK_LOOSE },
	{ "maxclients",		10,	TOK_MAXCLIENTS },
	{ "name",		4,	TOK_NAME },
	{ "nofollow",		8,	TOK_NOFOLLOW },
	{ "pidfile",		7,	TOK_PIDFILE },
	{ "port",		4,	TOK_PORT },
	{ "prefix",		6,	TOK_PREFIX },
	{ "release",		7,	TOK_RELEASE },
	{ "scanfile",		8,	TOK_SCANFILE },
	{ "super",		5,	TOK_SUPER },
	{ "umask",		5,	TOK_UMASK },
	{ NULL,			0,	TOK_UNKNOWN },
};

struct config *config_parse(struct config_args *);
struct config *config_parse_config(struct config_args *);
struct collection *config_parse_collection(struct config_args *);

bool config_resolv_distfile(struct config *, struct collection *);
bool config_resolv_super(struct config *, struct collection *);
bool config_set_collection_default_rcs(struct collection *);
bool config_set_collection_super(struct collection *, struct collection *);

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

struct config *
config_load(const char *cfname)
{
	struct config *cf;
	struct config_args *ca;
	struct config_include *ci;
	int wn;

	if ((ca = config_open(cfname)) == NULL)
		return (NULL);

	if ((cf = config_parse(ca)) == NULL) {
		config_close(ca);
		return (NULL);
	}
	cf->cf_refcnt = 0;

	if ((ci = malloc(sizeof(*ci))) == NULL) {
		config_close(ca);
		config_destroy(cf);
		return (NULL);
	}
	ci->ci_next = cf->cf_includes;
	cf->cf_includes = ci;
	wn = snprintf(ci->ci_name, sizeof(ci->ci_name), "%s", cfname);
	if ((wn <= 0) || ((size_t)wn >= sizeof(ci->ci_name))) {
		logmsg_err("%s: %s", cfname, strerror(EINVAL));
		config_close(ca);
		config_destroy(cf);
		return (NULL);
	}
	ci->ci_mtime = ca->ca_mtime;

	if (!config_close(ca)) {
		config_destroy(cf);
		return (NULL);
	}

	return (cf);
}

void
config_destroy(struct config *cf)
{
	struct config_include *ci, *next;

	if (cf != NULL) {
		ci = cf->cf_includes;
		while (ci != NULL) {
			next = ci->ci_next;
			free(ci);
			ci = next;
		}
		collection_destroy_all(cf->cf_collections);
		free(cf);
	}
}

void
config_acquire(struct config *cf)
{
	pthread_mutex_lock(&mtx);
	cf->cf_refcnt++;
	pthread_mutex_unlock(&mtx);
}

void
config_revoke(struct config *cf)
{
	pthread_mutex_lock(&mtx);
	if (--cf->cf_refcnt < 0)
		config_destroy(cf);
	pthread_mutex_unlock(&mtx);
}

bool
config_ischanged(struct config *cf)
{
	struct config_include *ci;
	struct stat st;

	for (ci = cf->cf_includes ; ci != NULL ; ci = ci->ci_next) {
		if (stat(ci->ci_name, &st) == -1) {
			logmsg_err("%s: %s", ci->ci_name, strerror(errno));
			continue;
		}
		if (st.st_mtime != ci->ci_mtime) {
			ci->ci_mtime = st.st_mtime;
			return (true);
		}
	}

	return (false);
}

struct config *
config_parse(struct config_args *ca)
{
	const struct token_keyword *key;
	struct config *cf = NULL;
	FILE *fp = ca->ca_fp;

	lineno = 1;

	for (;;) {
		if (!token_skip_whitespace(fp)) {
			if (feof(fp) == 0)
				return (NULL);
			break;
		}

		if ((key = token_get_keyword(fp, config_keywords)) == NULL)
			return (NULL);

		switch (key->type) {
		case TOK_CONFIG:
			if (cf != NULL) {
				logmsg_err("line %u: duplication", lineno);
				config_destroy(cf);
				return (NULL);
			}
			if ((cf = config_parse_config(ca)) == NULL)
				return (NULL);
			break;
		default:
			logmsg_err("line %u: %s: invalid keyword", lineno,
				   key->name);
			if (cf != NULL)
				config_destroy(cf);
			return (NULL);
		}
	}

	return (cf);
}

struct config *
config_parse_config(struct config_args *ca)
{
	const struct token_keyword *key;
	struct collection *cl;
	struct config *cf;
	FILE *fp = ca->ca_fp;
	unsigned long ul;

	if ((key = token_get_keyword(fp, config_keywords)) == NULL)
		return (NULL);
	if (key->type != TOK_LBRACE) {
		logmsg_err("line %u: missing '{' for the 'config'", lineno);
		return (NULL);
	}

	if ((cf = malloc(sizeof(*cf))) == NULL) {
		logmsg_err("%s", strerror(errno));
		return (NULL);
	}
	(void)memset(cf, 0, sizeof(*cf));
	cf->cf_maxclients = (size_t)-1;
	cf->cf_hash = HASH_UNSPEC;

	for (;;) {
		if ((key = token_get_keyword(fp, config_keywords)) == NULL) {
			config_destroy(cf);
			return (NULL);
		}

		if (key->type == TOK_RBRACE)
			break;
		ca->ca_key = key;

		switch (key->type) {
		case TOK_ACL:
			ca->ca_buffer = cf->cf_access_name;
			ca->ca_bufsize = sizeof(cf->cf_access_name);
			if (!config_set_string(ca)) {
				config_destroy(cf);
				return (NULL);
			}
			break;
		case TOK_BASE:
			if (!config_parse_base(ca, cf)) {
				config_destroy(cf);
				return (NULL);
			}
			break;
		case TOK_BASE_PREFIX:
			if (!config_parse_base_prefix(ca, cf)) {
				config_destroy(cf);
				return (NULL);
			}
			break;
		case TOK_COLLECTION:
			if ((cl = config_parse_collection(ca)) == NULL) {
				config_destroy(cf);
				return (NULL);
			}
			if (!config_insert_collection(cf, cl)) {
				collection_destroy(cl);
				config_destroy(cf);
				return (NULL);
			}
			break;
		case TOK_HALTFILE:
			ca->ca_buffer = cf->cf_halt_name;
			ca->ca_bufsize = sizeof(cf->cf_halt_name);
			if (!config_set_string(ca)) {
				config_destroy(cf);
				return (NULL);
			}
			break;
		case TOK_HASH:
			if (!config_parse_hash(ca, cf)) {
				config_destroy(cf);
				return (NULL);
			}
			break;
		case TOK_LISTEN:
			ca->ca_buffer = cf->cf_addr;
			ca->ca_bufsize = sizeof(cf->cf_addr);
			if (!config_set_string(ca)) {
				config_destroy(cf);
				return (NULL);
			}
			break;
		case TOK_MAXCLIENTS:
			if (cf->cf_maxclients != (size_t)-1) {
				logmsg_err("line %u: found duplication of the "
					   "'%s'", lineno, key->name);
				config_destroy(cf);
				return (NULL);
			}
			if (!token_get_number(fp, &ul)) {
				config_destroy(cf);
				return (NULL);
			}
			if ((ul < CVSYNCD_MIN_MAXCLIENTS) ||
			    (ul > CVSYNCD_MAX_MAXCLIENTS)) {
				logmsg_err("line %u: %s %lu: %s", lineno,
					   key->name, ul, strerror(ERANGE));
				config_destroy(cf);
				return (NULL);
			}
			cf->cf_maxclients = (size_t)ul;
			break;
		case TOK_PIDFILE:
			ca->ca_buffer = cf->cf_pid_name;
			ca->ca_bufsize = sizeof(cf->cf_pid_name);
			if (!config_set_string(ca)) {
				config_destroy(cf);
				return (NULL);
			}
			break;
		case TOK_PORT:
			ca->ca_buffer = cf->cf_serv;
			ca->ca_bufsize = sizeof(cf->cf_serv);
			if (!config_set_string(ca)) {
				config_destroy(cf);
				return (NULL);
			}
			break;
		default:
			logmsg_err("line %u: %s: invalid keyword", lineno,
				   key->name);
			config_destroy(cf);
			return (NULL);
		}
	}

	if (strlen(cf->cf_serv) == 0) {
		snprintf(cf->cf_serv, sizeof(cf->cf_serv), "%s",
			 CVSYNC_DEFAULT_PORT);
	}
	if (cf->cf_maxclients == (size_t)-1)
		cf->cf_maxclients = CVSYNCD_DEFAULT_MAXCLIENTS;
	if (cf->cf_hash == HASH_UNSPEC)
		cf->cf_hash = HASH_DEFAULT_TYPE;

	if ((strlen(cf->cf_access_name) != 0) &&
	    (cf->cf_access_name[0] != '/')) {
		logmsg_err("access %s: must be the absolute path",
			   cf->cf_access_name);
		config_destroy(cf);
		return (NULL);
	}
	if ((strlen(cf->cf_halt_name) != 0) && (cf->cf_halt_name[0] != '/')) {
		logmsg_err("haltfile %s: must be the absolute path",
			   cf->cf_halt_name);
		config_destroy(cf);
		return (NULL);
	}
	if ((strlen(cf->cf_pid_name) != 0) && (cf->cf_pid_name[0] != '/')) {
		logmsg_err("pidfile %s: must be the absolute path",
			   cf->cf_pid_name);
		config_destroy(cf);
		return (NULL);
	}

	if (cf->cf_collections == NULL) {
		logmsg_err("no collections");
		config_destroy(cf);
		return (NULL);
	}

	for (cl = cf->cf_collections ; cl != NULL ; cl = cl->cl_next) {
		if (!config_resolv_distfile(cf, cl)) {
			config_destroy(cf);
			return (NULL);
		}
		if (!config_resolv_prefix(cf, cl, true /* rdonly */)) {
			config_destroy(cf);
			return (NULL);
		}
		if (!config_resolv_scanfile(cf, cl)) {
			config_destroy(cf);
			return (NULL);
		}
		if ((strlen(cl->cl_super_name) != 0) &&
		    !config_resolv_super(cf, cl)) {
			config_destroy(cf);
			return (NULL);
		}
	}
	for (cl = cf->cf_collections ; cl != NULL ; cl = cl->cl_next) {
		if (cl->cl_super == NULL)
			continue;
		if (strcmp(cl->cl_release, cl->cl_super->cl_release) != 0)
			continue;

		if (!config_set_collection_super(cl, cl->cl_super)) {
			config_destroy(cf);
			return (NULL);
		}
	}

	return (cf);
}

struct collection *
config_parse_collection(struct config_args *ca)
{
	const struct token_keyword *key;
	struct collection *cl;
	FILE *fp = ca->ca_fp;

	if ((key = token_get_keyword(fp, config_keywords)) == NULL)
		return (NULL);
	if (key->type != TOK_LBRACE) {
		logmsg_err("line %u: missing '{' for the 'collection'",
			   lineno);
		return (NULL);
	}

	if ((cl = malloc(sizeof(*cl))) == NULL) {
		logmsg_err("%s", strerror(errno));
		return (NULL);
	}
	(void)memset(cl, 0, sizeof(*cl));
	cl->cl_errormode = CVSYNC_ERRORMODE_UNSPEC;
	cl->cl_symfollow = true;
	cl->cl_umask = CVSYNC_UMASK_UNSPEC;

	for (;;) {
		if ((key = token_get_keyword(fp, config_keywords)) == NULL) {
			collection_destroy(cl);
			return (NULL);
		}

		if (key->type == TOK_RBRACE)
			break;
		ca->ca_key = key;

		switch (key->type) {
		case TOK_COMMENT:
			ca->ca_buffer = cl->cl_comment;
			ca->ca_bufsize = sizeof(cl->cl_comment);
			if (!config_set_string(ca)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		case TOK_DISTFILE:
			ca->ca_buffer = cl->cl_dist_name;
			ca->ca_bufsize = sizeof(cl->cl_dist_name);
			if (!config_set_string(ca)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		case TOK_ERRORMODE:
			if (!config_parse_errormode(ca, cl)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		case TOK_LOOSE:
			logmsg_err("line %u: '%s' is obsoleted", lineno,
				   key->name);
			if (cl->cl_errormode != CVSYNC_ERRORMODE_UNSPEC) {
				logmsg_err("line %u: conflicts '%s' with "
					   "'errormode'", lineno, key->name);
				collection_destroy(cl);
				return (NULL);
			}
			cl->cl_errormode = CVSYNC_ERRORMODE_FIXUP;
			break;
		case TOK_NAME:
			ca->ca_buffer =  cl->cl_name;
			ca->ca_bufsize = sizeof(cl->cl_name);
			if (!config_set_string(ca)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		case TOK_NOFOLLOW:
			if (!cl->cl_symfollow) {
				logmsg_err("line %u: found duplication of the "
					   "'%s'", lineno, key->name);
				collection_destroy(cl);
				return (NULL);
			}
			cl->cl_symfollow = false;
			break;
		case TOK_PREFIX:
			ca->ca_buffer = cl->cl_prefix;
			ca->ca_bufsize = sizeof(cl->cl_prefix);
			if (!config_set_string(ca)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		case TOK_RELEASE:
			if (!config_parse_release(ca, cl)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		case TOK_SCANFILE:
			ca->ca_buffer = cl->cl_scan_name;
			ca->ca_bufsize = sizeof(cl->cl_scan_name);
			if (!config_set_string(ca)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		case TOK_SUPER:
			ca->ca_buffer = cl->cl_super_name;
			ca->ca_bufsize = sizeof(cl->cl_super_name);
			if (!config_set_string(ca)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		case TOK_UMASK:
			if (!config_parse_umask(ca, cl)) {
				collection_destroy(cl);
				return (NULL);
			}
			break;
		default:
			logmsg_err("line %u: %s: invalid keyword", lineno,
				   key->name);
			collection_destroy(cl);
			return (NULL);
		}
	}

	if (strlen(cl->cl_name) == 0) {
		logmsg_err("no collection name");
		collection_destroy(cl);
		return (NULL);
	}
	if (strlen(cl->cl_release) == 0) {
		logmsg_err("collection %s: no release", cl->cl_name);
		collection_destroy(cl);
		return (NULL);
	}

	switch (cvsync_release_pton(cl->cl_release)) {
	case CVSYNC_RELEASE_RCS:
		if (!config_set_collection_default_rcs(cl)) {
			collection_destroy(cl);
			return (NULL);
		}
		break;
	default:
		/* NOTREACHED */
		return (NULL);
	}

	if (cl->cl_errormode == CVSYNC_ERRORMODE_UNSPEC)
		cl->cl_errormode = CVSYNC_ERRORMODE_ABORT;

	return (cl);
}

bool
config_resolv_distfile(struct config *cf, struct collection *cl)
{
	char path[PATH_MAX + CVSYNC_NAME_MAX + 1];
	int wn;

	if (strlen(cl->cl_dist_name) == 0)
		return (true);

	if (cl->cl_dist_name[0] != '/') {
		if (strlen(cf->cf_base) == 0) {
			logmsg_err("collection %s/%s: distfile %s: must be an "
				   "absolute path", cl->cl_name,
				   cl->cl_release, cl->cl_dist_name);
			return (false);
		}
		wn = snprintf(path, sizeof(path), "%s/%s", cf->cf_base,
			      cl->cl_dist_name);
	} else {
		wn = snprintf(path, sizeof(path), "%s", cl->cl_dist_name);
	}
	if ((wn <= 0) || ((size_t)wn >= sizeof(path))) {
		logmsg_err("collection %s/%s: distfile %s: %s", cl->cl_name,
			   cl->cl_release, cl->cl_dist_name, strerror(EINVAL));
		return (false);
	}
	wn = snprintf(cl->cl_dist_name, sizeof(cl->cl_dist_name), "%s", path);
	if ((wn <= 0) || ((size_t)wn >= sizeof(cl->cl_dist_name))) {
		logmsg_err("collection %s/%s: distfile %s: %s", cl->cl_name,
			   cl->cl_release, path, strerror(EINVAL));
		return (false);
	}

	return (true);
}

bool
config_resolv_super(struct config *cf, struct collection *cl)
{
	struct collection *super;
	char *name = cl->cl_super_name;
	int type = cvsync_release_pton(cl->cl_release);
	bool found = false;

	for (super = cf->cf_collections ;
	     super != NULL ;
	     super = super->cl_next) {
		if (super == cl)
			continue;
		if (strcasecmp(super->cl_name, name) != 0)
			continue;
		switch (cvsync_release_pton(super->cl_release)) {
		case CVSYNC_RELEASE_RCS:
			if (type != CVSYNC_RELEASE_RCS)
				break;
			found = true;
			break;
		default:
			/* NOTREACHED */
			break;
		}
		if (found)
			break;
	}
	if (!found) {
		logmsg_err("Not found such a super collection: %s/%s", name,
			   cl->cl_release);
		return (false);
	}

	cl->cl_super = super;

	return (true);
}

bool
config_set_collection_default_rcs(struct collection *cl)
{
	bool found_errors = false;

	if (strlen(cl->cl_super_name) != 0) {
		if (cl->cl_umask != CVSYNC_UMASK_UNSPEC) {
			logmsg_err("collection %s/%s: invalid 'umask'",
				   cl->cl_name, cl->cl_release);
			found_errors = true;
		}
		if (!cl->cl_symfollow) {
			logmsg_err("collection %s/%s: invalid 'nofollow'",
				   cl->cl_name, cl->cl_release);
			found_errors = true;
		}
		if (found_errors)
			return (false);
	}

	if (cl->cl_umask == CVSYNC_UMASK_UNSPEC)
		cl->cl_umask = CVSYNC_UMASK_RCS;

	return (true);
}

bool
config_set_collection_super(struct collection *cl, struct collection *super)
{
	while (super->cl_super != NULL) {
		if (cl == super) {
			logmsg_err("Detect cyclic dependency of 'super' from "
				   "%s/%s", cl->cl_name, cl->cl_release);
			return (false);
		}
		super = super->cl_super;
	}

	if ((super->cl_prefixlen >= cl->cl_prefixlen) ||
	    (cl->cl_prefix[super->cl_prefixlen - 1] != '/') ||
	    (memcmp(super->cl_prefix, cl->cl_prefix,
		    super->cl_prefixlen) != 0)) {
		logmsg_err("prefix %s of %s/%s must be sub-directory of "
			   "prefix %s of %s/%s", cl->cl_prefix, cl->cl_name,
			   cl->cl_release, super->cl_prefix, super->cl_name,
			   super->cl_release);
		return (false);
	}

	if ((strlen(cl->cl_dist_name) == 0) &&
	    (strlen(super->cl_dist_name) != 0)) {
		snprintf(cl->cl_dist_name, sizeof(cl->cl_dist_name), "%s",
			 super->cl_dist_name);
	}
	if ((strlen(cl->cl_scan_name) == 0) &&
	    (strlen(super->cl_scan_name) != 0)) {
		snprintf(cl->cl_scan_name, sizeof(cl->cl_scan_name), "%s",
			 super->cl_scan_name);
	}

	cl->cl_rprefixlen = cl->cl_prefixlen - super->cl_prefixlen - 1;
	if (cl->cl_rprefixlen > sizeof(cl->cl_rprefix))
		return (false);
	if (cl->cl_rprefixlen > 0) {
		(void)memcpy(cl->cl_rprefix,
			     &cl->cl_prefix[super->cl_prefixlen],
			     cl->cl_rprefixlen);
		cl->cl_rprefix[cl->cl_rprefixlen] = '/';
	}
	cl->cl_prefixlen = super->cl_prefixlen;
	(void)memcpy(cl->cl_prefix, super->cl_prefix, cl->cl_prefixlen);
	cl->cl_prefix[cl->cl_prefixlen] = '\0';

	cl->cl_super = super;
	cl->cl_errormode = super->cl_errormode;

	switch (cvsync_release_pton(cl->cl_release)) {
	case CVSYNC_RELEASE_RCS:
		cl->cl_symfollow = super->cl_symfollow;
		cl->cl_umask = super->cl_umask;
		break;
	default:
		/* NOTREACHED */
		break;
	}

	return (true);
}


syntax highlighted by Code2HTML, v. 0.9.1