/*- * Copyright (c) 2003-2006, Maxime Henrion * 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 #include #include #include #include #include #include #include #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); }