freebsd-dev/contrib/csup/keyword.c
2008-10-19 09:08:59 +00:00

526 lines
12 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 <assert.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "diff.h"
#include "keyword.h"
#include "misc.h"
#include "queue.h"
#include "stream.h"
/*
* The keyword API is used to expand the CVS/RCS keywords in files,
* such as $Id$, $Revision$, etc. The server does it for us when it
* sends us entire files, but we need to handle the expansion when
* applying a diff update.
*/
enum rcskey {
RCSKEY_AUTHOR,
RCSKEY_CVSHEADER,
RCSKEY_DATE,
RCSKEY_HEADER,
RCSKEY_ID,
RCSKEY_LOCKER,
RCSKEY_LOG,
RCSKEY_NAME,
RCSKEY_RCSFILE,
RCSKEY_REVISION,
RCSKEY_SOURCE,
RCSKEY_STATE
};
typedef enum rcskey rcskey_t;
struct tag {
char *ident;
rcskey_t key;
int enabled;
STAILQ_ENTRY(tag) next;
};
static struct tag *tag_new(const char *, rcskey_t);
static char *tag_expand(struct tag *, struct diffinfo *);
static void tag_free(struct tag *);
struct keyword {
STAILQ_HEAD(, tag) keywords; /* Enabled keywords. */
size_t minkeylen;
size_t maxkeylen;
};
/* Default CVS keywords. */
static struct {
const char *ident;
rcskey_t key;
} tag_defaults[] = {
{ "Author", RCSKEY_AUTHOR },
{ "CVSHeader", RCSKEY_CVSHEADER },
{ "Date", RCSKEY_DATE },
{ "Header", RCSKEY_HEADER },
{ "Id", RCSKEY_ID },
{ "Locker", RCSKEY_LOCKER },
{ "Log", RCSKEY_LOG },
{ "Name", RCSKEY_NAME },
{ "RCSfile", RCSKEY_RCSFILE },
{ "Revision", RCSKEY_REVISION },
{ "Source", RCSKEY_SOURCE },
{ "State", RCSKEY_STATE },
{ NULL, 0, }
};
struct keyword *
keyword_new(void)
{
struct keyword *new;
struct tag *tag;
size_t len;
int i;
new = xmalloc(sizeof(struct keyword));
STAILQ_INIT(&new->keywords);
new->minkeylen = ~0;
new->maxkeylen = 0;
for (i = 0; tag_defaults[i].ident != NULL; i++) {
tag = tag_new(tag_defaults[i].ident, tag_defaults[i].key);
STAILQ_INSERT_TAIL(&new->keywords, tag, next);
len = strlen(tag->ident);
/*
* These values are only computed here and not updated when
* adding an alias. This is a bug, but CVSup has it and we
* need to be bug-to-bug compatible since the server will
* expect us to do the same, and we will fail with an MD5
* checksum mismatch if we don't.
*/
new->minkeylen = min(new->minkeylen, len);
new->maxkeylen = max(new->maxkeylen, len);
}
return (new);
}
int
keyword_decode_expand(const char *expand)
{
if (strcmp(expand, ".") == 0)
return (EXPAND_DEFAULT);
else if (strcmp(expand, "kv") == 0)
return (EXPAND_KEYVALUE);
else if (strcmp(expand, "kvl") == 0)
return (EXPAND_KEYVALUELOCKER);
else if (strcmp(expand, "k") == 0)
return (EXPAND_KEY);
else if (strcmp(expand, "o") == 0)
return (EXPAND_OLD);
else if (strcmp(expand, "b") == 0)
return (EXPAND_BINARY);
else if (strcmp(expand, "v") == 0)
return (EXPAND_VALUE);
else
return (-1);
}
const char *
keyword_encode_expand(int expand)
{
switch (expand) {
case EXPAND_DEFAULT:
return (".");
case EXPAND_KEYVALUE:
return ("kv");
case EXPAND_KEYVALUELOCKER:
return ("kvl");
case EXPAND_KEY:
return ("k");
case EXPAND_OLD:
return ("o");
case EXPAND_BINARY:
return ("b");
case EXPAND_VALUE:
return ("v");
}
return (NULL);
}
void
keyword_free(struct keyword *keyword)
{
struct tag *tag;
if (keyword == NULL)
return;
while (!STAILQ_EMPTY(&keyword->keywords)) {
tag = STAILQ_FIRST(&keyword->keywords);
STAILQ_REMOVE_HEAD(&keyword->keywords, next);
tag_free(tag);
}
free(keyword);
}
int
keyword_alias(struct keyword *keyword, const char *ident, const char *rcskey)
{
struct tag *new, *tag;
STAILQ_FOREACH(tag, &keyword->keywords, next) {
if (strcmp(tag->ident, rcskey) == 0) {
new = tag_new(ident, tag->key);
STAILQ_INSERT_HEAD(&keyword->keywords, new, next);
return (0);
}
}
errno = ENOENT;
return (-1);
}
int
keyword_enable(struct keyword *keyword, const char *ident)
{
struct tag *tag;
int all;
all = 0;
if (strcmp(ident, ".") == 0)
all = 1;
STAILQ_FOREACH(tag, &keyword->keywords, next) {
if (!all && strcmp(tag->ident, ident) != 0)
continue;
tag->enabled = 1;
if (!all)
return (0);
}
if (!all) {
errno = ENOENT;
return (-1);
}
return (0);
}
int
keyword_disable(struct keyword *keyword, const char *ident)
{
struct tag *tag;
int all;
all = 0;
if (strcmp(ident, ".") == 0)
all = 1;
STAILQ_FOREACH(tag, &keyword->keywords, next) {
if (!all && strcmp(tag->ident, ident) != 0)
continue;
tag->enabled = 0;
if (!all)
return (0);
}
if (!all) {
errno = ENOENT;
return (-1);
}
return (0);
}
void
keyword_prepare(struct keyword *keyword)
{
struct tag *tag, *temp;
STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) {
if (!tag->enabled) {
STAILQ_REMOVE(&keyword->keywords, tag, tag, next);
tag_free(tag);
continue;
}
}
}
/*
* Expand appropriate RCS keywords. If there's no tag to expand,
* keyword_expand() returns 0, otherwise it returns 1 and writes a
* pointer to the new line in *buf and the new len in *len. The
* new line is allocated with malloc() and needs to be freed by the
* caller after use.
*/
int
keyword_expand(struct keyword *keyword, struct diffinfo *di, char *line,
size_t size, char **buf, size_t *len)
{
struct tag *tag;
char *dollar, *keystart, *valstart, *vallim, *next;
char *linestart, *newline, *newval, *cp, *tmp;
size_t left, newsize, vallen;
if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY)
return (0);
newline = NULL;
newsize = 0;
left = size;
linestart = cp = line;
again:
dollar = memchr(cp, '$', left);
if (dollar == NULL) {
if (newline != NULL) {
*buf = newline;
*len = newsize;
return (1);
}
return (0);
}
keystart = dollar + 1;
left -= keystart - cp;
vallim = memchr(keystart, '$', left);
if (vallim == NULL) {
if (newline != NULL) {
*buf = newline;
*len = newsize;
return (1);
}
return (0);
}
if (vallim == keystart) {
cp = keystart;
goto again;
}
valstart = memchr(keystart, ':', left);
if (valstart == keystart) {
cp = vallim;
left -= vallim - keystart;
goto again;
}
if (valstart == NULL || valstart > vallim)
valstart = vallim;
if (valstart < keystart + keyword->minkeylen ||
valstart > keystart + keyword->maxkeylen) {
cp = vallim;
left -= vallim -keystart;
goto again;
}
STAILQ_FOREACH(tag, &keyword->keywords, next) {
if (strncmp(tag->ident, keystart, valstart - keystart) == 0 &&
tag->ident[valstart - keystart] == '\0') {
if (newline != NULL)
tmp = newline;
else
tmp = NULL;
newval = NULL;
if (di->di_expand == EXPAND_KEY) {
newsize = dollar - linestart + 1 +
valstart - keystart + 1 +
size - (vallim + 1 - linestart);
newline = xmalloc(newsize);
cp = newline;
memcpy(cp, linestart, dollar - linestart);
cp += dollar - linestart;
*cp++ = '$';
memcpy(cp, keystart, valstart - keystart);
cp += valstart - keystart;
*cp++ = '$';
next = cp;
memcpy(cp, vallim + 1,
size - (vallim + 1 - linestart));
} else if (di->di_expand == EXPAND_VALUE) {
newval = tag_expand(tag, di);
if (newval == NULL)
vallen = 0;
else
vallen = strlen(newval);
newsize = dollar - linestart +
vallen +
size - (vallim + 1 - linestart);
newline = xmalloc(newsize);
cp = newline;
memcpy(cp, linestart, dollar - linestart);
cp += dollar - linestart;
if (newval != NULL) {
memcpy(cp, newval, vallen);
cp += vallen;
}
next = cp;
memcpy(cp, vallim + 1,
size - (vallim + 1 - linestart));
} else {
assert(di->di_expand == EXPAND_DEFAULT ||
di->di_expand == EXPAND_KEYVALUE ||
di->di_expand == EXPAND_KEYVALUELOCKER);
newval = tag_expand(tag, di);
if (newval == NULL)
vallen = 0;
else
vallen = strlen(newval);
newsize = dollar - linestart + 1 +
valstart - keystart + 2 +
vallen + 2 +
size - (vallim + 1 - linestart);
newline = xmalloc(newsize);
cp = newline;
memcpy(cp, linestart, dollar - linestart);
cp += dollar - linestart;
*cp++ = '$';
memcpy(cp, keystart, valstart - keystart);
cp += valstart - keystart;
*cp++ = ':';
*cp++ = ' ';
if (newval != NULL) {
memcpy(cp, newval, vallen);
cp += vallen;
}
*cp++ = ' ';
*cp++ = '$';
next = cp;
memcpy(cp, vallim + 1,
size - (vallim + 1 - linestart));
}
if (newval != NULL)
free(newval);
if (tmp != NULL)
free(tmp);
/*
* Continue looking for tags in the rest of the line.
*/
cp = next;
size = newsize;
left = size - (cp - newline);
linestart = newline;
goto again;
}
}
cp = vallim;
left = size - (cp - linestart);
goto again;
}
static struct tag *
tag_new(const char *ident, rcskey_t key)
{
struct tag *new;
new = xmalloc(sizeof(struct tag));
new->ident = xstrdup(ident);
new->key = key;
new->enabled = 1;
return (new);
}
static void
tag_free(struct tag *tag)
{
free(tag->ident);
free(tag);
}
/*
* Expand a specific tag and return the new value. If NULL
* is returned, the tag is empty.
*/
static char *
tag_expand(struct tag *tag, struct diffinfo *di)
{
/*
* CVS formats dates as "XXXX/XX/XX XX:XX:XX". 32 bytes
* is big enough until year 10,000,000,000,000,000 :-).
*/
char cvsdate[32];
struct tm tm;
char *filename, *val;
int error;
error = rcsdatetotm(di->di_revdate, &tm);
if (error)
err(1, "strptime");
if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0)
err(1, "strftime");
filename = strrchr(di->di_rcsfile, '/');
if (filename == NULL)
filename = di->di_rcsfile;
else
filename++;
switch (tag->key) {
case RCSKEY_AUTHOR:
xasprintf(&val, "%s", di->di_author);
break;
case RCSKEY_CVSHEADER:
xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile,
di->di_revnum, cvsdate, di->di_author, di->di_state);
break;
case RCSKEY_DATE:
xasprintf(&val, "%s", cvsdate);
break;
case RCSKEY_HEADER:
xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot,
di->di_rcsfile, di->di_revnum, cvsdate, di->di_author,
di->di_state);
break;
case RCSKEY_ID:
xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum,
cvsdate, di->di_author, di->di_state);
break;
case RCSKEY_LOCKER:
/*
* Unimplemented even in CVSup sources. It seems we don't
* even have this information sent by the server.
*/
return (NULL);
case RCSKEY_LOG:
/* XXX */
printf("%s: Implement Log keyword expansion\n", __func__);
return (NULL);
case RCSKEY_NAME:
if (di->di_tag != NULL)
xasprintf(&val, "%s", di->di_tag);
else
return (NULL);
break;
case RCSKEY_RCSFILE:
xasprintf(&val, "%s", filename);
break;
case RCSKEY_REVISION:
xasprintf(&val, "%s", di->di_revnum);
break;
case RCSKEY_SOURCE:
xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile);
break;
case RCSKEY_STATE:
xasprintf(&val, "%s", di->di_state);
break;
}
return (val);
}