lulf 6ce0f78fdf - Implement rsync support in csup, which is chosen as a protocol for regular
files if the client supports it. The support is implemented with an API to
  operate on files, calculating the rolling checksum and md5 checksum for the
  blocks etc.
- Remove unneeded stream_filter_stop and stream_flush before stream_close.
2008-10-25 10:54:28 +00:00

583 lines
14 KiB
C

/*-
* 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;
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 (!(cur_coll->co_options & CO_CHECKOUTMODE)) {
lprintf(-1, "Client only supports checkout mode\n");
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);
}