summaryrefslogtreecommitdiffstats
path: root/usr.bin/csup/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/csup/config.c')
-rw-r--r--usr.bin/csup/config.c579
1 files changed, 579 insertions, 0 deletions
diff --git a/usr.bin/csup/config.c b/usr.bin/csup/config.c
new file mode 100644
index 0000000..46ae6cd
--- /dev/null
+++ b/usr.bin/csup/config.c
@@ -0,0 +1,579 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.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.
+ *
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "globtree.h"
+#include "keyword.h"
+#include "misc.h"
+#include "parse.h"
+#include "stream.h"
+#include "token.h"
+
+static int config_parse_refusefiles(struct coll *);
+static int config_parse_refusefile(struct coll *, char *);
+
+extern FILE *yyin;
+
+/* These are globals because I can't think of a better way with yacc. */
+static STAILQ_HEAD(, coll) colls;
+static struct coll *cur_coll;
+static struct coll *defaults;
+static struct coll *ovcoll;
+static int ovmask;
+static const char *cfgfile;
+
+/*
+ * Extract all the configuration information from the config
+ * file and some command line parameters.
+ */
+struct config *
+config_init(const char *file, struct coll *override, int overridemask)
+{
+ struct config *config;
+ struct coll *coll;
+ size_t slen;
+ char *prefix;
+ int error;
+ mode_t mask;
+
+ config = xmalloc(sizeof(struct config));
+ memset(config, 0, sizeof(struct config));
+ STAILQ_INIT(&colls);
+
+ defaults = coll_new(NULL);
+ /* Set the default umask. */
+ mask = umask(0);
+ umask(mask);
+ defaults->co_umask = mask;
+ ovcoll = override;
+ ovmask = overridemask;
+
+ /* Extract a list of collections from the configuration file. */
+ cur_coll = coll_new(defaults);
+ yyin = fopen(file, "r");
+ if (yyin == NULL) {
+ lprintf(-1, "Cannot open \"%s\": %s\n", file, strerror(errno));
+ goto bad;
+ }
+ cfgfile = file;
+ error = yyparse();
+ fclose(yyin);
+ if (error)
+ goto bad;
+
+ memcpy(&config->colls, &colls, sizeof(colls));
+ if (STAILQ_EMPTY(&config->colls)) {
+ lprintf(-1, "Empty supfile\n");
+ goto bad;
+ }
+
+ /* Fixup the list of collections. */
+ STAILQ_FOREACH(coll, &config->colls, co_next) {
+ if (coll->co_base == NULL)
+ coll->co_base = xstrdup("/usr/local/etc/cvsup");
+ if (coll->co_colldir == NULL)
+ coll->co_colldir = "sup";
+ if (coll->co_prefix == NULL) {
+ coll->co_prefix = xstrdup(coll->co_base);
+ /*
+ * If prefix is not an absolute pathname, it is
+ * interpreted relative to base.
+ */
+ } else if (coll->co_prefix[0] != '/') {
+ slen = strlen(coll->co_base);
+ if (slen > 0 && coll->co_base[slen - 1] != '/')
+ xasprintf(&prefix, "%s/%s", coll->co_base,
+ coll->co_prefix);
+ else
+ xasprintf(&prefix, "%s%s", coll->co_base,
+ coll->co_prefix);
+ free(coll->co_prefix);
+ coll->co_prefix = prefix;
+ }
+ coll->co_prefixlen = strlen(coll->co_prefix);
+ /* Determine whether to checksum RCS files or not. */
+ if (coll->co_options & CO_EXACTRCS)
+ coll->co_options |= CO_CHECKRCS;
+ else
+ coll->co_options &= ~CO_CHECKRCS;
+ /* In recent versions, we always try to set the file modes. */
+ coll->co_options |= CO_SETMODE;
+ coll->co_options |= CO_NORSYNC;
+ error = config_parse_refusefiles(coll);
+ if (error)
+ goto bad;
+ }
+
+ coll_free(cur_coll);
+ coll_free(defaults);
+ config->host = STAILQ_FIRST(&config->colls)->co_host;
+ return (config);
+bad:
+ coll_free(cur_coll);
+ coll_free(defaults);
+ config_free(config);
+ return (NULL);
+}
+
+int
+config_checkcolls(struct config *config)
+{
+ char linkname[4];
+ struct stat sb;
+ struct coll *coll;
+ int error, numvalid, ret;
+
+ numvalid = 0;
+ STAILQ_FOREACH(coll, &config->colls, co_next) {
+ error = stat(coll->co_prefix, &sb);
+ if (error || !S_ISDIR(sb.st_mode)) {
+ /* Skip this collection, and warn about it unless its
+ prefix is a symbolic link pointing to "SKIP". */
+ coll->co_options |= CO_SKIP;
+ ret = readlink(coll->co_prefix, linkname,
+ sizeof(linkname));
+ if (ret != 4 || memcmp(linkname, "SKIP", 4) != 0) {
+ lprintf(-1,"Nonexistent prefix \"%s\" for "
+ "%s/%s\n", coll->co_prefix, coll->co_name,
+ coll->co_release);
+ }
+ continue;
+ }
+ numvalid++;
+ }
+ return (numvalid);
+}
+
+static int
+config_parse_refusefiles(struct coll *coll)
+{
+ char *collstem, *suffix, *supdir, *path;
+ int error;
+
+ if (coll->co_colldir[0] == '/')
+ supdir = xstrdup(coll->co_colldir);
+ else
+ xasprintf(&supdir, "%s/%s", coll->co_base, coll->co_colldir);
+
+ /* First, the global refuse file that applies to all collections. */
+ xasprintf(&path, "%s/refuse", supdir);
+ error = config_parse_refusefile(coll, path);
+ free(path);
+ if (error) {
+ free(supdir);
+ return (error);
+ }
+
+ /* Next the per-collection refuse files that applies to all release/tag
+ combinations. */
+ xasprintf(&collstem, "%s/%s/refuse", supdir, coll->co_name);
+ free(supdir);
+ error = config_parse_refusefile(coll, collstem);
+ if (error) {
+ free(collstem);
+ return (error);
+ }
+
+ /* Finally, the per-release and per-tag refuse file. */
+ suffix = coll_statussuffix(coll);
+ if (suffix != NULL) {
+ xasprintf(&path, "%s%s", collstem, suffix);
+ free(suffix);
+ error = config_parse_refusefile(coll, path);
+ free(path);
+ }
+ free(collstem);
+ return (error);
+}
+
+/*
+ * Parses a "refuse" file, and records the relevant information in
+ * coll->co_refusals. If the file does not exist, it is silently
+ * ignored.
+ */
+static int
+config_parse_refusefile(struct coll *coll, char *path)
+{
+ struct stream *rd;
+ char *cp, *line, *pat;
+
+ rd = stream_open_file(path, O_RDONLY);
+ if (rd == NULL)
+ return (0);
+ while ((line = stream_getln(rd, NULL)) != NULL) {
+ pat = line;
+ for (;;) {
+ /* Trim leading whitespace. */
+ pat += strspn(pat, " \t");
+ if (pat[0] == '\0')
+ break;
+ cp = strpbrk(pat, " \t");
+ if (cp != NULL)
+ *cp = '\0';
+ pattlist_add(coll->co_refusals, pat);
+ if (cp == NULL)
+ break;
+ pat = cp + 1;
+ }
+ }
+ if (!stream_eof(rd)) {
+ stream_close(rd);
+ lprintf(-1, "Read failure from \"%s\": %s\n", path,
+ strerror(errno));
+ return (-1);
+ }
+ stream_close(rd);
+ return (0);
+}
+
+void
+config_free(struct config *config)
+{
+ struct coll *coll;
+
+ while (!STAILQ_EMPTY(&config->colls)) {
+ coll = STAILQ_FIRST(&config->colls);
+ STAILQ_REMOVE_HEAD(&config->colls, co_next);
+ coll_free(coll);
+ }
+ if (config->server != NULL)
+ stream_close(config->server);
+ if (config->laddr != NULL)
+ free(config->laddr);
+ free(config);
+}
+
+/* Create a new collection, inheriting options from the default collection. */
+struct coll *
+coll_new(struct coll *def)
+{
+ struct coll *new;
+
+ new = xmalloc(sizeof(struct coll));
+ memset(new, 0, sizeof(struct coll));
+ if (def != NULL) {
+ new->co_options = def->co_options;
+ new->co_umask = def->co_umask;
+ if (def->co_host != NULL)
+ new->co_host = xstrdup(def->co_host);
+ if (def->co_base != NULL)
+ new->co_base = xstrdup(def->co_base);
+ if (def->co_date != NULL)
+ new->co_date = xstrdup(def->co_date);
+ if (def->co_prefix != NULL)
+ new->co_prefix = xstrdup(def->co_prefix);
+ if (def->co_release != NULL)
+ new->co_release = xstrdup(def->co_release);
+ if (def->co_tag != NULL)
+ new->co_tag = xstrdup(def->co_tag);
+ if (def->co_listsuffix != NULL)
+ new->co_listsuffix = xstrdup(def->co_listsuffix);
+ } else {
+ new->co_tag = xstrdup(".");
+ new->co_date = xstrdup(".");
+ }
+ new->co_keyword = keyword_new();
+ new->co_accepts = pattlist_new();
+ new->co_refusals = pattlist_new();
+ new->co_attrignore = FA_DEV | FA_INODE;
+ return (new);
+}
+
+void
+coll_override(struct coll *coll, struct coll *from, int mask)
+{
+ size_t i;
+ int newoptions, oldoptions;
+
+ newoptions = from->co_options & mask;
+ oldoptions = coll->co_options & (CO_MASK & ~mask);
+
+ if (from->co_release != NULL) {
+ if (coll->co_release != NULL)
+ free(coll->co_release);
+ coll->co_release = xstrdup(from->co_release);
+ }
+ if (from->co_host != NULL) {
+ if (coll->co_host != NULL)
+ free(coll->co_host);
+ coll->co_host = xstrdup(from->co_host);
+ }
+ if (from->co_base != NULL) {
+ if (coll->co_base != NULL)
+ free(coll->co_base);
+ coll->co_base = xstrdup(from->co_base);
+ }
+ if (from->co_colldir != NULL)
+ coll->co_colldir = from->co_colldir;
+ if (from->co_prefix != NULL) {
+ if (coll->co_prefix != NULL)
+ free(coll->co_prefix);
+ coll->co_prefix = xstrdup(from->co_prefix);
+ }
+ if (newoptions & CO_CHECKOUTMODE) {
+ if (from->co_tag != NULL) {
+ if (coll->co_tag != NULL)
+ free(coll->co_tag);
+ coll->co_tag = xstrdup(from->co_tag);
+ }
+ if (from->co_date != NULL) {
+ if (coll->co_date != NULL)
+ free(coll->co_date);
+ coll->co_date = xstrdup(from->co_date);
+ }
+ }
+ if (from->co_listsuffix != NULL) {
+ if (coll->co_listsuffix != NULL)
+ free(coll->co_listsuffix);
+ coll->co_listsuffix = xstrdup(from->co_listsuffix);
+ }
+ for (i = 0; i < pattlist_size(from->co_accepts); i++) {
+ pattlist_add(coll->co_accepts,
+ pattlist_get(from->co_accepts, i));
+ }
+ for (i = 0; i < pattlist_size(from->co_refusals); i++) {
+ pattlist_add(coll->co_refusals,
+ pattlist_get(from->co_refusals, i));
+ }
+ coll->co_options = oldoptions | newoptions;
+}
+
+char *
+coll_statussuffix(struct coll *coll)
+{
+ const char *tag;
+ char *suffix;
+
+ if (coll->co_listsuffix != NULL) {
+ xasprintf(&suffix, ".%s", coll->co_listsuffix);
+ } else if (coll->co_options & CO_USERELSUFFIX) {
+ if (coll->co_tag == NULL)
+ tag = ".";
+ else
+ tag = coll->co_tag;
+ if (coll->co_release != NULL) {
+ if (coll->co_options & CO_CHECKOUTMODE) {
+ xasprintf(&suffix, ".%s:%s",
+ coll->co_release, tag);
+ } else {
+ xasprintf(&suffix, ".%s", coll->co_release);
+ }
+ } else if (coll->co_options & CO_CHECKOUTMODE) {
+ xasprintf(&suffix, ":%s", tag);
+ }
+ } else
+ suffix = NULL;
+ return (suffix);
+}
+
+char *
+coll_statuspath(struct coll *coll)
+{
+ char *path, *suffix;
+
+ suffix = coll_statussuffix(coll);
+ if (suffix != NULL) {
+ if (coll->co_colldir[0] == '/')
+ xasprintf(&path, "%s/%s/checkouts%s", coll->co_colldir,
+ coll->co_name, suffix);
+ else
+ xasprintf(&path, "%s/%s/%s/checkouts%s", coll->co_base,
+ coll->co_colldir, coll->co_name, suffix);
+ } else {
+ if (coll->co_colldir[0] == '/')
+ xasprintf(&path, "%s/%s/checkouts", coll->co_colldir,
+ coll->co_name);
+ else
+ xasprintf(&path, "%s/%s/%s/checkouts", coll->co_base,
+ coll->co_colldir, coll->co_name);
+ }
+ free(suffix);
+ return (path);
+}
+
+void
+coll_add(char *name)
+{
+ struct coll *coll;
+
+ cur_coll->co_name = name;
+ coll_override(cur_coll, ovcoll, ovmask);
+ if (cur_coll->co_release == NULL) {
+ lprintf(-1, "Release not specified for collection "
+ "\"%s\"\n", cur_coll->co_name);
+ exit(1);
+ }
+ if (cur_coll->co_host == NULL) {
+ lprintf(-1, "Host not specified for collection "
+ "\"%s\"\n", cur_coll->co_name);
+ exit(1);
+ }
+ if (!STAILQ_EMPTY(&colls)) {
+ coll = STAILQ_LAST(&colls, coll, co_next);
+ if (strcmp(coll->co_host, cur_coll->co_host) != 0) {
+ lprintf(-1, "All \"host\" fields in the supfile "
+ "must be the same\n");
+ exit(1);
+ }
+ }
+ STAILQ_INSERT_TAIL(&colls, cur_coll, co_next);
+ cur_coll = coll_new(defaults);
+}
+
+void
+coll_free(struct coll *coll)
+{
+
+ if (coll == NULL)
+ return;
+ if (coll->co_host != NULL)
+ free(coll->co_host);
+ if (coll->co_base != NULL)
+ free(coll->co_base);
+ if (coll->co_date != NULL)
+ free(coll->co_date);
+ if (coll->co_prefix != NULL)
+ free(coll->co_prefix);
+ if (coll->co_release != NULL)
+ free(coll->co_release);
+ if (coll->co_tag != NULL)
+ free(coll->co_tag);
+ if (coll->co_cvsroot != NULL)
+ free(coll->co_cvsroot);
+ if (coll->co_name != NULL)
+ free(coll->co_name);
+ if (coll->co_listsuffix != NULL)
+ free(coll->co_listsuffix);
+ keyword_free(coll->co_keyword);
+ if (coll->co_dirfilter != NULL)
+ globtree_free(coll->co_dirfilter);
+ if (coll->co_dirfilter != NULL)
+ globtree_free(coll->co_filefilter);
+ if (coll->co_norsync != NULL)
+ globtree_free(coll->co_norsync);
+ if (coll->co_accepts != NULL)
+ pattlist_free(coll->co_accepts);
+ if (coll->co_refusals != NULL)
+ pattlist_free(coll->co_refusals);
+ free(coll);
+}
+
+void
+coll_setopt(int opt, char *value)
+{
+ struct coll *coll;
+ int error, mask;
+
+ coll = cur_coll;
+ switch (opt) {
+ case PT_HOST:
+ if (coll->co_host != NULL)
+ free(coll->co_host);
+ coll->co_host = value;
+ break;
+ case PT_BASE:
+ if (coll->co_base != NULL)
+ free(coll->co_base);
+ coll->co_base = value;
+ break;
+ case PT_DATE:
+ if (coll->co_date != NULL)
+ free(coll->co_date);
+ coll->co_date = value;
+ coll->co_options |= CO_CHECKOUTMODE;
+ break;
+ case PT_PREFIX:
+ if (coll->co_prefix != NULL)
+ free(coll->co_prefix);
+ coll->co_prefix = value;
+ break;
+ case PT_RELEASE:
+ if (coll->co_release != NULL)
+ free(coll->co_release);
+ coll->co_release = value;
+ break;
+ case PT_TAG:
+ if (coll->co_tag != NULL)
+ free(coll->co_tag);
+ coll->co_tag = value;
+ coll->co_options |= CO_CHECKOUTMODE;
+ break;
+ case PT_LIST:
+ if (strchr(value, '/') != NULL) {
+ lprintf(-1, "Parse error in \"%s\": \"list\" suffix "
+ "must not contain slashes\n", cfgfile);
+ exit(1);
+ }
+ if (coll->co_listsuffix != NULL)
+ free(coll->co_listsuffix);
+ coll->co_listsuffix = value;
+ break;
+ case PT_UMASK:
+ error = asciitoint(value, &mask, 8);
+ free(value);
+ if (error) {
+ lprintf(-1, "Parse error in \"%s\": Invalid "
+ "umask value\n", cfgfile);
+ exit(1);
+ }
+ coll->co_umask = mask;
+ break;
+ case PT_USE_REL_SUFFIX:
+ coll->co_options |= CO_USERELSUFFIX;
+ break;
+ case PT_DELETE:
+ coll->co_options |= CO_DELETE | CO_EXACTRCS;
+ break;
+ case PT_COMPRESS:
+ coll->co_options |= CO_COMPRESS;
+ break;
+ case PT_NORSYNC:
+ coll->co_options |= CO_NORSYNC;
+ break;
+ }
+}
+
+/* Set "coll" as being the default collection. */
+void
+coll_setdef(void)
+{
+
+ coll_free(defaults);
+ defaults = cur_coll;
+ cur_coll = coll_new(defaults);
+}
OpenPOWER on IntegriCloud