summaryrefslogtreecommitdiffstats
path: root/contrib/csup/keyword.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/csup/keyword.c')
-rw-r--r--contrib/csup/keyword.c502
1 files changed, 502 insertions, 0 deletions
diff --git a/contrib/csup/keyword.c b/contrib/csup/keyword.c
new file mode 100644
index 0000000..dab44f0
--- /dev/null
+++ b/contrib/csup/keyword.c
@@ -0,0 +1,502 @@
+/*-
+ * 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);
+}
+
+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);
+}
OpenPOWER on IntegriCloud