summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjoris <joris@openbsd.org>2006-04-26 02:55:13 +0000
committerjoris <joris@openbsd.org>2006-04-26 02:55:13 +0000
commit2dc36bedc040afa056feb9a0a587d853dcbf6604 (patch)
tree48d6eb047dd72f506119bc30fe3a10169dc98a00
parentpleasing lint, without displeasing future developers (diff)
downloadwireguard-openbsd-2dc36bedc040afa056feb9a0a587d853dcbf6604.tar.xz
wireguard-openbsd-2dc36bedc040afa056feb9a0a587d853dcbf6604.zip
fork our code we shared between openrcs/cvs into the openrcs dir.
this was starting to become inhuman to maintain without ugly ugly hacks in the shared code, and it will be easier to make specific changes for openrcs without touching the soon-to-be-replaced opencvs code.
-rw-r--r--usr.bin/rcs/Makefile8
-rw-r--r--usr.bin/rcs/buf.c415
-rw-r--r--usr.bin/rcs/buf.h64
-rw-r--r--usr.bin/rcs/ci.c44
-rw-r--r--usr.bin/rcs/co.c20
-rw-r--r--usr.bin/rcs/date.y922
-rw-r--r--usr.bin/rcs/diff.c1394
-rw-r--r--usr.bin/rcs/diff.h114
-rw-r--r--usr.bin/rcs/diff3.c806
-rw-r--r--usr.bin/rcs/includes.h60
-rw-r--r--usr.bin/rcs/rcs.c2922
-rw-r--r--usr.bin/rcs/rcs.h280
-rw-r--r--usr.bin/rcs/rcsclean.c14
-rw-r--r--usr.bin/rcs/rcsdiff.c32
-rw-r--r--usr.bin/rcs/rcsmerge.c14
-rw-r--r--usr.bin/rcs/rcsnum.c398
-rw-r--r--usr.bin/rcs/rcsprog.c20
-rw-r--r--usr.bin/rcs/rcsprog.h7
-rw-r--r--usr.bin/rcs/rcstime.c92
-rw-r--r--usr.bin/rcs/rcsutil.c26
-rw-r--r--usr.bin/rcs/rlog.c16
-rw-r--r--usr.bin/rcs/util.c187
-rw-r--r--usr.bin/rcs/util.h58
-rw-r--r--usr.bin/rcs/worklist.c93
-rw-r--r--usr.bin/rcs/worklist.h45
-rw-r--r--usr.bin/rcs/xmalloc.c105
-rw-r--r--usr.bin/rcs/xmalloc.h31
27 files changed, 8084 insertions, 103 deletions
diff --git a/usr.bin/rcs/Makefile b/usr.bin/rcs/Makefile
index 3c60706be19..427120064f9 100644
--- a/usr.bin/rcs/Makefile
+++ b/usr.bin/rcs/Makefile
@@ -1,16 +1,12 @@
-# $OpenBSD: Makefile,v 1.31 2006/04/21 17:17:29 xsa Exp $
-
-.PATH: ${.CURDIR}/../cvs
+# $OpenBSD: Makefile,v 1.32 2006/04/26 02:55:13 joris Exp $
PROG= rcs
MAN= ci.1 co.1 ident.1 rcs.1 rcsclean.1 rcsdiff.1 rcsmerge.1 rlog.1
SRCS= ci.c co.c ident.c rcsclean.c rcsdiff.c rcsmerge.c rcsprog.c rlog.c \
- rcsutil.c buf.c date.y diff.c diff3.c fatal.c log.c rcs.c rcsnum.c \
+ rcsutil.c buf.c date.y diff.c diff3.c rcs.c rcsnum.c \
rcstime.c util.c worklist.c xmalloc.c
-CPPFLAGS+=-I${.CURDIR}/../cvs -DRCSPROG
-
LINKS= ${BINDIR}/rcs ${BINDIR}/ci ${BINDIR}/rcs ${BINDIR}/co \
${BINDIR}/rcs ${BINDIR}/rcsclean ${BINDIR}/rcs ${BINDIR}/rcsdiff \
${BINDIR}/rcs ${BINDIR}/rcsmerge ${BINDIR}/rcs ${BINDIR}/rlog \
diff --git a/usr.bin/rcs/buf.c b/usr.bin/rcs/buf.c
new file mode 100644
index 00000000000..e4e20060a39
--- /dev/null
+++ b/usr.bin/rcs/buf.c
@@ -0,0 +1,415 @@
+/* $OpenBSD: buf.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2003 Jean-Francois Brousseau <jfb@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#include "includes.h"
+
+#include "buf.h"
+#include "xmalloc.h"
+#include "worklist.h"
+
+#define BUF_INCR 128
+
+struct rcs_buf {
+ u_int cb_flags;
+
+ /* buffer handle and size */
+ u_char *cb_buf;
+ size_t cb_size;
+
+ /* start and length of valid data in buffer */
+ u_char *cb_cur;
+ size_t cb_len;
+};
+
+#define SIZE_LEFT(b) (b->cb_size - (size_t)(b->cb_cur - b->cb_buf) \
+ - b->cb_len)
+
+static void rcs_buf_grow(BUF *, size_t);
+
+/*
+ * rcs_buf_alloc()
+ *
+ * Create a new buffer structure and return a pointer to it. This structure
+ * uses dynamically-allocated memory and must be freed with rcs_buf_free(),
+ * once the buffer is no longer needed.
+ */
+BUF *
+rcs_buf_alloc(size_t len, u_int flags)
+{
+ BUF *b;
+
+ b = xmalloc(sizeof(*b));
+ /* Postpone creation of zero-sized buffers */
+ if (len > 0)
+ b->cb_buf = xcalloc(1, len);
+ else
+ b->cb_buf = NULL;
+
+ b->cb_flags = flags;
+ b->cb_size = len;
+ b->cb_cur = b->cb_buf;
+ b->cb_len = 0;
+
+ return (b);
+}
+
+/*
+ * rcs_buf_load()
+ *
+ * Open the file specified by <path> and load all of its contents into a
+ * buffer.
+ * Returns the loaded buffer on success.
+ */
+BUF *
+rcs_buf_load(const char *path, u_int flags)
+{
+ int fd;
+ ssize_t ret;
+ size_t len;
+ u_char *bp;
+ struct stat st;
+ BUF *buf;
+
+ if ((fd = open(path, O_RDONLY, 0600)) == -1) {
+ warn("%s", path);
+ return (NULL);
+ }
+
+ if (fstat(fd, &st) == -1)
+ err(1, "rcs_buf_load: fstat: %s", path);
+
+ buf = rcs_buf_alloc((size_t)st.st_size, flags);
+ for (bp = buf->cb_cur; ; bp += (size_t)ret) {
+ len = SIZE_LEFT(buf);
+ ret = read(fd, bp, len);
+ if (ret == -1) {
+ rcs_buf_free(buf);
+ err(1, "rcs_buf_load: read: %s", strerror(errno));
+ } else if (ret == 0)
+ break;
+
+ buf->cb_len += (size_t)ret;
+ }
+
+ (void)close(fd);
+
+ return (buf);
+}
+
+/*
+ * rcs_buf_free()
+ *
+ * Free the buffer <b> and all associated data.
+ */
+void
+rcs_buf_free(BUF *b)
+{
+ if (b->cb_buf != NULL)
+ xfree(b->cb_buf);
+ xfree(b);
+}
+
+/*
+ * rcs_buf_release()
+ *
+ * Free the buffer <b>'s structural information but do not free the contents
+ * of the buffer. Instead, they are returned and should be freed later using
+ * free().
+ */
+void *
+rcs_buf_release(BUF *b)
+{
+ u_char *tmp;
+
+ tmp = b->cb_buf;
+ xfree(b);
+ return (tmp);
+}
+
+/*
+ * rcs_buf_empty()
+ *
+ * Empty the contents of the buffer <b> and reset pointers.
+ */
+void
+rcs_buf_empty(BUF *b)
+{
+ memset(b->cb_buf, 0, b->cb_size);
+ b->cb_cur = b->cb_buf;
+ b->cb_len = 0;
+}
+
+/*
+ * rcs_buf_set()
+ *
+ * Set the contents of the buffer <b> at offset <off> to the first <len>
+ * bytes of data found at <src>. If the buffer was not created with
+ * BUF_AUTOEXT, as many bytes as possible will be copied in the buffer.
+ */
+ssize_t
+rcs_buf_set(BUF *b, const void *src, size_t len, size_t off)
+{
+ size_t rlen = 0;
+
+ if (b->cb_size < (len + off)) {
+ if ((b->cb_flags & BUF_AUTOEXT)) {
+ rcs_buf_grow(b, len + off - b->cb_size);
+ rlen = len + off;
+ } else {
+ rlen = b->cb_size - off;
+ }
+ } else {
+ rlen = len;
+ }
+
+ b->cb_len = rlen;
+ memcpy((b->cb_buf + off), src, rlen);
+
+ if (b->cb_len == 0) {
+ b->cb_cur = b->cb_buf + off;
+ b->cb_len = rlen;
+ }
+
+ return (rlen);
+}
+
+/*
+ * rcs_buf_putc()
+ *
+ * Append a single character <c> to the end of the buffer <b>.
+ */
+void
+rcs_buf_putc(BUF *b, int c)
+{
+ u_char *bp;
+
+ bp = b->cb_cur + b->cb_len;
+ if (bp == (b->cb_buf + b->cb_size)) {
+ /* extend */
+ if (b->cb_flags & BUF_AUTOEXT)
+ rcs_buf_grow(b, (size_t)BUF_INCR);
+ else
+ errx(1, "rcs_buf_putc failed");
+
+ /* the buffer might have been moved */
+ bp = b->cb_cur + b->cb_len;
+ }
+ *bp = (u_char)c;
+ b->cb_len++;
+}
+
+/*
+ * rcs_buf_getc()
+ *
+ * Return u_char at buffer position <pos>.
+ *
+ */
+u_char
+rcs_buf_getc(BUF *b, size_t pos)
+{
+ return (b->cb_cur[pos]);
+}
+
+/*
+ * rcs_buf_append()
+ *
+ * Append <len> bytes of data pointed to by <data> to the buffer <b>. If the
+ * buffer is too small to accept all data, it will attempt to append as much
+ * data as possible, or if the BUF_AUTOEXT flag is set for the buffer, it
+ * will get resized to an appropriate size to accept all data.
+ * Returns the number of bytes successfully appended to the buffer.
+ */
+ssize_t
+rcs_buf_append(BUF *b, const void *data, size_t len)
+{
+ size_t left, rlen;
+ u_char *bp, *bep;
+
+ bp = b->cb_cur + b->cb_len;
+ bep = b->cb_buf + b->cb_size;
+ left = bep - bp;
+ rlen = len;
+
+ if (left < len) {
+ if (b->cb_flags & BUF_AUTOEXT) {
+ rcs_buf_grow(b, len - left);
+ bp = b->cb_cur + b->cb_len;
+ } else
+ rlen = bep - bp;
+ }
+
+ memcpy(bp, data, rlen);
+ b->cb_len += rlen;
+
+ return (rlen);
+}
+
+/*
+ * rcs_buf_fappend()
+ *
+ */
+ssize_t
+rcs_buf_fappend(BUF *b, const char *fmt, ...)
+{
+ ssize_t ret;
+ char *str;
+ va_list vap;
+
+ va_start(vap, fmt);
+ ret = vasprintf(&str, fmt, vap);
+ va_end(vap);
+
+ if (ret == -1)
+ errx(1, "rcs_buf_fappend: failed to format data");
+
+ ret = rcs_buf_append(b, str, (size_t)ret);
+ xfree(str);
+ return (ret);
+}
+
+/*
+ * rcs_buf_len()
+ *
+ * Returns the size of the buffer that is being used.
+ */
+size_t
+rcs_buf_len(BUF *b)
+{
+ return (b->cb_len);
+}
+
+/*
+ * rcs_buf_write_fd()
+ *
+ * Write the contents of the buffer <b> to the specified <fd>
+ */
+int
+rcs_buf_write_fd(BUF *b, int fd)
+{
+ u_char *bp;
+ size_t len;
+ ssize_t ret;
+
+ len = b->cb_len;
+ bp = b->cb_cur;
+
+ do {
+ ret = write(fd, bp, len);
+ if (ret == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return (-1);
+ }
+
+ len -= (size_t)ret;
+ bp += (size_t)ret;
+ } while (len > 0);
+
+ return (0);
+}
+
+/*
+ * rcs_buf_write()
+ *
+ * Write the contents of the buffer <b> to the file whose path is given in
+ * <path>. If the file does not exist, it is created with mode <mode>.
+ */
+int
+rcs_buf_write(BUF *b, const char *path, mode_t mode)
+{
+ int fd;
+ open:
+ if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, mode)) == -1) {
+ if (errno == EACCES && unlink(path) != -1)
+ goto open;
+ else
+ err(1, "open: `%s'", path);
+ }
+
+ if (rcs_buf_write_fd(b, fd) == -1) {
+ (void)unlink(path);
+ errx(1, "rcs_buf_write: rcs_buf_write_fd: `%s'", path);
+ }
+
+ if (fchmod(fd, mode) < 0)
+ warn("permissions not set on file %s", path);
+
+ (void)close(fd);
+
+ return (0);
+}
+
+/*
+ * rcs_buf_write_stmp()
+ *
+ * Write the contents of the buffer <b> to a temporary file whose path is
+ * specified using <template> (see mkstemp.3). NB. This function will modify
+ * <template>, as per mkstemp
+ */
+void
+rcs_buf_write_stmp(BUF *b, char *template, mode_t mode)
+{
+ int fd;
+
+ if ((fd = mkstemp(template)) == -1)
+ err(1, "mkstemp: `%s'", template);
+
+#if defined(RCSPROG)
+ rcs_worklist_add(template, &rcs_temp_files);
+#endif
+
+ if (rcs_buf_write_fd(b, fd) == -1) {
+ (void)unlink(template);
+ errx(1, "rcs_buf_write_stmp: rcs_buf_write_fd: `%s'", template);
+ }
+
+ if (fchmod(fd, mode) < 0)
+ warn("permissions not set on temporary file %s",
+ template);
+
+ (void)close(fd);
+}
+
+/*
+ * rcs_buf_grow()
+ *
+ * Grow the buffer <b> by <len> bytes. The contents are unchanged by this
+ * operation regardless of the result.
+ */
+static void
+rcs_buf_grow(BUF *b, size_t len)
+{
+ void *tmp;
+ size_t diff;
+
+ diff = b->cb_cur - b->cb_buf;
+ tmp = xrealloc(b->cb_buf, 1, b->cb_size + len);
+ b->cb_buf = tmp;
+ b->cb_size += len;
+
+ /* readjust pointers in case the buffer moved in memory */
+ b->cb_cur = b->cb_buf + diff;
+}
diff --git a/usr.bin/rcs/buf.h b/usr.bin/rcs/buf.h
new file mode 100644
index 00000000000..55255991b39
--- /dev/null
+++ b/usr.bin/rcs/buf.h
@@ -0,0 +1,64 @@
+/* $OpenBSD: buf.h,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2003 Jean-Francois Brousseau <jfb@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ *
+ * Buffer management
+ * -----------------
+ *
+ * This code provides an API to generic memory buffer management. All
+ * operations are performed on a rcs_buf structure, which is kept opaque to the
+ * API user in order to avoid corruption of the fields and make sure that only
+ * the internals can modify the fields.
+ *
+ * The first step is to allocate a new buffer using the rcs_buf_create()
+ * function, which returns a pointer to a new buffer.
+ */
+
+#ifndef BUF_H
+#define BUF_H
+
+/* flags */
+#define BUF_AUTOEXT 1 /* autoextend on append */
+
+typedef struct rcs_buf BUF;
+
+BUF *rcs_buf_alloc(size_t, u_int);
+BUF *rcs_buf_load(const char *, u_int);
+void rcs_buf_free(BUF *);
+void *rcs_buf_release(BUF *);
+u_char rcs_buf_getc(BUF *, size_t);
+void rcs_buf_empty(BUF *);
+ssize_t rcs_buf_set(BUF *, const void *, size_t, size_t);
+ssize_t rcs_buf_append(BUF *, const void *, size_t);
+ssize_t rcs_buf_fappend(BUF *, const char *, ...)
+ __attribute__((format(printf, 2, 3)));
+void rcs_buf_putc(BUF *, int);
+size_t rcs_buf_len(BUF *);
+int rcs_buf_write_fd(BUF *, int);
+int rcs_buf_write(BUF *, const char *, mode_t);
+void rcs_buf_write_stmp(BUF *, char *, mode_t);
+
+#define rcs_buf_get(b) rcs_buf_peek(b, 0)
+
+#endif /* BUF_H */
diff --git a/usr.bin/rcs/ci.c b/usr.bin/rcs/ci.c
index 4b840515f61..b2d7c2d8869 100644
--- a/usr.bin/rcs/ci.c
+++ b/usr.bin/rcs/ci.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ci.c,v 1.161 2006/04/25 13:55:49 xsa Exp $ */
+/* $OpenBSD: ci.c,v 1.162 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005, 2006 Niall O'Higgins <niallo@openbsd.org>
* All rights reserved.
@@ -120,7 +120,7 @@ checkin_main(int argc, char **argv)
case 'd':
if (rcs_optarg == NULL)
pb.date = DATE_MTIME;
- else if ((pb.date = cvs_date_parse(rcs_optarg)) <= 0)
+ else if ((pb.date = rcs_date_parse(rcs_optarg)) <= 0)
errx(1, "invalid date");
break;
case 'f':
@@ -341,7 +341,7 @@ checkin_diff_file(struct checkin_params *pb)
deltatext = NULL;
rcsnum_tostr(pb->frev, rbuf, sizeof(rbuf));
- if ((b1 = cvs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL) {
+ if ((b1 = rcs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL) {
warnx("failed to load file: `%s'", pb->filename);
goto out;
}
@@ -351,39 +351,39 @@ checkin_diff_file(struct checkin_params *pb)
goto out;
}
- if ((b3 = cvs_buf_alloc((size_t)128, BUF_AUTOEXT)) == NULL) {
+ if ((b3 = rcs_buf_alloc((size_t)128, BUF_AUTOEXT)) == NULL) {
warnx("failed to allocated buffer for diff");
goto out;
}
strlcpy(path1, rcs_tmpdir, sizeof(path1));
strlcat(path1, "/diff1.XXXXXXXXXX", sizeof(path1));
- cvs_buf_write_stmp(b1, path1, 0600);
+ rcs_buf_write_stmp(b1, path1, 0600);
- cvs_buf_free(b1);
+ rcs_buf_free(b1);
b1 = NULL;
strlcpy(path2, rcs_tmpdir, sizeof(path2));
strlcat(path2, "/diff2.XXXXXXXXXX", sizeof(path2));
- cvs_buf_write_stmp(b2, path2, 0600);
+ rcs_buf_write_stmp(b2, path2, 0600);
- cvs_buf_free(b2);
+ rcs_buf_free(b2);
b2 = NULL;
diff_format = D_RCSDIFF;
- cvs_diffreg(path1, path2, b3);
+ rcs_diffreg(path1, path2, b3);
- cvs_buf_putc(b3, '\0');
- deltatext = (char *)cvs_buf_release(b3);
+ rcs_buf_putc(b3, '\0');
+ deltatext = (char *)rcs_buf_release(b3);
b3 = NULL;
out:
if (b1 != NULL)
- cvs_buf_free(b1);
+ rcs_buf_free(b1);
if (b2 != NULL)
- cvs_buf_free(b2);
+ rcs_buf_free(b2);
if (b3 != NULL)
- cvs_buf_free(b3);
+ rcs_buf_free(b3);
return (deltatext);
}
@@ -445,11 +445,11 @@ checkin_update(struct checkin_params *pb)
pb->frev = pb->file->rf_head;
/* Load file contents */
- if ((bp = cvs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL)
+ if ((bp = rcs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL)
goto fail;
- cvs_buf_putc(bp, '\0');
- filec = (char *)cvs_buf_release(bp);
+ rcs_buf_putc(bp, '\0');
+ filec = (char *)rcs_buf_release(bp);
/* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */
if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev))
@@ -622,11 +622,11 @@ checkin_init(struct checkin_params *pb)
}
/* Load file contents */
- if ((bp = cvs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL)
+ if ((bp = rcs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL)
goto fail;
- cvs_buf_putc(bp, '\0');
- filec = (char *)cvs_buf_release(bp);
+ rcs_buf_putc(bp, '\0');
+ filec = (char *)rcs_buf_release(bp);
/* Get default values from working copy if -k specified */
if (pb->flags & CI_KEYWORDSCAN)
@@ -977,7 +977,7 @@ checkin_parsekeyword(char *keystring, RCSNUM **rev, time_t *date,
strlcpy(datestring, tokens[3], len);
strlcat(datestring, " ", len);
strlcat(datestring, tokens[4], len);
- if ((*date = cvs_date_parse(datestring)) <= 0)
+ if ((*date = rcs_date_parse(datestring)) <= 0)
errx(1, "could not parse date");
xfree(datestring);
break;
@@ -1002,7 +1002,7 @@ checkin_parsekeyword(char *keystring, RCSNUM **rev, time_t *date,
strlcpy(datestring, tokens[1], len);
strlcat(datestring, " ", len);
strlcat(datestring, tokens[2], len);
- if ((*date = cvs_date_parse(datestring)) <= 0)
+ if ((*date = rcs_date_parse(datestring)) <= 0)
errx(1, "could not parse date");
xfree(datestring);
break;
diff --git a/usr.bin/rcs/co.c b/usr.bin/rcs/co.c
index b07321ba228..300076eb53d 100644
--- a/usr.bin/rcs/co.c
+++ b/usr.bin/rcs/co.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: co.c,v 1.84 2006/04/25 13:36:35 xsa Exp $ */
+/* $OpenBSD: co.c,v 1.85 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005 Joris Vink <joris@openbsd.org>
* All rights reserved.
@@ -252,7 +252,7 @@ checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
rcsdate = givendate = -1;
if (date != NULL)
- givendate = cvs_date_parse(date);
+ givendate = rcs_date_parse(date);
if (file->rf_ndelta == 0)
printf("no revisions present; generating empty revision 0.0\n");
@@ -292,7 +292,7 @@ checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) {
if (date != NULL) {
fdate = asctime(&rdp->rd_date);
- rcsdate = cvs_date_parse(fdate);
+ rcsdate = rcs_date_parse(fdate);
if (givendate <= rcsdate)
continue;
}
@@ -352,7 +352,7 @@ checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
return (-1);
}
} else {
- bp = cvs_buf_alloc(1, 0);
+ bp = rcs_buf_alloc(1, 0);
}
/*
@@ -444,7 +444,7 @@ checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
", and you do not own it");
printf("remove it? [ny](n): ");
/* default is n */
- if (cvs_yesno() == -1) {
+ if (rcs_yesno() == -1) {
if (!(flags & QUIET) && isatty(STDIN_FILENO))
warnx("writable %s exists; "
"checkout aborted", dst);
@@ -455,17 +455,17 @@ checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
}
if (flags & PIPEOUT) {
- cvs_buf_putc(bp, '\0');
- content = cvs_buf_release(bp);
+ rcs_buf_putc(bp, '\0');
+ content = rcs_buf_release(bp);
printf("%s", content);
xfree(content);
} else {
- if (cvs_buf_write(bp, dst, mode) < 0) {
+ if (rcs_buf_write(bp, dst, mode) < 0) {
warnx("failed to write revision to file");
- cvs_buf_free(bp);
+ rcs_buf_free(bp);
return (-1);
}
- cvs_buf_free(bp);
+ rcs_buf_free(bp);
if (flags & CO_REVDATE) {
struct timeval tv[2];
memset(&tv, 0, sizeof(tv));
diff --git a/usr.bin/rcs/date.y b/usr.bin/rcs/date.y
new file mode 100644
index 00000000000..2f7f4a43ea7
--- /dev/null
+++ b/usr.bin/rcs/date.y
@@ -0,0 +1,922 @@
+%{
+/* $OpenBSD: date.y,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+
+/*
+** Originally written by Steven M. Bellovin <smb@research.att.com> while
+** at the University of North Carolina at Chapel Hill. Later tweaked by
+** a couple of people on Usenet. Completely overhauled by Rich $alz
+** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
+**
+** This grammar has 10 shift/reduce conflicts.
+**
+** This code is in the public domain and has no copyright.
+*/
+/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
+/* SUPPRESS 288 on yyerrlab *//* Label unused */
+
+#include "includes.h"
+
+#include "rcsprog.h"
+
+#define YEAR_EPOCH 1970
+#define YEAR_TMORIGIN 1900
+#define HOUR(x) ((time_t)(x) * 60)
+#define SECSPERDAY (24L * 60L * 60L)
+
+
+/* An entry in the lexical lookup table */
+typedef struct _TABLE {
+ char *name;
+ int type;
+ time_t value;
+} TABLE;
+
+
+/* Daylight-savings mode: on, off, or not yet known. */
+typedef enum _DSTMODE {
+ DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/* Meridian: am, pm, or 24-hour style. */
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+ * Global variables. We could get rid of most of these by using a good
+ * union as the yacc stack. (This routine was originally written before
+ * yacc had the %union construct.) Maybe someday; right now we only use
+ * the %union very rarely.
+ */
+static const char *yyInput;
+static DSTMODE yyDSTmode;
+static time_t yyDayOrdinal;
+static time_t yyDayNumber;
+static int yyHaveDate;
+static int yyHaveDay;
+static int yyHaveRel;
+static int yyHaveTime;
+static int yyHaveZone;
+static time_t yyTimezone;
+static time_t yyDay;
+static time_t yyHour;
+static time_t yyMinutes;
+static time_t yyMonth;
+static time_t yySeconds;
+static time_t yyYear;
+static MERIDIAN yyMeridian;
+static time_t yyRelMonth;
+static time_t yyRelSeconds;
+
+
+static int yyerror (const char *);
+static int yylex (void);
+static int yyparse (void);
+static int lookup (char *);
+
+%}
+
+%union {
+ time_t Number;
+ enum _MERIDIAN Meridian;
+}
+
+%token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
+%token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
+
+%type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
+%type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
+%type <Meridian> tMERIDIAN o_merid
+
+%%
+
+spec : /* NULL */
+ | spec item
+ ;
+
+item : time {
+ yyHaveTime++;
+ }
+ | zone {
+ yyHaveZone++;
+ }
+ | date {
+ yyHaveDate++;
+ }
+ | day {
+ yyHaveDay++;
+ }
+ | rel {
+ yyHaveRel++;
+ }
+ | number
+ ;
+
+time : tUNUMBER tMERIDIAN {
+ yyHour = $1;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = 0;
+ yyMeridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER tSNUMBER {
+ yyHour = $1;
+ yyMinutes = $3;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
+ }
+ ;
+
+zone : tZONE {
+ yyTimezone = $1;
+ yyDSTmode = DSToff;
+ }
+ | tDAYZONE {
+ yyTimezone = $1;
+ yyDSTmode = DSTon;
+ }
+ | tZONE tDST {
+ yyTimezone = $1;
+ yyDSTmode = DSTon;
+ }
+ ;
+
+day : tDAY {
+ yyDayOrdinal = 1;
+ yyDayNumber = $1;
+ }
+ | tDAY ',' {
+ yyDayOrdinal = 1;
+ yyDayNumber = $1;
+ }
+ | tUNUMBER tDAY {
+ yyDayOrdinal = $1;
+ yyDayNumber = $2;
+ }
+ ;
+
+date : tUNUMBER '/' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $3;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ if ($1 >= 100) {
+ yyYear = $1;
+ yyMonth = $3;
+ yyDay = $5;
+ } else {
+ yyMonth = $1;
+ yyDay = $3;
+ yyYear = $5;
+ }
+ }
+ | tUNUMBER tSNUMBER tSNUMBER {
+ /* ISO 8601 format. yyyy-mm-dd. */
+ yyYear = $1;
+ yyMonth = -$2;
+ yyDay = -$3;
+ }
+ | tUNUMBER tMONTH tSNUMBER {
+ /* e.g. 17-JUN-1992. */
+ yyDay = $1;
+ yyMonth = $2;
+ yyYear = -$3;
+ }
+ | tMONTH tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ yyYear = $4;
+ }
+ | tUNUMBER tMONTH {
+ yyMonth = $2;
+ yyDay = $1;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ yyMonth = $2;
+ yyDay = $1;
+ yyYear = $3;
+ }
+ ;
+
+rel : relunit tAGO {
+ yyRelSeconds = -yyRelSeconds;
+ yyRelMonth = -yyRelMonth;
+ }
+ | relunit
+ ;
+
+relunit : tUNUMBER tMINUTE_UNIT {
+ yyRelSeconds += $1 * $2 * 60L;
+ }
+ | tSNUMBER tMINUTE_UNIT {
+ yyRelSeconds += $1 * $2 * 60L;
+ }
+ | tMINUTE_UNIT {
+ yyRelSeconds += $1 * 60L;
+ }
+ | tSNUMBER tSEC_UNIT {
+ yyRelSeconds += $1;
+ }
+ | tUNUMBER tSEC_UNIT {
+ yyRelSeconds += $1;
+ }
+ | tSEC_UNIT {
+ yyRelSeconds++;
+ }
+ | tSNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tUNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tMONTH_UNIT {
+ yyRelMonth += $1;
+ }
+ ;
+
+number : tUNUMBER {
+ if (yyHaveTime && yyHaveDate && !yyHaveRel)
+ yyYear = $1;
+ else {
+ if ($1 > 10000) {
+ yyHaveDate++;
+ yyDay= ($1)%100;
+ yyMonth= ($1/100)%100;
+ yyYear = $1/10000;
+ } else {
+ yyHaveTime++;
+ if ($1 < 100) {
+ yyHour = $1;
+ yyMinutes = 0;
+ } else {
+ yyHour = $1 / 100;
+ yyMinutes = $1 % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = MER24;
+ }
+ }
+ }
+ ;
+
+o_merid : /* NULL */ {
+ $$ = MER24;
+ }
+ | tMERIDIAN {
+ $$ = $1;
+ }
+ ;
+
+%%
+
+/* Month and day table. */
+static TABLE const MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "sept", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 1 },
+ { "tuesday", tDAY, 2 },
+ { "tues", tDAY, 2 },
+ { "wednesday", tDAY, 3 },
+ { "wednes", tDAY, 3 },
+ { "thursday", tDAY, 4 },
+ { "thur", tDAY, 4 },
+ { "thurs", tDAY, 4 },
+ { "friday", tDAY, 5 },
+ { "saturday", tDAY, 6 },
+ { NULL }
+};
+
+/* Time units table. */
+static TABLE const UnitsTable[] = {
+ { "year", tMONTH_UNIT, 12 },
+ { "month", tMONTH_UNIT, 1 },
+ { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
+ { "week", tMINUTE_UNIT, 7 * 24 * 60 },
+ { "day", tMINUTE_UNIT, 1 * 24 * 60 },
+ { "hour", tMINUTE_UNIT, 60 },
+ { "minute", tMINUTE_UNIT, 1 },
+ { "min", tMINUTE_UNIT, 1 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+ { NULL }
+};
+
+/* Assorted relative-time words. */
+static TABLE const OtherTable[] = {
+ { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
+ { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
+ { "today", tMINUTE_UNIT, 0 },
+ { "now", tMINUTE_UNIT, 0 },
+ { "last", tUNUMBER, -1 },
+ { "this", tMINUTE_UNIT, 0 },
+ { "next", tUNUMBER, 2 },
+ { "first", tUNUMBER, 1 },
+/* { "second", tUNUMBER, 2 }, */
+ { "third", tUNUMBER, 3 },
+ { "fourth", tUNUMBER, 4 },
+ { "fifth", tUNUMBER, 5 },
+ { "sixth", tUNUMBER, 6 },
+ { "seventh", tUNUMBER, 7 },
+ { "eighth", tUNUMBER, 8 },
+ { "ninth", tUNUMBER, 9 },
+ { "tenth", tUNUMBER, 10 },
+ { "eleventh", tUNUMBER, 11 },
+ { "twelfth", tUNUMBER, 12 },
+ { "ago", tAGO, 1 },
+ { NULL }
+};
+
+/* The timezone table. */
+/* Some of these are commented out because a time_t can't store a float. */
+static TABLE const TimezoneTable[] = {
+ { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
+ { "utc", tZONE, HOUR( 0) },
+ { "wet", tZONE, HOUR( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
+ { "wat", tZONE, HOUR( 1) }, /* West Africa */
+ { "at", tZONE, HOUR( 2) }, /* Azores */
+#if 0
+ /* For completeness. BST is also British Summer, and GST is
+ * also Guam Standard. */
+ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
+ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
+#endif
+#if 0
+ { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
+ { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
+#endif
+ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
+ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
+ { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
+ { "cat", tZONE, HOUR(10) }, /* Central Alaska */
+ { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
+ { "nt", tZONE, HOUR(11) }, /* Nome */
+ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
+ { "cet", tZONE, -HOUR(1) }, /* Central European */
+ { "met", tZONE, -HOUR(1) }, /* Middle European */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR(1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
+ { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
+ { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
+#if 0
+ { "it", tZONE, -HOUR(3.5) },/* Iran */
+#endif
+ { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
+#if 0
+ { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
+#endif
+ { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
+#if 0
+ /* For completeness. NST is also Newfoundland Stanard, and SST is
+ * also Swedish Summer. */
+ { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
+ { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
+#endif /* 0 */
+ { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
+#if 0
+ { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
+#endif
+ { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
+ { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
+#if 0
+ { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
+ { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
+#endif
+ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
+ { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
+ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
+ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
+ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
+ { NULL }
+};
+
+/* Military timezone table. */
+static TABLE const MilitaryTable[] = {
+ { "a", tZONE, HOUR( 1) },
+ { "b", tZONE, HOUR( 2) },
+ { "c", tZONE, HOUR( 3) },
+ { "d", tZONE, HOUR( 4) },
+ { "e", tZONE, HOUR( 5) },
+ { "f", tZONE, HOUR( 6) },
+ { "g", tZONE, HOUR( 7) },
+ { "h", tZONE, HOUR( 8) },
+ { "i", tZONE, HOUR( 9) },
+ { "k", tZONE, HOUR( 10) },
+ { "l", tZONE, HOUR( 11) },
+ { "m", tZONE, HOUR( 12) },
+ { "n", tZONE, HOUR(- 1) },
+ { "o", tZONE, HOUR(- 2) },
+ { "p", tZONE, HOUR(- 3) },
+ { "q", tZONE, HOUR(- 4) },
+ { "r", tZONE, HOUR(- 5) },
+ { "s", tZONE, HOUR(- 6) },
+ { "t", tZONE, HOUR(- 7) },
+ { "u", tZONE, HOUR(- 8) },
+ { "v", tZONE, HOUR(- 9) },
+ { "w", tZONE, HOUR(-10) },
+ { "x", tZONE, HOUR(-11) },
+ { "y", tZONE, HOUR(-12) },
+ { "z", tZONE, HOUR( 0) },
+ { NULL }
+};
+
+
+static int
+yyerror(const char *s)
+{
+ char *str;
+ int n;
+
+ if (isspace(yyInput[0]) || !isprint(yyInput[0]))
+ n = asprintf(&str, "%s: unexpected char 0x%02x in date string",
+ s, yyInput[0]);
+ else
+ n = asprintf(&str, "%s: unexpected %s in date string",
+ s, yyInput);
+ if (n == -1)
+ return (0);
+
+#if defined(TEST)
+ printf("%s", str);
+#else
+ warnx("%s", str);
+#endif
+ free(str);
+ return (0);
+}
+
+
+static time_t
+ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+ if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
+ return (-1);
+
+ switch (Meridian) {
+ case MER24:
+ if (Hours < 0 || Hours > 23)
+ return (-1);
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+ case MERam:
+ if (Hours < 1 || Hours > 12)
+ return (-1);
+ if (Hours == 12)
+ Hours = 0;
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+ case MERpm:
+ if (Hours < 1 || Hours > 12)
+ return (-1);
+ if (Hours == 12)
+ Hours = 0;
+ return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
+ default:
+ abort();
+ }
+ /* NOTREACHED */
+}
+
+
+/* Year is either
+ * A negative number, which means to use its absolute value (why?)
+ * A number from 0 to 99, which means a year from 1900 to 1999, or
+ * The actual year (>=100).
+ */
+static time_t
+Convert(time_t Month, time_t Day, time_t Year, time_t Hours, time_t Minutes,
+ time_t Seconds, MERIDIAN Meridian, DSTMODE DSTmode)
+{
+ static int DaysInMonth[12] = {
+ 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ time_t tod;
+ time_t julian;
+ int i;
+
+ if (Year < 0)
+ Year = -Year;
+ if (Year < 69)
+ Year += 2000;
+ else if (Year < 100) {
+ Year += 1900;
+ if (Year < YEAR_EPOCH)
+ Year += 100;
+ }
+ DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
+ ? 29 : 28;
+ /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
+ I'm too lazy to try to check for time_t overflow in another way. */
+ if (Year < YEAR_EPOCH || Year > 2038 || Month < 1 || Month > 12 ||
+ /* Lint fluff: "conversion from long may lose accuracy" */
+ Day < 1 || Day > DaysInMonth[(int)--Month])
+ return (-1);
+
+ for (julian = Day - 1, i = 0; i < Month; i++)
+ julian += DaysInMonth[i];
+
+ for (i = YEAR_EPOCH; i < Year; i++)
+ julian += 365 + (i % 4 == 0);
+ julian *= SECSPERDAY;
+ julian += yyTimezone * 60L;
+
+ if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+ return (-1);
+ julian += tod;
+ if ((DSTmode == DSTon) ||
+ (DSTmode == DSTmaybe && localtime(&julian)->tm_isdst))
+ julian -= 60 * 60;
+ return (julian);
+}
+
+
+static time_t
+DSTcorrect(time_t Start, time_t Future)
+{
+ time_t StartDay;
+ time_t FutureDay;
+
+ StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+ FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+ return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
+}
+
+
+static time_t
+RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber)
+{
+ struct tm *tm;
+ time_t now;
+
+ now = Start;
+ tm = localtime(&now);
+ now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
+ now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
+ return DSTcorrect(Start, now);
+}
+
+
+static time_t
+RelativeMonth(time_t Start, time_t RelMonth)
+{
+ struct tm *tm;
+ time_t Month;
+ time_t Year;
+
+ if (RelMonth == 0)
+ return (0);
+ tm = localtime(&Start);
+ Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
+ Year = Month / 12;
+ Month = Month % 12 + 1;
+ return DSTcorrect(Start,
+ Convert(Month, (time_t)tm->tm_mday, Year,
+ (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+ MER24, DSTmaybe));
+}
+
+
+static int
+lookup(char *buff)
+{
+ char *p, *q;
+ int i, abbrev;
+ const TABLE *tp;
+
+ /* Make it lowercase. */
+ for (p = buff; *p; p++)
+ if (isupper(*p))
+ *p = tolower(*p);
+
+ if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
+ yylval.Meridian = MERam;
+ return (tMERIDIAN);
+ }
+ if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
+ yylval.Meridian = MERpm;
+ return (tMERIDIAN);
+ }
+
+ /* See if we have an abbreviation for a month. */
+ if (strlen(buff) == 3)
+ abbrev = 1;
+ else if (strlen(buff) == 4 && buff[3] == '.') {
+ abbrev = 1;
+ buff[3] = '\0';
+ } else
+ abbrev = 0;
+
+ for (tp = MonthDayTable; tp->name; tp++) {
+ if (abbrev) {
+ if (strncmp(buff, tp->name, 3) == 0) {
+ yylval.Number = tp->value;
+ return (tp->type);
+ }
+ } else if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return (tp->type);
+ }
+ }
+
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return (tp->type);
+ }
+
+ if (strcmp(buff, "dst") == 0)
+ return (tDST);
+
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return (tp->type);
+ }
+
+ /* Strip off any plural and try the units table again. */
+ i = strlen(buff) - 1;
+ if (buff[i] == 's') {
+ buff[i] = '\0';
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return (tp->type);
+ }
+ buff[i] = 's'; /* Put back for "this" in OtherTable. */
+ }
+
+ for (tp = OtherTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return (tp->type);
+ }
+
+ /* Military timezones. */
+ if (buff[1] == '\0' && isalpha(*buff)) {
+ for (tp = MilitaryTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return (tp->type);
+ }
+ }
+
+ /* Drop out any periods and try the timezone table again. */
+ for (i = 0, p = q = buff; *q; q++)
+ if (*q != '.')
+ *p++ = *q;
+ else
+ i++;
+ *p = '\0';
+ if (i)
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return (tp->type);
+ }
+
+ return (tID);
+}
+
+
+static int
+yylex(void)
+{
+ char c, *p, buff[20];
+ int count, sign;
+
+ for (;;) {
+ while (isspace(*yyInput))
+ yyInput++;
+
+ if (isdigit(c = *yyInput) || c == '-' || c == '+') {
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ if (!isdigit(*++yyInput))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+
+ for (yylval.Number = 0; isdigit(c = *yyInput++); )
+ yylval.Number = 10 * yylval.Number + c - '0';
+ yyInput--;
+ if (sign < 0)
+ yylval.Number = -yylval.Number;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+
+ if (isalpha(c)) {
+ for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
+ if (p < &buff[sizeof buff - 1])
+ *p++ = c;
+ *p = '\0';
+ yyInput--;
+ return lookup(buff);
+ }
+ if (c != '(')
+ return *yyInput++;
+
+ count = 0;
+ do {
+ c = *yyInput++;
+ if (c == '\0')
+ return (c);
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ } while (count > 0);
+ }
+}
+
+/* Yield A - B, measured in seconds. */
+static long
+difftm(struct tm *a, struct tm *b)
+{
+ int ay = a->tm_year + (YEAR_TMORIGIN - 1);
+ int by = b->tm_year + (YEAR_TMORIGIN - 1);
+ int days = (
+ /* difference in day of year */
+ a->tm_yday - b->tm_yday
+ /* + intervening leap days */
+ + ((ay >> 2) - (by >> 2))
+ - (ay/100 - by/100)
+ + ((ay/100 >> 2) - (by/100 >> 2))
+ /* + difference in years * 365 */
+ + (long)(ay-by) * 365);
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min)) + (a->tm_sec - b->tm_sec));
+}
+
+/*
+ * rcs_date_parse()
+ *
+ * Returns the number of seconds since the Epoch corresponding to the date.
+ */
+time_t
+rcs_date_parse(const char *p)
+{
+ struct tm *tm, gmt;
+ struct timeb ftz, *now;
+ time_t Start, tod, nowtime;
+
+ now = NULL;
+
+ yyInput = p;
+ if (now == NULL) {
+ struct tm *gmt_ptr;
+
+ now = &ftz;
+ (void)time(&nowtime);
+
+ gmt_ptr = gmtime(&nowtime);
+ if (gmt_ptr != NULL) {
+ /* Make a copy, in case localtime modifies *tm (I think
+ * that comment now applies to *gmt_ptr, but I am too
+ * lazy to dig into how gmtime and locatime allocate the
+ * structures they return pointers to).
+ */
+ gmt = *gmt_ptr;
+ }
+
+ if (!(tm = localtime(&nowtime)))
+ return (-1);
+
+ if (gmt_ptr != NULL)
+ ftz.timezone = difftm(&gmt, tm) / 60;
+
+ if (tm->tm_isdst)
+ ftz.timezone += 60;
+ }
+ else {
+ nowtime = now->time;
+ }
+
+ tm = localtime(&nowtime);
+ yyYear = tm->tm_year + 1900;
+ yyMonth = tm->tm_mon + 1;
+ yyDay = tm->tm_mday;
+ yyTimezone = now->timezone;
+ yyDSTmode = DSTmaybe;
+ yyHour = 0;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMonth = 0;
+ yyHaveDate = 0;
+ yyHaveDay = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+ yyHaveZone = 0;
+
+ if (yyparse() || yyHaveTime > 1 || yyHaveZone > 1 ||
+ yyHaveDate > 1 || yyHaveDay > 1)
+ return (-1);
+
+ if (yyHaveDate || yyHaveTime || yyHaveDay) {
+ Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes,
+ yySeconds, yyMeridian, yyDSTmode);
+ if (Start < 0)
+ return (-1);
+ } else {
+ Start = nowtime;
+ if (!yyHaveRel)
+ Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) +
+ tm->tm_sec;
+ }
+
+ Start += yyRelSeconds;
+ Start += RelativeMonth(Start, yyRelMonth);
+
+ if (yyHaveDay && !yyHaveDate) {
+ tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
+ Start += tod;
+ }
+
+ /* Have to do *something* with a legitimate -1 so it's distinguishable
+ * from the error return value. (Alternately could set errno on error.)
+ */
+ return (Start == -1) ? (0) : (Start);
+}
+
+#if defined(TEST)
+/* ARGSUSED */
+int
+main(int argc, char **argv)
+{
+ char buff[128];
+ time_t d;
+
+ (void)printf("Enter date, or blank line to exit.\n\t> ");
+ (void)fflush(stdout);
+ while (fgets(buff, sizeof(buff), stdin) && buff[0]) {
+ d = rcs_date_parse(buff);
+ if (d == -1)
+ (void)printf("Bad format - couldn't convert.\n");
+ else
+ (void)printf("%s", ctime(&d));
+ (void)printf("\t> ");
+ (void)fflush(stdout);
+ }
+
+ return (0);
+}
+#endif /* defined(TEST) */
diff --git a/usr.bin/rcs/diff.c b/usr.bin/rcs/diff.c
new file mode 100644
index 00000000000..5cf520c3bcf
--- /dev/null
+++ b/usr.bin/rcs/diff.c
@@ -0,0 +1,1394 @@
+/* $OpenBSD: diff.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (C) Caldera International Inc. 2001-2002.
+ * 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 and documentation 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed or owned by Caldera
+ * International, Inc.
+ * 4. Neither the name of Caldera International, Inc. nor the names of other
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
+ * INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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.
+ */
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2004 Jean-Francois Brousseau. 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * @(#)diffreg.c 8.1 (Berkeley) 6/6/93
+ */
+/*
+ * Uses an algorithm due to Harold Stone, which finds
+ * a pair of longest identical subsequences in the two
+ * files.
+ *
+ * The major goal is to generate the match vector J.
+ * J[i] is the index of the line in file1 corresponding
+ * to line i file0. J[i] = 0 if there is no
+ * such line in file1.
+ *
+ * Lines are hashed so as to work in core. All potential
+ * matches are located by sorting the lines of each file
+ * on the hash (called ``value''). In particular, this
+ * collects the equivalence classes in file1 together.
+ * Subroutine equiv replaces the value of each line in
+ * file0 by the index of the first element of its
+ * matching equivalence in (the reordered) file1.
+ * To save space equiv squeezes file1 into a single
+ * array member in which the equivalence classes
+ * are simply concatenated, except that their first
+ * members are flagged by changing sign.
+ *
+ * Next the indices that point into member are unsorted into
+ * array class according to the original order of file0.
+ *
+ * The cleverness lies in routine stone. This marches
+ * through the lines of file0, developing a vector klist
+ * of "k-candidates". At step i a k-candidate is a matched
+ * pair of lines x,y (x in file0 y in file1) such that
+ * there is a common subsequence of length k
+ * between the first i lines of file0 and the first y
+ * lines of file1, but there is no such subsequence for
+ * any smaller y. x is the earliest possible mate to y
+ * that occurs in such a subsequence.
+ *
+ * Whenever any of the members of the equivalence class of
+ * lines in file1 matable to a line in file0 has serial number
+ * less than the y of some k-candidate, that k-candidate
+ * with the smallest such y is replaced. The new
+ * k-candidate is chained (via pred) to the current
+ * k-1 candidate so that the actual subsequence can
+ * be recovered. When a member has serial number greater
+ * that the y of all k-candidates, the klist is extended.
+ * At the end, the longest subsequence is pulled out
+ * and placed in the array J by unravel
+ *
+ * With J in hand, the matches there recorded are
+ * check'ed against reality to assure that no spurious
+ * matches have crept in due to hashing. If they have,
+ * they are broken, and "jackpot" is recorded--a harmless
+ * matter except that a true match for a spuriously
+ * mated line may now be unnecessarily reported as a change.
+ *
+ * Much of the complexity of the program comes simply
+ * from trying to minimize core utilization and
+ * maximize the range of doable problems by dynamically
+ * allocating what is needed and reusing what is not.
+ * The core requirements for problems larger than somewhat
+ * are (in words) 2*length(file0) + length(file1) +
+ * 3*(number of k-candidates installed), typically about
+ * 6n words for files of length n.
+ */
+
+#include "includes.h"
+
+#include "buf.h"
+#include "diff.h"
+#include "xmalloc.h"
+
+struct cand {
+ int x;
+ int y;
+ int pred;
+} cand;
+
+struct line {
+ int serial;
+ int value;
+} *file[2];
+
+/*
+ * The following struct is used to record change in formation when
+ * doing a "context" or "unified" diff. (see routine "change" to
+ * understand the highly mnemonic field names)
+ */
+struct context_vec {
+ int a; /* start line in old file */
+ int b; /* end line in old file */
+ int c; /* start line in new file */
+ int d; /* end line in new file */
+};
+
+struct diff_arg {
+ char *rev1;
+ char *rev2;
+ char *date1;
+ char *date2;
+};
+
+static void output(FILE *, FILE *);
+static void check(FILE *, FILE *);
+static void range(int, int, char *);
+static void uni_range(int, int);
+static void dump_context_vec(FILE *, FILE *);
+static void dump_unified_vec(FILE *, FILE *);
+static int prepare(int, FILE *, off_t);
+static void prune(void);
+static void equiv(struct line *, int, struct line *, int, int *);
+static void unravel(int);
+static void unsort(struct line *, int, int *);
+static void change(FILE *, FILE *, int, int, int, int);
+static void sort(struct line *, int);
+static int ignoreline(char *);
+static int asciifile(FILE *);
+static void fetch(long *, int, int, FILE *, int, int);
+static int newcand(int, int, int);
+static int search(int *, int, int);
+static int skipline(FILE *);
+static int isqrt(int);
+static int stone(int *, int, int *, int *);
+static int readhash(FILE *);
+static int files_differ(FILE *, FILE *);
+static char *match_function(const long *, int, FILE *);
+static char *preadline(int, size_t, off_t);
+
+
+static int aflag, bflag, dflag, iflag, pflag, tflag, Tflag, wflag;
+static int context = 3;
+int diff_format = D_NORMAL;
+char *diff_file = NULL;
+RCSNUM *diff_rev1 = NULL;
+RCSNUM *diff_rev2 = NULL;
+char diffargs[128];
+static struct stat stb1, stb2;
+static char *ifdefname, *ignore_pats;
+regex_t ignore_re;
+
+static int *J; /* will be overlaid on class */
+static int *class; /* will be overlaid on file[0] */
+static int *klist; /* will be overlaid on file[0] after class */
+static int *member; /* will be overlaid on file[1] */
+static int clen;
+static int inifdef; /* whether or not we are in a #ifdef block */
+static int diff_len[2];
+static int pref, suff; /* length of prefix and suffix */
+static int slen[2];
+static int anychange;
+static long *ixnew; /* will be overlaid on file[1] */
+static long *ixold; /* will be overlaid on klist */
+static struct cand *clist; /* merely a free storage pot for candidates */
+static int clistlen; /* the length of clist */
+static struct line *sfile[2]; /* shortened by pruning common prefix/suffix */
+static u_char *chrtran; /* translation table for case-folding */
+static struct context_vec *context_vec_start;
+static struct context_vec *context_vec_end;
+static struct context_vec *context_vec_ptr;
+
+#define FUNCTION_CONTEXT_SIZE 41
+static char lastbuf[FUNCTION_CONTEXT_SIZE];
+static int lastline;
+static int lastmatchline;
+BUF *diffbuf = NULL;
+
+/*
+ * chrtran points to one of 2 translation tables: cup2low if folding upper to
+ * lower case clow2low if not folding case
+ */
+u_char clow2low[256] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+ 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+ 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+ 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41,
+ 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c,
+ 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62,
+ 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d,
+ 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83,
+ 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
+ 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+ 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4,
+ 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
+ 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5,
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0,
+ 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
+ 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
+ 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1,
+ 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc,
+ 0xfd, 0xfe, 0xff
+};
+
+u_char cup2low[256] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+ 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+ 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+ 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x60, 0x61,
+ 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c,
+ 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x60, 0x61, 0x62,
+ 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d,
+ 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83,
+ 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
+ 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+ 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4,
+ 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
+ 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5,
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0,
+ 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
+ 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
+ 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1,
+ 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc,
+ 0xfd, 0xfe, 0xff
+};
+
+int
+rcs_diffreg(const char *file1, const char *file2, BUF *out)
+{
+ FILE *f1, *f2;
+ int i, rval;
+ void *tmp;
+
+ f1 = f2 = NULL;
+ rval = D_SAME;
+ anychange = 0;
+ lastline = 0;
+ lastmatchline = 0;
+ context_vec_ptr = context_vec_start - 1;
+ chrtran = (iflag ? cup2low : clow2low);
+ if (out != NULL)
+ diffbuf = out;
+
+ f1 = fopen(file1, "r");
+ if (f1 == NULL) {
+ warn("%s", file1);
+ goto closem;
+ }
+
+ f2 = fopen(file2, "r");
+ if (f2 == NULL) {
+ warn("%s", file2);
+ goto closem;
+ }
+
+ if (stat(file1, &stb1) < 0) {
+ warn("%s", file1);
+ goto closem;
+ }
+
+ if (stat(file2, &stb2) < 0) {
+ warn("%s", file2);
+ goto closem;
+ }
+
+ switch (files_differ(f1, f2)) {
+ case 0:
+ goto closem;
+ case 1:
+ break;
+ default:
+ /* error */
+ goto closem;
+ }
+
+ if (!asciifile(f1) || !asciifile(f2)) {
+ rval = D_BINARY;
+ goto closem;
+ }
+
+ if (prepare(0, f1, stb1.st_size) < 0 ||
+ prepare(1, f2, stb2.st_size) < 0) {
+ goto closem;
+ }
+
+ prune();
+ sort(sfile[0], slen[0]);
+ sort(sfile[1], slen[1]);
+
+ member = (int *)file[1];
+ equiv(sfile[0], slen[0], sfile[1], slen[1], member);
+ tmp = xrealloc(member, slen[1] + 2, sizeof(*member));
+ member = tmp;
+
+ class = (int *)file[0];
+ unsort(sfile[0], slen[0], class);
+ tmp = xrealloc(class, slen[0] + 2, sizeof(*class));
+ class = tmp;
+
+ klist = xcalloc(slen[0] + 2, sizeof(*klist));
+ clen = 0;
+ clistlen = 100;
+ clist = xcalloc(clistlen, sizeof(*clist));
+
+ if ((i = stone(class, slen[0], member, klist)) < 0)
+ goto closem;
+
+ xfree(member);
+ xfree(class);
+
+ tmp = xrealloc(J, diff_len[0] + 2, sizeof(*J));
+ J = tmp;
+ unravel(klist[i]);
+ xfree(clist);
+ xfree(klist);
+
+ tmp = xrealloc(ixold, diff_len[0] + 2, sizeof(*ixold));
+ ixold = tmp;
+
+ tmp = xrealloc(ixnew, diff_len[1] + 2, sizeof(*ixnew));
+ ixnew = tmp;
+ check(f1, f2);
+ output(f1, f2);
+
+closem:
+ if (anychange == 1) {
+ if (rval == D_SAME)
+ rval = D_DIFFER;
+ }
+ if (f1 != NULL)
+ fclose(f1);
+ if (f2 != NULL)
+ fclose(f2);
+
+ return (rval);
+}
+
+/*
+ * Check to see if the given files differ.
+ * Returns 0 if they are the same, 1 if different, and -1 on error.
+ * XXX - could use code from cmp(1) [faster]
+ */
+static int
+files_differ(FILE *f1, FILE *f2)
+{
+ char buf1[BUFSIZ], buf2[BUFSIZ];
+ size_t i, j;
+
+ if (stb1.st_size != stb2.st_size)
+ return (1);
+ for (;;) {
+ i = fread(buf1, (size_t)1, sizeof(buf1), f1);
+ j = fread(buf2, (size_t)1, sizeof(buf2), f2);
+ if (i != j)
+ return (1);
+ if (i == 0 && j == 0) {
+ if (ferror(f1) || ferror(f2))
+ return (1);
+ return (0);
+ }
+ if (memcmp(buf1, buf2, i) != 0)
+ return (1);
+ }
+}
+
+static int
+prepare(int i, FILE *fd, off_t filesize)
+{
+ void *tmp;
+ struct line *p;
+ int j, h;
+ size_t sz;
+
+ rewind(fd);
+
+ sz = ((size_t)filesize <= SIZE_MAX ? (size_t)filesize : SIZE_MAX) / 25;
+ if (sz < 100)
+ sz = 100;
+
+ p = xcalloc(sz + 3, sizeof(*p));
+ for (j = 0; (h = readhash(fd));) {
+ if (j == (int)sz) {
+ sz = sz * 3 / 2;
+ tmp = xrealloc(p, sz + 3, sizeof(*p));
+ p = tmp;
+ }
+ p[++j].value = h;
+ }
+ diff_len[i] = j;
+ file[i] = p;
+
+ return (0);
+}
+
+static void
+prune(void)
+{
+ int i, j;
+
+ for (pref = 0; pref < diff_len[0] && pref < diff_len[1] &&
+ file[0][pref + 1].value == file[1][pref + 1].value;
+ pref++)
+ ;
+ for (suff = 0;
+ (suff < diff_len[0] - pref) && (suff < diff_len[1] - pref) &&
+ (file[0][diff_len[0] - suff].value ==
+ file[1][diff_len[1] - suff].value);
+ suff++)
+ ;
+ for (j = 0; j < 2; j++) {
+ sfile[j] = file[j] + pref;
+ slen[j] = diff_len[j] - pref - suff;
+ for (i = 0; i <= slen[j]; i++)
+ sfile[j][i].serial = i;
+ }
+}
+
+static void
+equiv(struct line *a, int n, struct line *b, int m, int *c)
+{
+ int i, j;
+
+ i = j = 1;
+ while (i <= n && j <= m) {
+ if (a[i].value < b[j].value)
+ a[i++].value = 0;
+ else if (a[i].value == b[j].value)
+ a[i++].value = j;
+ else
+ j++;
+ }
+ while (i <= n)
+ a[i++].value = 0;
+ b[m + 1].value = 0;
+ j = 0;
+ while (++j <= m) {
+ c[j] = -b[j].serial;
+ while (b[j + 1].value == b[j].value) {
+ j++;
+ c[j] = b[j].serial;
+ }
+ }
+ c[j] = -1;
+}
+
+/* Code taken from ping.c */
+static int
+isqrt(int n)
+{
+ int y, x = 1;
+
+ if (n == 0)
+ return (0);
+
+ do { /* newton was a stinker */
+ y = x;
+ x = n / x;
+ x += y;
+ x /= 2;
+ } while (x - y > 1 || x - y < -1);
+
+ return (x);
+}
+
+static int
+stone(int *a, int n, int *b, int *c)
+{
+ int ret;
+ int i, k, y, j, l;
+ int oldc, tc, oldl;
+ u_int numtries;
+
+ /* XXX move the isqrt() out of the macro to avoid multiple calls */
+ const u_int bound = dflag ? UINT_MAX : MAX(256, (u_int)isqrt(n));
+
+ k = 0;
+ if ((ret = newcand(0, 0, 0)) < 0)
+ return (-1);
+ c[0] = ret;
+ for (i = 1; i <= n; i++) {
+ j = a[i];
+ if (j == 0)
+ continue;
+ y = -b[j];
+ oldl = 0;
+ oldc = c[0];
+ numtries = 0;
+ do {
+ if (y <= clist[oldc].y)
+ continue;
+ l = search(c, k, y);
+ if (l != oldl + 1)
+ oldc = c[l - 1];
+ if (l <= k) {
+ if (clist[c[l]].y <= y)
+ continue;
+ tc = c[l];
+ if ((ret = newcand(i, y, oldc)) < 0)
+ return (-1);
+ c[l] = ret;
+ oldc = tc;
+ oldl = l;
+ numtries++;
+ } else {
+ if ((ret = newcand(i, y, oldc)) < 0)
+ return (-1);
+ c[l] = ret;
+ k++;
+ break;
+ }
+ } while ((y = b[++j]) > 0 && numtries < bound);
+ }
+ return (k);
+}
+
+static int
+newcand(int x, int y, int pred)
+{
+ struct cand *q, *tmp;
+ int newclistlen;
+
+ if (clen == clistlen) {
+ newclistlen = clistlen * 11 / 10;
+ tmp = xrealloc(clist, newclistlen, sizeof(*clist));
+ clist = tmp;
+ clistlen = newclistlen;
+ }
+ q = clist + clen;
+ q->x = x;
+ q->y = y;
+ q->pred = pred;
+ return (clen++);
+}
+
+static int
+search(int *c, int k, int y)
+{
+ int i, j, l, t;
+
+ if (clist[c[k]].y < y) /* quick look for typical case */
+ return (k + 1);
+ i = 0;
+ j = k + 1;
+ for (;;) {
+ l = (i + j) / 2;
+ if (l <= i)
+ break;
+ t = clist[c[l]].y;
+ if (t > y)
+ j = l;
+ else if (t < y)
+ i = l;
+ else
+ return (l);
+ }
+ return (l + 1);
+}
+
+static void
+unravel(int p)
+{
+ struct cand *q;
+ int i;
+
+ for (i = 0; i <= diff_len[0]; i++)
+ J[i] = i <= pref ? i :
+ i > diff_len[0] - suff ? i + diff_len[1] - diff_len[0] : 0;
+ for (q = clist + p; q->y != 0; q = clist + q->pred)
+ J[q->x + pref] = q->y + pref;
+}
+
+/*
+ * Check does double duty:
+ * 1. ferret out any fortuitous correspondences due
+ * to confounding by hashing (which result in "jackpot")
+ * 2. collect random access indexes to the two files
+ */
+static void
+check(FILE *f1, FILE *f2)
+{
+ int i, j, jackpot, c, d;
+ long ctold, ctnew;
+
+ rewind(f1);
+ rewind(f2);
+ j = 1;
+ ixold[0] = ixnew[0] = 0;
+ jackpot = 0;
+ ctold = ctnew = 0;
+ for (i = 1; i <= diff_len[0]; i++) {
+ if (J[i] == 0) {
+ ixold[i] = ctold += skipline(f1);
+ continue;
+ }
+ while (j < J[i]) {
+ ixnew[j] = ctnew += skipline(f2);
+ j++;
+ }
+ if (bflag == 1 || wflag == 1 || iflag == 1) {
+ for (;;) {
+ c = getc(f1);
+ d = getc(f2);
+ /*
+ * GNU diff ignores a missing newline
+ * in one file if bflag || wflag.
+ */
+ if ((bflag == 1 || wflag == 1) &&
+ ((c == EOF && d == '\n') ||
+ (c == '\n' && d == EOF))) {
+ break;
+ }
+ ctold++;
+ ctnew++;
+ if (bflag == 1 && isspace(c) && isspace(d)) {
+ do {
+ if (c == '\n')
+ break;
+ ctold++;
+ } while (isspace(c = getc(f1)));
+ do {
+ if (d == '\n')
+ break;
+ ctnew++;
+ } while (isspace(d = getc(f2)));
+ } else if (wflag == 1) {
+ while (isspace(c) && c != '\n') {
+ c = getc(f1);
+ ctold++;
+ }
+ while (isspace(d) && d != '\n') {
+ d = getc(f2);
+ ctnew++;
+ }
+ }
+ if (chrtran[c] != chrtran[d]) {
+ jackpot++;
+ J[i] = 0;
+ if (c != '\n' && c != EOF)
+ ctold += skipline(f1);
+ if (d != '\n' && c != EOF)
+ ctnew += skipline(f2);
+ break;
+ }
+ if (c == '\n' || c == EOF)
+ break;
+ }
+ } else {
+ for (;;) {
+ ctold++;
+ ctnew++;
+ if ((c = getc(f1)) != (d = getc(f2))) {
+ /* jackpot++; */
+ J[i] = 0;
+ if (c != '\n' && c != EOF)
+ ctold += skipline(f1);
+ if (d != '\n' && c != EOF)
+ ctnew += skipline(f2);
+ break;
+ }
+ if (c == '\n' || c == EOF)
+ break;
+ }
+ }
+ ixold[i] = ctold;
+ ixnew[j] = ctnew;
+ j++;
+ }
+ for (; j <= diff_len[1]; j++)
+ ixnew[j] = ctnew += skipline(f2);
+ /*
+ * if (jackpot != 0)
+ * printf("jackpot\n");
+ */
+}
+
+/* shellsort CACM #201 */
+static void
+sort(struct line *a, int n)
+{
+ struct line *ai, *aim, w;
+ int j, m = 0, k;
+
+ if (n == 0)
+ return;
+ for (j = 1; j <= n; j *= 2)
+ m = 2 * j - 1;
+ for (m /= 2; m != 0; m /= 2) {
+ k = n - m;
+ for (j = 1; j <= k; j++) {
+ for (ai = &a[j]; ai > a; ai -= m) {
+ aim = &ai[m];
+ if (aim < ai)
+ break; /* wraparound */
+ if (aim->value > ai[0].value ||
+ (aim->value == ai[0].value &&
+ aim->serial > ai[0].serial))
+ break;
+ w.value = ai[0].value;
+ ai[0].value = aim->value;
+ aim->value = w.value;
+ w.serial = ai[0].serial;
+ ai[0].serial = aim->serial;
+ aim->serial = w.serial;
+ }
+ }
+ }
+}
+
+static void
+unsort(struct line *f, int l, int *b)
+{
+ int *a, i;
+
+ a = xcalloc(l + 1, sizeof(*a));
+ for (i = 1; i <= l; i++)
+ a[f[i].serial] = f[i].value;
+ for (i = 1; i <= l; i++)
+ b[i] = a[i];
+ xfree(a);
+}
+
+static int
+skipline(FILE *f)
+{
+ int i, c;
+
+ for (i = 1; (c = getc(f)) != '\n' && c != EOF; i++)
+ continue;
+ return (i);
+}
+
+static void
+output(FILE *f1, FILE *f2)
+{
+ int m, i0, i1, j0, j1;
+
+ rewind(f1);
+ rewind(f2);
+ m = diff_len[0];
+ J[0] = 0;
+ J[m + 1] = diff_len[1] + 1;
+ for (i0 = 1; i0 <= m; i0 = i1 + 1) {
+ while (i0 <= m && J[i0] == J[i0 - 1] + 1)
+ i0++;
+ j0 = J[i0 - 1] + 1;
+ i1 = i0 - 1;
+ while (i1 < m && J[i1 + 1] == 0)
+ i1++;
+ j1 = J[i1 + 1] - 1;
+ J[i1] = j1;
+ change(f1, f2, i0, i1, j0, j1);
+ }
+ if (m == 0)
+ change(f1, f2, 1, 0, 1, diff_len[1]);
+ if (diff_format == D_IFDEF) {
+ for (;;) {
+#define c i0
+ if ((c = getc(f1)) == EOF)
+ return;
+ diff_output("%c", c);
+ }
+#undef c
+ }
+ if (anychange != 0) {
+ if (diff_format == D_CONTEXT)
+ dump_context_vec(f1, f2);
+ else if (diff_format == D_UNIFIED)
+ dump_unified_vec(f1, f2);
+ }
+}
+
+static __inline void
+range(int a, int b, char *separator)
+{
+ diff_output("%d", a > b ? b : a);
+ if (a < b)
+ diff_output("%s%d", separator, b);
+}
+
+static __inline void
+uni_range(int a, int b)
+{
+ if (a < b)
+ diff_output("%d,%d", a, b - a + 1);
+ else if (a == b)
+ diff_output("%d", b);
+ else
+ diff_output("%d,0", b);
+}
+
+static char *
+preadline(int fd, size_t rlen, off_t off)
+{
+ char *line;
+ ssize_t nr;
+
+ line = xmalloc(rlen + 1);
+ if ((nr = pread(fd, line, rlen, off)) < 0) {
+ warn("preadline failed");
+ return (NULL);
+ }
+ line[nr] = '\0';
+ return (line);
+}
+
+static int
+ignoreline(char *line)
+{
+ int ret;
+
+ ret = regexec(&ignore_re, line, (size_t)0, NULL, 0);
+ xfree(line);
+ return (ret == 0); /* if it matched, it should be ignored. */
+}
+
+/*
+ * Indicate that there is a difference between lines a and b of the from file
+ * to get to lines c to d of the to file. If a is greater then b then there
+ * are no lines in the from file involved and this means that there were
+ * lines appended (beginning at b). If c is greater than d then there are
+ * lines missing from the to file.
+ */
+static void
+change(FILE *f1, FILE *f2, int a, int b, int c, int d)
+{
+ int i;
+ static size_t max_context = 64;
+ char buf[64];
+ struct tm *t;
+
+ if (diff_format != D_IFDEF && a > b && c > d)
+ return;
+ if (ignore_pats != NULL) {
+ char *line;
+ /*
+ * All lines in the change, insert, or delete must
+ * match an ignore pattern for the change to be
+ * ignored.
+ */
+ if (a <= b) { /* Changes and deletes. */
+ for (i = a; i <= b; i++) {
+ line = preadline(fileno(f1),
+ ixold[i] - ixold[i - 1], ixold[i - 1]);
+ if (!ignoreline(line))
+ goto proceed;
+ }
+ }
+ if (a > b || c <= d) { /* Changes and inserts. */
+ for (i = c; i <= d; i++) {
+ line = preadline(fileno(f2),
+ ixnew[i] - ixnew[i - 1], ixnew[i - 1]);
+ if (!ignoreline(line))
+ goto proceed;
+ }
+ }
+ return;
+ }
+proceed:
+ if (diff_format == D_CONTEXT || diff_format == D_UNIFIED) {
+ /*
+ * Allocate change records as needed.
+ */
+ if (context_vec_ptr == context_vec_end - 1) {
+ struct context_vec *tmp;
+ ptrdiff_t offset = context_vec_ptr - context_vec_start;
+ max_context <<= 1;
+ tmp = xrealloc(context_vec_start, max_context,
+ sizeof(*context_vec_start));
+ context_vec_start = tmp;
+ context_vec_end = context_vec_start + max_context;
+ context_vec_ptr = context_vec_start + offset;
+ }
+ if (anychange == 0) {
+ /*
+ * Print the context/unidiff header first time through.
+ */
+ t = localtime(&stb1.st_mtime);
+ (void)strftime(buf, sizeof(buf),
+ "%Y/%m/%d %H:%M:%S", t);
+
+ diff_output("%s %s %s",
+ diff_format == D_CONTEXT ? "***" : "---", diff_file,
+ buf);
+
+ if (diff_rev1 != NULL) {
+ rcsnum_tostr(diff_rev1, buf, sizeof(buf));
+ diff_output("\t%s", buf);
+ }
+
+ printf("\n");
+
+ t = localtime(&stb2.st_mtime);
+ (void)strftime(buf, sizeof(buf),
+ "%Y/%m/%d %H:%M:%S", t);
+
+ diff_output("%s %s %s",
+ diff_format == D_CONTEXT ? "---" : "+++", diff_file,
+ buf);
+
+ if (diff_rev2 != NULL) {
+ rcsnum_tostr(diff_rev2, buf, sizeof(buf));
+ diff_output("\t%s", buf);
+ }
+
+ printf("\n");
+ anychange = 1;
+ } else if (a > context_vec_ptr->b + (2 * context) + 1 &&
+ c > context_vec_ptr->d + (2 * context) + 1) {
+ /*
+ * If this change is more than 'context' lines from the
+ * previous change, dump the record and reset it.
+ */
+ if (diff_format == D_CONTEXT)
+ dump_context_vec(f1, f2);
+ else
+ dump_unified_vec(f1, f2);
+ }
+ context_vec_ptr++;
+ context_vec_ptr->a = a;
+ context_vec_ptr->b = b;
+ context_vec_ptr->c = c;
+ context_vec_ptr->d = d;
+ return;
+ }
+ if (anychange == 0)
+ anychange = 1;
+ switch (diff_format) {
+ case D_BRIEF:
+ return;
+ case D_NORMAL:
+ range(a, b, ",");
+ diff_output("%c", a > b ? 'a' : c > d ? 'd' : 'c');
+ if (diff_format == D_NORMAL)
+ range(c, d, ",");
+ diff_output("\n");
+ break;
+ case D_RCSDIFF:
+ if (a > b)
+ diff_output("a%d %d\n", b, d - c + 1);
+ else {
+ diff_output("d%d %d\n", a, b - a + 1);
+
+ if (!(c > d)) /* add changed lines */
+ diff_output("a%d %d\n", b, d - c + 1);
+ }
+ break;
+ }
+ if (diff_format == D_NORMAL || diff_format == D_IFDEF) {
+ fetch(ixold, a, b, f1, '<', 1);
+ if (a <= b && c <= d && diff_format == D_NORMAL)
+ diff_output("---\n");
+ }
+ fetch(ixnew, c, d, f2, diff_format == D_NORMAL ? '>' : '\0', 0);
+ if (inifdef) {
+ diff_output("#endif /* %s */\n", ifdefname);
+ inifdef = 0;
+ }
+}
+
+static void
+fetch(long *f, int a, int b, FILE *lb, int ch, int oldfile)
+{
+ long j, nc;
+ int i, c, col;
+
+ /*
+ * When doing #ifdef's, copy down to current line
+ * if this is the first file, so that stuff makes it to output.
+ */
+ if (diff_format == D_IFDEF && oldfile) {
+ long curpos = ftell(lb);
+ /* print through if append (a>b), else to (nb: 0 vs 1 orig) */
+ nc = f[a > b ? b : a - 1] - curpos;
+ for (i = 0; i < nc; i++)
+ diff_output("%c", getc(lb));
+ }
+ if (a > b)
+ return;
+ if (diff_format == D_IFDEF) {
+ if (inifdef) {
+ diff_output("#else /* %s%s */\n",
+ oldfile == 1 ? "!" : "", ifdefname);
+ } else {
+ if (oldfile)
+ diff_output("#ifndef %s\n", ifdefname);
+ else
+ diff_output("#ifdef %s\n", ifdefname);
+ }
+ inifdef = 1 + oldfile;
+ }
+ for (i = a; i <= b; i++) {
+ fseek(lb, f[i - 1], SEEK_SET);
+ nc = f[i] - f[i - 1];
+ if (diff_format != D_IFDEF && ch != '\0') {
+ diff_output("%c", ch);
+ if (Tflag == 1 && (diff_format == D_NORMAL ||
+ diff_format == D_CONTEXT ||
+ diff_format == D_UNIFIED))
+ diff_output("\t");
+ else if (diff_format != D_UNIFIED)
+ diff_output(" ");
+ }
+ col = 0;
+ for (j = 0; j < nc; j++) {
+ if ((c = getc(lb)) == EOF) {
+ if (diff_format == D_RCSDIFF)
+ warn("No newline at end of file");
+ else
+ diff_output("\n\\ No newline at end of "
+ "file");
+ return;
+ }
+ if (c == '\t' && tflag == 1) {
+ do {
+ diff_output(" ");
+ } while (++col & 7);
+ } else {
+ diff_output("%c", c);
+ col++;
+ }
+ }
+ }
+}
+
+/*
+ * Hash function taken from Robert Sedgewick, Algorithms in C, 3d ed., p 578.
+ */
+static int
+readhash(FILE *f)
+{
+ int i, t, space;
+ int sum;
+
+ sum = 1;
+ space = 0;
+ if (bflag != 1 && wflag != 1) {
+ if (iflag == 1)
+ for (i = 0; (t = getc(f)) != '\n'; i++) {
+ if (t == EOF) {
+ if (i == 0)
+ return (0);
+ break;
+ }
+ sum = sum * 127 + chrtran[t];
+ }
+ else
+ for (i = 0; (t = getc(f)) != '\n'; i++) {
+ if (t == EOF) {
+ if (i == 0)
+ return (0);
+ break;
+ }
+ sum = sum * 127 + t;
+ }
+ } else {
+ for (i = 0;;) {
+ switch (t = getc(f)) {
+ case '\t':
+ case ' ':
+ space++;
+ continue;
+ default:
+ if (space != 0 && wflag != 1) {
+ i++;
+ space = 0;
+ }
+ sum = sum * 127 + chrtran[t];
+ i++;
+ continue;
+ case EOF:
+ if (i == 0)
+ return (0);
+ /* FALLTHROUGH */
+ case '\n':
+ break;
+ }
+ break;
+ }
+ }
+ /*
+ * There is a remote possibility that we end up with a zero sum.
+ * Zero is used as an EOF marker, so return 1 instead.
+ */
+ return (sum == 0 ? 1 : sum);
+}
+
+static int
+asciifile(FILE *f)
+{
+ char buf[BUFSIZ];
+ size_t i, cnt;
+
+ if (aflag == 1 || f == NULL)
+ return (1);
+
+ rewind(f);
+ cnt = fread(buf, (size_t)1, sizeof(buf), f);
+ for (i = 0; i < cnt; i++)
+ if (!isprint(buf[i]) && !isspace(buf[i]))
+ return (0);
+ return (1);
+}
+
+static char*
+match_function(const long *f, int pos, FILE *fp)
+{
+ unsigned char buf[FUNCTION_CONTEXT_SIZE];
+ size_t nc;
+ int last = lastline;
+ char *p;
+
+ lastline = pos;
+ while (pos > last) {
+ fseek(fp, f[pos - 1], SEEK_SET);
+ nc = f[pos] - f[pos - 1];
+ if (nc >= sizeof(buf))
+ nc = sizeof(buf) - 1;
+ nc = fread(buf, (size_t)1, nc, fp);
+ if (nc > 0) {
+ buf[nc] = '\0';
+ p = strchr((const char *)buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ if (isalpha(buf[0]) || buf[0] == '_' || buf[0] == '$') {
+ strlcpy(lastbuf, (const char *)buf,
+ sizeof lastbuf);
+ lastmatchline = pos;
+ return lastbuf;
+ }
+ }
+ pos--;
+ }
+ return (lastmatchline > 0) ? lastbuf : NULL;
+}
+
+
+/* dump accumulated "context" diff changes */
+static void
+dump_context_vec(FILE *f1, FILE *f2)
+{
+ struct context_vec *cvp = context_vec_start;
+ int lowa, upb, lowc, upd, do_output;
+ int a, b, c, d;
+ char ch, *f;
+
+ if (context_vec_start > context_vec_ptr)
+ return;
+
+ b = d = 0; /* gcc */
+ lowa = MAX(1, cvp->a - context);
+ upb = MIN(diff_len[0], context_vec_ptr->b + context);
+ lowc = MAX(1, cvp->c - context);
+ upd = MIN(diff_len[1], context_vec_ptr->d + context);
+
+ diff_output("***************");
+ if (pflag == 1) {
+ f = match_function(ixold, lowa - 1, f1);
+ if (f != NULL) {
+ diff_output(" ");
+ diff_output("%s", f);
+ }
+ }
+ diff_output("\n*** ");
+ range(lowa, upb, ",");
+ diff_output(" ****\n");
+
+ /*
+ * Output changes to the "old" file. The first loop suppresses
+ * output if there were no changes to the "old" file (we'll see
+ * the "old" lines as context in the "new" list).
+ */
+ do_output = 0;
+ for (; cvp <= context_vec_ptr; cvp++)
+ if (cvp->a <= cvp->b) {
+ cvp = context_vec_start;
+ do_output++;
+ break;
+ }
+ if (do_output != 0) {
+ while (cvp <= context_vec_ptr) {
+ a = cvp->a;
+ b = cvp->b;
+ c = cvp->c;
+ d = cvp->d;
+
+ if (a <= b && c <= d)
+ ch = 'c';
+ else
+ ch = (a <= b) ? 'd' : 'a';
+
+ if (ch == 'a')
+ fetch(ixold, lowa, b, f1, ' ', 0);
+ else {
+ fetch(ixold, lowa, a - 1, f1, ' ', 0);
+ fetch(ixold, a, b, f1,
+ ch == 'c' ? '!' : '-', 0);
+ }
+ lowa = b + 1;
+ cvp++;
+ }
+ fetch(ixold, b + 1, upb, f1, ' ', 0);
+ }
+ /* output changes to the "new" file */
+ diff_output("--- ");
+ range(lowc, upd, ",");
+ diff_output(" ----\n");
+
+ do_output = 0;
+ for (cvp = context_vec_start; cvp <= context_vec_ptr; cvp++)
+ if (cvp->c <= cvp->d) {
+ cvp = context_vec_start;
+ do_output++;
+ break;
+ }
+ if (do_output != 0) {
+ while (cvp <= context_vec_ptr) {
+ a = cvp->a;
+ b = cvp->b;
+ c = cvp->c;
+ d = cvp->d;
+
+ if (a <= b && c <= d)
+ ch = 'c';
+ else
+ ch = (a <= b) ? 'd' : 'a';
+
+ if (ch == 'd')
+ fetch(ixnew, lowc, d, f2, ' ', 0);
+ else {
+ fetch(ixnew, lowc, c - 1, f2, ' ', 0);
+ fetch(ixnew, c, d, f2,
+ ch == 'c' ? '!' : '+', 0);
+ }
+ lowc = d + 1;
+ cvp++;
+ }
+ fetch(ixnew, d + 1, upd, f2, ' ', 0);
+ }
+ context_vec_ptr = context_vec_start - 1;
+}
+
+/* dump accumulated "unified" diff changes */
+static void
+dump_unified_vec(FILE *f1, FILE *f2)
+{
+ struct context_vec *cvp = context_vec_start;
+ int lowa, upb, lowc, upd;
+ int a, b, c, d;
+ char ch, *f;
+
+ if (context_vec_start > context_vec_ptr)
+ return;
+
+ b = d = 0; /* gcc */
+ lowa = MAX(1, cvp->a - context);
+ upb = MIN(diff_len[0], context_vec_ptr->b + context);
+ lowc = MAX(1, cvp->c - context);
+ upd = MIN(diff_len[1], context_vec_ptr->d + context);
+
+ diff_output("@@ -");
+ uni_range(lowa, upb);
+ diff_output(" +");
+ uni_range(lowc, upd);
+ diff_output(" @@");
+ if (pflag == 1) {
+ f = match_function(ixold, lowa - 1, f1);
+ if (f != NULL) {
+ diff_output(" ");
+ diff_output("%s", f);
+ }
+ }
+ diff_output("\n");
+
+ /*
+ * Output changes in "unified" diff format--the old and new lines
+ * are printed together.
+ */
+ for (; cvp <= context_vec_ptr; cvp++) {
+ a = cvp->a;
+ b = cvp->b;
+ c = cvp->c;
+ d = cvp->d;
+
+ /*
+ * c: both new and old changes
+ * d: only changes in the old file
+ * a: only changes in the new file
+ */
+ if (a <= b && c <= d)
+ ch = 'c';
+ else
+ ch = (a <= b) ? 'd' : 'a';
+
+ switch (ch) {
+ case 'c':
+ fetch(ixold, lowa, a - 1, f1, ' ', 0);
+ fetch(ixold, a, b, f1, '-', 0);
+ fetch(ixnew, c, d, f2, '+', 0);
+ break;
+ case 'd':
+ fetch(ixold, lowa, a - 1, f1, ' ', 0);
+ fetch(ixold, a, b, f1, '-', 0);
+ break;
+ case 'a':
+ fetch(ixnew, lowc, c - 1, f2, ' ', 0);
+ fetch(ixnew, c, d, f2, '+', 0);
+ break;
+ }
+ lowa = b + 1;
+ lowc = d + 1;
+ }
+ fetch(ixnew, d + 1, upd, f2, ' ', 0);
+
+ context_vec_ptr = context_vec_start - 1;
+}
+
+void
+diff_output(const char *fmt, ...)
+{
+ va_list vap;
+ int i;
+ char *str;
+
+ va_start(vap, fmt);
+ i = vasprintf(&str, fmt, vap);
+ va_end(vap);
+ if (i == -1)
+ err(1, "diff_output:");
+ if (diffbuf != NULL)
+ rcs_buf_append(diffbuf, str, strlen(str));
+ else
+ printf("%s", str);
+ xfree(str);
+}
diff --git a/usr.bin/rcs/diff.h b/usr.bin/rcs/diff.h
new file mode 100644
index 00000000000..73b3144716a
--- /dev/null
+++ b/usr.bin/rcs/diff.h
@@ -0,0 +1,114 @@
+/* $OpenBSD: diff.h,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (C) Caldera International Inc. 2001-2002.
+ * 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 and documentation 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed or owned by Caldera
+ * International, Inc.
+ * 4. Neither the name of Caldera International, Inc. nor the names of other
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
+ * INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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.
+ */
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2004 Jean-Francois Brousseau. 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * @(#)diffreg.c 8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef CVS_DIFF_H
+#define CVS_DIFF_H
+
+#include "buf.h"
+#include "rcs.h"
+
+#define CVS_DIFF_DEFCTX 3 /* default context length */
+
+/*
+ * Output format options
+ */
+#define D_NORMAL 0 /* Normal output */
+#define D_CONTEXT 1 /* Diff with context */
+#define D_UNIFIED 2 /* Unified context diff */
+#define D_IFDEF 3 /* Diff with merged #ifdef's */
+#define D_BRIEF 4 /* Say if the files differ */
+#define D_RCSDIFF 5 /* Reverse editor output: RCS format */
+
+/*
+ * Status values for rcs_diffreg() return values
+ */
+#define D_SAME 0 /* Files are the same */
+#define D_DIFFER 1 /* Files are different */
+#define D_BINARY 2 /* Binary files are different */
+#define D_COMMON 3 /* Subdirectory common to both dirs */
+#define D_ONLY 4 /* Only exists in one directory */
+#define D_MISMATCH1 5 /* path1 was a dir, path2 a file */
+#define D_MISMATCH2 6 /* path1 was a file, path2 a dir */
+#define D_ERROR 7 /* An error occurred */
+#define D_SKIPPED1 8 /* path1 was a special file */
+#define D_SKIPPED2 9 /* path2 was a special file */
+
+struct rcs_lines;
+
+BUF *rcs_diff3(RCSFILE *, char *, RCSNUM *, RCSNUM *, int);
+void diff_output(const char *, ...);
+int rcs_diffreg(const char *, const char *, BUF *);
+int ed_patch_lines(struct rcs_lines *, struct rcs_lines *);
+
+extern int diff_format;
+extern int diff3_conflicts;
+extern char *diff_file;
+extern char diffargs[128];
+extern BUF *diffbuf;
+extern RCSNUM *diff_rev1;
+extern RCSNUM *diff_rev2;
+
+#endif
diff --git a/usr.bin/rcs/diff3.c b/usr.bin/rcs/diff3.c
new file mode 100644
index 00000000000..687759ea4a9
--- /dev/null
+++ b/usr.bin/rcs/diff3.c
@@ -0,0 +1,806 @@
+/* $OpenBSD: diff3.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+
+/*
+ * Copyright (C) Caldera International Inc. 2001-2002.
+ * 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 and documentation 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed or owned by Caldera
+ * International, Inc.
+ * 4. Neither the name of Caldera International, Inc. nor the names of other
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
+ * INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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.
+ */
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * @(#)diff3.c 8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static const char rcsid[] =
+ "$OpenBSD: diff3.c,v 1.1 2006/04/26 02:55:13 joris Exp $";
+#endif /* not lint */
+
+#include "includes.h"
+
+#include "diff.h"
+#include "util.h"
+#include "xmalloc.h"
+
+/* diff3 - 3-way differential file comparison */
+
+/* diff3 [-ex3EX] d13 d23 f1 f2 f3 [m1 m3]
+ *
+ * d13 = diff report on f1 vs f3
+ * d23 = diff report on f2 vs f3
+ * f1, f2, f3 the 3 files
+ * if changes in f1 overlap with changes in f3, m1 and m3 are used
+ * to mark the overlaps; otherwise, the file names f1 and f3 are used
+ * (only for options E and X).
+ */
+
+/*
+ * "from" is first in range of changed lines; "to" is last+1
+ * from=to=line after point of insertion for added lines.
+ */
+struct range {
+ int from;
+ int to;
+};
+
+struct diff {
+ struct range old;
+ struct range new;
+};
+
+static size_t szchanges;
+
+static struct diff *d13;
+static struct diff *d23;
+
+/*
+ * "de" is used to gather editing scripts. These are later spewed out in
+ * reverse order. Its first element must be all zero, the "new" component
+ * of "de" contains line positions or byte positions depending on when you
+ * look (!?). Array overlap indicates which sections in "de" correspond to
+ * lines that are different in all three files.
+ */
+static struct diff *de;
+static char *overlap;
+static int overlapcnt = 0;
+static FILE *fp[3];
+static int cline[3]; /* # of the last-read line in each file (0-2) */
+
+/*
+ * the latest known correspondence between line numbers of the 3 files
+ * is stored in last[1-3];
+ */
+static int last[4];
+static int eflag;
+static int oflag; /* indicates whether to mark overlaps (-E or -X)*/
+static int debug = 0;
+static char f1mark[40], f3mark[40]; /* markers for -E and -X */
+
+static int duplicate(struct range *, struct range *);
+static int edit(struct diff *, int, int);
+static char *getchange(FILE *);
+static char *getline(FILE *, size_t *);
+static int number(char **);
+static size_t readin(char *, struct diff **);
+static int skip(int, int, char *);
+static int edscript(int);
+static int merge(size_t, size_t);
+static void change(int, struct range *, int);
+static void keep(int, struct range *);
+static void prange(struct range *);
+static void repos(int);
+static void separate(const char *);
+static void increase(void);
+static int diff3_internal(int, char **, const char *, const char *);
+
+int diff3_conflicts = 0;
+
+BUF *
+rcs_diff3(RCSFILE *rf, char *workfile, RCSNUM *rev1, RCSNUM *rev2, int verbose)
+{
+ int argc;
+ char *data, *patch;
+ char *argv[5], r1[16], r2[16];
+ char path1[MAXPATHLEN], path2[MAXPATHLEN], path3[MAXPATHLEN];
+ char dp13[MAXPATHLEN], dp23[MAXPATHLEN];
+ BUF *b1, *b2, *b3, *d1, *d2, *diffb;
+
+ b1 = b2 = b3 = d1 = d2 = diffb = NULL;
+
+ rcsnum_tostr(rev1, r1, sizeof(r1));
+ rcsnum_tostr(rev2, r2, sizeof(r2));
+
+ if ((b1 = rcs_buf_load(workfile, BUF_AUTOEXT)) == NULL)
+ goto out;
+
+ if (verbose == 1)
+ printf("Retrieving revision %s\n", r1);
+ if ((b2 = rcs_getrev(rf, rev1)) == NULL)
+ goto out;
+
+ if (verbose == 1)
+ printf("Retrieving revision %s\n", r2);
+ if ((b3 = rcs_getrev(rf, rev2)) == NULL)
+ goto out;
+
+ d1 = rcs_buf_alloc((size_t)128, BUF_AUTOEXT);
+ d2 = rcs_buf_alloc((size_t)128, BUF_AUTOEXT);
+ diffb = rcs_buf_alloc((size_t)128, BUF_AUTOEXT);
+
+ strlcpy(path1, "/tmp/diff1.XXXXXXXXXX", sizeof(path1));
+ rcs_buf_write_stmp(b1, path1, 0600);
+
+ strlcpy(path2, "/tmp/diff2.XXXXXXXXXX", sizeof(path2));
+ rcs_buf_write_stmp(b2, path2, 0600);
+
+ strlcpy(path3, "/tmp/diff3.XXXXXXXXXX", sizeof(path3));
+ rcs_buf_write_stmp(b3, path3, 0600);
+
+ rcs_buf_free(b2);
+ b2 = NULL;
+
+ rcs_diffreg(path1, path3, d1);
+ rcs_diffreg(path2, path3, d2);
+
+ strlcpy(dp13, "/tmp/d13.XXXXXXXXXX", sizeof(dp13));
+ rcs_buf_write_stmp(d1, dp13, 0600);
+
+ rcs_buf_free(d1);
+ d1 = NULL;
+
+ strlcpy(dp23, "/tmp/d23.XXXXXXXXXX", sizeof(dp23));
+ rcs_buf_write_stmp(d2, dp23, 0600);
+
+ rcs_buf_free(d2);
+ d2 = NULL;
+
+ argc = 0;
+ diffbuf = diffb;
+ argv[argc++] = dp13;
+ argv[argc++] = dp23;
+ argv[argc++] = path1;
+ argv[argc++] = path2;
+ argv[argc++] = path3;
+
+ diff3_conflicts = diff3_internal(argc, argv, workfile, r2);
+ if (diff3_conflicts < 0) {
+ rcs_buf_free(diffb);
+ diffb = NULL;
+ goto out;
+ }
+
+ rcs_buf_putc(diffb, '\0');
+ rcs_buf_putc(b1, '\0');
+
+ patch = rcs_buf_release(diffb);
+ data = rcs_buf_release(b1);
+ diffb = b1 = NULL;
+
+ if ((diffb = rcs_patchfile(data, patch, ed_patch_lines)) == NULL)
+ goto out;
+
+ if (verbose == 1 && diff3_conflicts != 0) {
+ warnx("%d conflict%s found during merge, "
+ "please correct.", diff3_conflicts,
+ (diff3_conflicts > 1) ? "s" : "");
+ }
+
+ xfree(data);
+ xfree(patch);
+
+out:
+ if (b1 != NULL)
+ rcs_buf_free(b1);
+ if (b2 != NULL)
+ rcs_buf_free(b2);
+ if (b3 != NULL)
+ rcs_buf_free(b3);
+ if (d1 != NULL)
+ rcs_buf_free(d1);
+ if (d2 != NULL)
+ rcs_buf_free(d2);
+
+ (void)unlink(path1);
+ (void)unlink(path2);
+ (void)unlink(path3);
+ (void)unlink(dp13);
+ (void)unlink(dp23);
+
+ return (diffb);
+}
+
+static int
+diff3_internal(int argc, char **argv, const char *fmark, const char *rmark)
+{
+ size_t m, n;
+ int i;
+
+ /* XXX */
+ eflag = 3;
+ oflag = 1;
+
+ if (argc < 5)
+ return (-1);
+
+ strlcpy(f1mark, "<<<<<<< ", sizeof(f1mark));
+ strlcat(f1mark, fmark, sizeof(f1mark));
+
+ strlcpy(f3mark, ">>>>>>> ", sizeof(f3mark));
+ strlcat(f3mark, rmark, sizeof(f3mark));
+
+ increase();
+ m = readin(argv[0], &d13);
+ n = readin(argv[1], &d23);
+
+ for (i = 0; i <= 2; i++) {
+ if ((fp[i] = fopen(argv[i + 2], "r")) == NULL) {
+ warn("%s", argv[i + 2]);
+ return (-1);
+ }
+ }
+
+ return (merge(m, n));
+}
+
+int
+ed_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
+{
+ char op, *ep;
+ struct rcs_line *sort, *lp, *dlp, *ndlp;
+ int start, end, i, lineno;
+
+ dlp = TAILQ_FIRST(&(dlines->l_lines));
+ lp = TAILQ_FIRST(&(plines->l_lines));
+
+ end = 0;
+ for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
+ lp = TAILQ_NEXT(lp, l_list)) {
+ op = lp->l_line[strlen(lp->l_line) - 1];
+ start = (int)strtol(lp->l_line, &ep, 10);
+ if (op == 'a') {
+ if (start > dlines->l_nblines ||
+ start < 0 || *ep != 'a')
+ errx(1, "ed_patch_lines");
+ } else if (op == 'c') {
+ if (start > dlines->l_nblines ||
+ start < 0 || (*ep != ',' && *ep != 'c'))
+ errx(1, "ed_patch_lines");
+
+ if (*ep == ',') {
+ ep++;
+ end = (int)strtol(ep, &ep, 10);
+ if (end < 0 || *ep != 'c')
+ errx(1, "ed_patch_lines");
+ } else {
+ end = start;
+ }
+ }
+
+
+ for (;;) {
+ if (dlp == NULL)
+ break;
+ if (dlp->l_lineno == start)
+ break;
+ if (dlp->l_lineno > start) {
+ dlp = TAILQ_PREV(dlp, rcs_tqh, l_list);
+ } else if (dlp->l_lineno < start) {
+ ndlp = TAILQ_NEXT(dlp, l_list);
+ if (ndlp->l_lineno > start)
+ break;
+ dlp = ndlp;
+ }
+ }
+
+ if (dlp == NULL)
+ errx(1, "ed_patch_lines");
+
+
+ if (op == 'c') {
+ for (i = 0; i <= (end - start); i++) {
+ ndlp = TAILQ_NEXT(dlp, l_list);
+ TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
+ dlp = ndlp;
+ }
+ dlp = TAILQ_PREV(dlp, rcs_tqh, l_list);
+ }
+
+ if (op == 'a' || op == 'c') {
+ for (;;) {
+ ndlp = lp;
+ lp = TAILQ_NEXT(lp, l_list);
+ if (lp == NULL)
+ errx(1, "ed_patch_lines");
+
+ if (!strcmp(lp->l_line, "."))
+ break;
+
+ TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
+ TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
+ lp, l_list);
+ dlp = lp;
+
+ lp->l_lineno = start;
+ lp = ndlp;
+ }
+ }
+
+ /*
+ * always resort lines as the markers might be put at the
+ * same line as we first started editing.
+ */
+ lineno = 0;
+ TAILQ_FOREACH(sort, &(dlines->l_lines), l_list)
+ sort->l_lineno = lineno++;
+ dlines->l_nblines = lineno - 1;
+ }
+
+ return (0);
+}
+
+/*
+ * Pick up the line numbers of all changes from one change file.
+ * (This puts the numbers in a vector, which is not strictly necessary,
+ * since the vector is processed in one sequential pass.
+ * The vector could be optimized out of existence)
+ */
+static size_t
+readin(char *name, struct diff **dd)
+{
+ int a, b, c, d;
+ char kind, *p;
+ size_t i;
+
+ fp[0] = fopen(name, "r");
+ for (i = 0; (p = getchange(fp[0])); i++) {
+ if (i >= szchanges - 1)
+ increase();
+ a = b = number(&p);
+ if (*p == ',') {
+ p++;
+ b = number(&p);
+ }
+ kind = *p++;
+ c = d = number(&p);
+ if (*p==',') {
+ p++;
+ d = number(&p);
+ }
+ if (kind == 'a')
+ a++;
+ if (kind == 'd')
+ c++;
+ b++;
+ d++;
+ (*dd)[i].old.from = a;
+ (*dd)[i].old.to = b;
+ (*dd)[i].new.from = c;
+ (*dd)[i].new.to = d;
+ }
+
+ if (i) {
+ (*dd)[i].old.from = (*dd)[i-1].old.to;
+ (*dd)[i].new.from = (*dd)[i-1].new.to;
+ }
+ (void)fclose(fp[0]);
+
+ return (i);
+}
+
+static int
+number(char **lc)
+{
+ int nn;
+
+ nn = 0;
+ while (isdigit((unsigned char)(**lc)))
+ nn = nn*10 + *(*lc)++ - '0';
+
+ return (nn);
+}
+
+static char *
+getchange(FILE *b)
+{
+ char *line;
+
+ while ((line = getline(b, NULL))) {
+ if (isdigit((unsigned char)line[0]))
+ return (line);
+ }
+
+ return (NULL);
+}
+
+static char *
+getline(FILE *b, size_t *n)
+{
+ char *cp;
+ size_t len;
+ static char *buf;
+ static size_t bufsize;
+
+ if ((cp = fgetln(b, &len)) == NULL)
+ return (NULL);
+
+ if (cp[len - 1] != '\n')
+ len++;
+ if (len + 1 > bufsize) {
+ char *newbuf;
+ do {
+ bufsize += 1024;
+ } while (len + 1 > bufsize);
+ newbuf = xrealloc(buf, 1, bufsize);
+ buf = newbuf;
+ }
+ memcpy(buf, cp, len - 1);
+ buf[len - 1] = '\n';
+ buf[len] = '\0';
+ if (n != NULL)
+ *n = len;
+
+ return (buf);
+}
+
+static int
+merge(size_t m1, size_t m2)
+{
+ struct diff *d1, *d2, *d3;
+ int dpl, j, t1, t2;
+
+ d1 = d13;
+ d2 = d23;
+ j = 0;
+ while ((t1 = d1 < d13 + m1) | (t2 = d2 < d23 + m2)) {
+ if (debug) {
+ printf("%d,%d=%d,%d %d,%d=%d,%d\n",
+ d1->old.from, d1->old.to,
+ d1->new.from, d1->new.to,
+ d2->old.from, d2->old.to,
+ d2->new.from, d2->new.to);
+ }
+
+ /* first file is different from others */
+ if (!t2 || (t1 && d1->new.to < d2->new.from)) {
+ /* stuff peculiar to 1st file */
+ if (eflag==0) {
+ separate("1");
+ change(1, &d1->old, 0);
+ keep(2, &d1->new);
+ change(3, &d1->new, 0);
+ }
+ d1++;
+ continue;
+ }
+
+ /* second file is different from others */
+ if (!t1 || (t2 && d2->new.to < d1->new.from)) {
+ if (eflag==0) {
+ separate("2");
+ keep(1, &d2->new);
+ change(2, &d2->old, 0);
+ change(3, &d2->new, 0);
+ }
+ d2++;
+ continue;
+ }
+
+ /*
+ * Merge overlapping changes in first file
+ * this happens after extension (see below).
+ */
+ if (d1 + 1 < d13 + m1 && d1->new.to >= d1[1].new.from) {
+ d1[1].old.from = d1->old.from;
+ d1[1].new.from = d1->new.from;
+ d1++;
+ continue;
+ }
+
+ /* merge overlapping changes in second */
+ if (d2 + 1 < d23 + m2 && d2->new.to >= d2[1].new.from) {
+ d2[1].old.from = d2->old.from;
+ d2[1].new.from = d2->new.from;
+ d2++;
+ continue;
+ }
+ /* stuff peculiar to third file or different in all */
+ if (d1->new.from == d2->new.from && d1->new.to == d2->new.to) {
+ dpl = duplicate(&d1->old,&d2->old);
+ if (dpl == -1)
+ return (-1);
+
+ /*
+ * dpl = 0 means all files differ
+ * dpl = 1 means files 1 and 2 identical
+ */
+ if (eflag==0) {
+ separate(dpl ? "3" : "");
+ change(1, &d1->old, dpl);
+ change(2, &d2->old, 0);
+ d3 = d1->old.to > d1->old.from ? d1 : d2;
+ change(3, &d3->new, 0);
+ } else
+ j = edit(d1, dpl, j);
+ d1++;
+ d2++;
+ continue;
+ }
+
+ /*
+ * Overlapping changes from file 1 and 2; extend changes
+ * appropriately to make them coincide.
+ */
+ if (d1->new.from < d2->new.from) {
+ d2->old.from -= d2->new.from-d1->new.from;
+ d2->new.from = d1->new.from;
+ } else if (d2->new.from < d1->new.from) {
+ d1->old.from -= d1->new.from-d2->new.from;
+ d1->new.from = d2->new.from;
+ }
+ if (d1->new.to > d2->new.to) {
+ d2->old.to += d1->new.to - d2->new.to;
+ d2->new.to = d1->new.to;
+ } else if (d2->new.to > d1->new.to) {
+ d1->old.to += d2->new.to - d1->new.to;
+ d1->new.to = d2->new.to;
+ }
+ }
+
+ return (edscript(j));
+}
+
+static void
+separate(const char *s)
+{
+ diff_output("====%s\n", s);
+}
+
+/*
+ * The range of lines rold.from thru rold.to in file i is to be changed.
+ * It is to be printed only if it does not duplicate something to be
+ * printed later.
+ */
+static void
+change(int i, struct range *rold, int fdup)
+{
+ diff_output("%d:", i);
+ last[i] = rold->to;
+ prange(rold);
+ if (fdup || debug)
+ return;
+ i--;
+ (void)skip(i, rold->from, NULL);
+ (void)skip(i, rold->to, " ");
+}
+
+/*
+ * print the range of line numbers, rold.from thru rold.to, as n1,n2 or n1
+ */
+static void
+prange(struct range *rold)
+{
+ if (rold->to <= rold->from)
+ diff_output("%da\n", rold->from - 1);
+ else {
+ diff_output("%d", rold->from);
+ if (rold->to > rold->from+1)
+ diff_output(",%d", rold->to - 1);
+ diff_output("c\n");
+ }
+}
+
+/*
+ * No difference was reported by diff between file 1 (or 2) and file 3,
+ * and an artificial dummy difference (trange) must be ginned up to
+ * correspond to the change reported in the other file.
+ */
+static void
+keep(int i, struct range *rnew)
+{
+ int delta;
+ struct range trange;
+
+ delta = last[3] - last[i];
+ trange.from = rnew->from - delta;
+ trange.to = rnew->to - delta;
+ change(i, &trange, 1);
+}
+
+/*
+ * skip to just before line number from in file "i". If "pr" is non-NULL,
+ * print all skipped stuff with string pr as a prefix.
+ */
+static int
+skip(int i, int from, char *pr)
+{
+ size_t j, n;
+ char *line;
+
+ for (n = 0; cline[i] < from - 1; n += j) {
+ if ((line = getline(fp[i], &j)) == NULL)
+ return (-1);
+ if (pr != NULL)
+ diff_output("%s%s", pr, line);
+ cline[i]++;
+ }
+ return ((int) n);
+}
+
+/*
+ * Return 1 or 0 according as the old range (in file 1) contains exactly
+ * the same data as the new range (in file 2).
+ */
+static int
+duplicate(struct range *r1, struct range *r2)
+{
+ int c,d;
+ int nchar;
+ int nline;
+
+ if (r1->to-r1->from != r2->to-r2->from)
+ return (0);
+ (void)skip(0, r1->from, NULL);
+ (void)skip(1, r2->from, NULL);
+ nchar = 0;
+ for (nline=0; nline < r1->to - r1->from; nline++) {
+ do {
+ c = getc(fp[0]);
+ d = getc(fp[1]);
+ if (c == -1 || d== -1)
+ return (-1);
+ nchar++;
+ if (c != d) {
+ repos(nchar);
+ return (0);
+ }
+ } while (c != '\n');
+ }
+ repos(nchar);
+ return (1);
+}
+
+static void
+repos(int nchar)
+{
+ int i;
+
+ for (i = 0; i < 2; i++)
+ (void)fseek(fp[i], (long)-nchar, 1);
+}
+
+/*
+ * collect an editing script for later regurgitation
+ */
+static int
+edit(struct diff *diff, int fdup, int j)
+{
+ if (((fdup + 1) & eflag) == 0)
+ return (j);
+ j++;
+ overlap[j] = !fdup;
+ if (!fdup)
+ overlapcnt++;
+ de[j].old.from = diff->old.from;
+ de[j].old.to = diff->old.to;
+ de[j].new.from = de[j-1].new.to + skip(2, diff->new.from, NULL);
+ de[j].new.to = de[j].new.from + skip(2, diff->new.to, NULL);
+ return (j);
+}
+
+/* regurgitate */
+static int
+edscript(int n)
+{
+ int j, k;
+ char block[BUFSIZ+1];
+
+ for (n = n; n > 0; n--) {
+ if (!oflag || !overlap[n])
+ prange(&de[n].old);
+ else
+ diff_output("%da\n=======\n", de[n].old.to -1);
+ (void)fseek(fp[2], (long)de[n].new.from, 0);
+ for (k = de[n].new.to-de[n].new.from; k > 0; k-= j) {
+ j = k > BUFSIZ ? BUFSIZ : k;
+ if (fread(block, (size_t)1, (size_t)j,
+ fp[2]) != (size_t)j)
+ return (-1);
+ block[j] = '\0';
+ diff_output("%s", block);
+ }
+
+ if (!oflag || !overlap[n])
+ diff_output(".\n");
+ else {
+ diff_output("%s\n.\n", f3mark);
+ diff_output("%da\n%s\n.\n", de[n].old.from - 1, f1mark);
+ }
+ }
+
+ return (overlapcnt);
+}
+
+static void
+increase(void)
+{
+ struct diff *p;
+ char *q;
+ size_t newsz, incr;
+
+ /* are the memset(3) calls needed? */
+ newsz = szchanges == 0 ? 64 : 2 * szchanges;
+ incr = newsz - szchanges;
+
+ p = xrealloc(d13, newsz, sizeof(*d13));
+ memset(p + szchanges, 0, incr * sizeof(*d13));
+ d13 = p;
+ p = xrealloc(d23, newsz, sizeof(*d23));
+ memset(p + szchanges, 0, incr * sizeof(*d23));
+ d23 = p;
+ p = xrealloc(de, newsz, sizeof(*de));
+ memset(p + szchanges, 0, incr * sizeof(*de));
+ de = p;
+ q = xrealloc(overlap, newsz, sizeof(*overlap));
+ memset(q + szchanges, 0, incr * sizeof(*overlap));
+ overlap = q;
+ szchanges = newsz;
+}
diff --git a/usr.bin/rcs/includes.h b/usr.bin/rcs/includes.h
new file mode 100644
index 00000000000..1a8716f1994
--- /dev/null
+++ b/usr.bin/rcs/includes.h
@@ -0,0 +1,60 @@
+/* $OpenBSD: includes.h,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2005 Xavier Santolaria <xsa@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#ifndef INCLUDES_H
+#define INCLUDES_H
+
+#include <sys/time.h>
+#include <sys/timeb.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <libgen.h>
+#include <md5.h>
+#include <pwd.h>
+#include <regex.h>
+#include <search.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#endif /* INCLUDES_H */
diff --git a/usr.bin/rcs/rcs.c b/usr.bin/rcs/rcs.c
new file mode 100644
index 00000000000..21f0ff6ce1d
--- /dev/null
+++ b/usr.bin/rcs/rcs.c
@@ -0,0 +1,2922 @@
+/* $OpenBSD: rcs.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#include "includes.h"
+
+#include "diff.h"
+#include "util.h"
+#include "rcs.h"
+#include "rcsprog.h"
+#include "xmalloc.h"
+
+#define RCS_BUFSIZE 16384
+#define RCS_BUFEXTSIZE 8192
+#define RCS_KWEXP_SIZE 1024
+
+/* RCS token types */
+#define RCS_TOK_ERR -1
+#define RCS_TOK_EOF 0
+#define RCS_TOK_NUM 1
+#define RCS_TOK_ID 2
+#define RCS_TOK_STRING 3
+#define RCS_TOK_SCOLON 4
+#define RCS_TOK_COLON 5
+
+#define RCS_TOK_HEAD 8
+#define RCS_TOK_BRANCH 9
+#define RCS_TOK_ACCESS 10
+#define RCS_TOK_SYMBOLS 11
+#define RCS_TOK_LOCKS 12
+#define RCS_TOK_COMMENT 13
+#define RCS_TOK_EXPAND 14
+#define RCS_TOK_DATE 15
+#define RCS_TOK_AUTHOR 16
+#define RCS_TOK_STATE 17
+#define RCS_TOK_NEXT 18
+#define RCS_TOK_BRANCHES 19
+#define RCS_TOK_DESC 20
+#define RCS_TOK_LOG 21
+#define RCS_TOK_TEXT 22
+#define RCS_TOK_STRICT 23
+
+#define RCS_ISKEY(t) (((t) >= RCS_TOK_HEAD) && ((t) <= RCS_TOK_BRANCHES))
+
+#define RCS_NOSCOL 0x01 /* no terminating semi-colon */
+#define RCS_VOPT 0x02 /* value is optional */
+
+/* opaque parse data */
+struct rcs_pdata {
+ u_int rp_lines;
+
+ char *rp_buf;
+ size_t rp_blen;
+ char *rp_bufend;
+ size_t rp_tlen;
+
+ /* pushback token buffer */
+ char rp_ptok[128];
+ int rp_pttype; /* token type, RCS_TOK_ERR if no token */
+
+ FILE *rp_file;
+};
+
+#define RCS_TOKSTR(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_buf
+#define RCS_TOKLEN(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_tlen
+
+/* invalid characters in RCS symbol names */
+static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
+
+/* comment leaders, depending on the file's suffix */
+static const struct rcs_comment {
+ const char *rc_suffix;
+ const char *rc_cstr;
+} rcs_comments[] = {
+ { "1", ".\\\" " },
+ { "2", ".\\\" " },
+ { "3", ".\\\" " },
+ { "4", ".\\\" " },
+ { "5", ".\\\" " },
+ { "6", ".\\\" " },
+ { "7", ".\\\" " },
+ { "8", ".\\\" " },
+ { "9", ".\\\" " },
+ { "a", "-- " }, /* Ada */
+ { "ada", "-- " },
+ { "adb", "-- " },
+ { "asm", ";; " }, /* assembler (MS-DOS) */
+ { "ads", "-- " }, /* Ada */
+ { "bat", ":: " }, /* batch (MS-DOS) */
+ { "body", "-- " }, /* Ada */
+ { "c", " * " }, /* C */
+ { "c++", "// " }, /* C++ */
+ { "cc", "// " },
+ { "cpp", "// " },
+ { "cxx", "// " },
+ { "m", "// " }, /* Objective-C */
+ { "cl", ";;; " }, /* Common Lisp */
+ { "cmd", ":: " }, /* command (OS/2) */
+ { "cmf", "c " }, /* CM Fortran */
+ { "csh", "# " }, /* shell */
+ { "e", "# " }, /* efl */
+ { "epsf", "% " }, /* encapsulated postscript */
+ { "epsi", "% " }, /* encapsulated postscript */
+ { "el", "; " }, /* Emacs Lisp */
+ { "f", "c " }, /* Fortran */
+ { "for", "c " },
+ { "h", " * " }, /* C-header */
+ { "hh", "// " }, /* C++ header */
+ { "hpp", "// " },
+ { "hxx", "// " },
+ { "in", "# " }, /* for Makefile.in */
+ { "l", " * " }, /* lex */
+ { "mac", ";; " }, /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
+ { "mak", "# " }, /* makefile, e.g. Visual C++ */
+ { "me", ".\\\" " }, /* me-macros t/nroff */
+ { "ml", "; " }, /* mocklisp */
+ { "mm", ".\\\" " }, /* mm-macros t/nroff */
+ { "ms", ".\\\" " }, /* ms-macros t/nroff */
+ { "man", ".\\\" " }, /* man-macros t/nroff */
+ { "p", " * " }, /* pascal */
+ { "pas", " * " },
+ { "pl", "# " }, /* Perl (conflict with Prolog) */
+ { "pm", "# " }, /* Perl module */
+ { "ps", "% " }, /* postscript */
+ { "psw", "% " }, /* postscript wrap */
+ { "pswm", "% " }, /* postscript wrap */
+ { "r", "# " }, /* ratfor */
+ { "rc", " * " }, /* Microsoft Windows resource file */
+ { "red", "% " }, /* psl/rlisp */
+ { "sh", "# " }, /* shell */
+ { "sl", "% " }, /* psl */
+ { "spec", "-- " }, /* Ada */
+ { "tex", "% " }, /* tex */
+ { "y", " * " }, /* yacc */
+ { "ye", " * " }, /* yacc-efl */
+ { "yr", " * " }, /* yacc-ratfor */
+};
+
+struct rcs_kw rcs_expkw[] = {
+ { "Author", RCS_KW_AUTHOR },
+ { "Date", RCS_KW_DATE },
+ { "Header", RCS_KW_HEADER },
+ { "Id", RCS_KW_ID },
+ { "Log", RCS_KW_LOG },
+ { "Name", RCS_KW_NAME },
+ { "RCSfile", RCS_KW_RCSFILE },
+ { "Revision", RCS_KW_REVISION },
+ { "Source", RCS_KW_SOURCE },
+ { "State", RCS_KW_STATE },
+};
+
+#define NB_COMTYPES (sizeof(rcs_comments)/sizeof(rcs_comments[0]))
+
+static struct rcs_key {
+ char rk_str[16];
+ int rk_id;
+ int rk_val;
+ int rk_flags;
+} rcs_keys[] = {
+ { "access", RCS_TOK_ACCESS, RCS_TOK_ID, RCS_VOPT },
+ { "author", RCS_TOK_AUTHOR, RCS_TOK_ID, 0 },
+ { "branch", RCS_TOK_BRANCH, RCS_TOK_NUM, RCS_VOPT },
+ { "branches", RCS_TOK_BRANCHES, RCS_TOK_NUM, RCS_VOPT },
+ { "comment", RCS_TOK_COMMENT, RCS_TOK_STRING, RCS_VOPT },
+ { "date", RCS_TOK_DATE, RCS_TOK_NUM, 0 },
+ { "desc", RCS_TOK_DESC, RCS_TOK_STRING, RCS_NOSCOL },
+ { "expand", RCS_TOK_EXPAND, RCS_TOK_STRING, RCS_VOPT },
+ { "head", RCS_TOK_HEAD, RCS_TOK_NUM, RCS_VOPT },
+ { "locks", RCS_TOK_LOCKS, RCS_TOK_ID, 0 },
+ { "log", RCS_TOK_LOG, RCS_TOK_STRING, RCS_NOSCOL },
+ { "next", RCS_TOK_NEXT, RCS_TOK_NUM, RCS_VOPT },
+ { "state", RCS_TOK_STATE, RCS_TOK_ID, RCS_VOPT },
+ { "strict", RCS_TOK_STRICT, 0, 0, },
+ { "symbols", RCS_TOK_SYMBOLS, 0, 0 },
+ { "text", RCS_TOK_TEXT, RCS_TOK_STRING, RCS_NOSCOL },
+};
+
+#define RCS_NKEYS (sizeof(rcs_keys)/sizeof(rcs_keys[0]))
+
+static const char *rcs_errstrs[] = {
+ "No error",
+ "No such entry",
+ "Duplicate entry found",
+ "Bad RCS number",
+ "Invalid RCS symbol",
+ "Parse error",
+};
+
+#define RCS_NERR (sizeof(rcs_errstrs)/sizeof(rcs_errstrs[0]))
+
+int rcs_errno = RCS_ERR_NOERR;
+char *timezone_flag = NULL;
+
+int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *);
+static void rcs_parse_init(RCSFILE *);
+static int rcs_parse_admin(RCSFILE *);
+static int rcs_parse_delta(RCSFILE *);
+static void rcs_parse_deltas(RCSFILE *, RCSNUM *);
+static int rcs_parse_deltatext(RCSFILE *);
+static void rcs_parse_deltatexts(RCSFILE *, RCSNUM *);
+static void rcs_parse_desc(RCSFILE *, RCSNUM *);
+
+static int rcs_parse_access(RCSFILE *);
+static int rcs_parse_symbols(RCSFILE *);
+static int rcs_parse_locks(RCSFILE *);
+static int rcs_parse_branches(RCSFILE *, struct rcs_delta *);
+static void rcs_freedelta(struct rcs_delta *);
+static void rcs_freepdata(struct rcs_pdata *);
+static int rcs_gettok(RCSFILE *);
+static int rcs_pushtok(RCSFILE *, const char *, int);
+static void rcs_growbuf(RCSFILE *);
+static void rcs_strprint(const u_char *, size_t, FILE *);
+
+static char* rcs_expand_keywords(char *, struct rcs_delta *, char *,
+ size_t, int);
+
+/*
+ * rcs_open()
+ *
+ * Open a file containing RCS-formatted information. The file's path is
+ * given in <path>, and the opening flags are given in <flags>, which is either
+ * RCS_READ, RCS_WRITE, or RCS_RDWR. If the open requests write access and
+ * the file does not exist, the RCS_CREATE flag must also be given, in which
+ * case it will be created with the mode specified in a third argument of
+ * type mode_t. If the file exists and RCS_CREATE is passed, the open will
+ * fail.
+ * Returns a handle to the opened file on success, or NULL on failure.
+ */
+RCSFILE *
+rcs_open(const char *path, int flags, ...)
+{
+ int ret, mode;
+ mode_t fmode;
+ RCSFILE *rfp;
+ struct stat st;
+ va_list vap;
+ struct rcs_delta *rdp;
+ struct rcs_lock *lkr;
+
+ fmode = S_IRUSR|S_IRGRP|S_IROTH;
+ flags &= 0xffff; /* ditch any internal flags */
+
+ if (((ret = stat(path, &st)) == -1) && errno == ENOENT) {
+ if (flags & RCS_CREATE) {
+ va_start(vap, flags);
+ mode = va_arg(vap, int);
+ va_end(vap);
+ fmode = (mode_t)mode;
+ } else {
+ rcs_errno = RCS_ERR_NOENT;
+ return (NULL);
+ }
+ } else if (ret == 0 && (flags & RCS_CREATE)) {
+ warnx("RCS file `%s' exists", path);
+ return (NULL);
+ }
+
+ rfp = xcalloc(1, sizeof(*rfp));
+
+ rfp->rf_path = xstrdup(path);
+ rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
+ rfp->rf_mode = fmode;
+
+ TAILQ_INIT(&(rfp->rf_delta));
+ TAILQ_INIT(&(rfp->rf_access));
+ TAILQ_INIT(&(rfp->rf_symbols));
+ TAILQ_INIT(&(rfp->rf_locks));
+
+ if (!(rfp->rf_flags & RCS_CREATE))
+ rcs_parse_init(rfp);
+
+ /* fill in rd_locker */
+ TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
+ if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
+ rcs_close(rfp);
+ return (NULL);
+ }
+
+ rdp->rd_locker = xstrdup(lkr->rl_name);
+ }
+
+ return (rfp);
+}
+
+/*
+ * rcs_close()
+ *
+ * Close an RCS file handle.
+ */
+void
+rcs_close(RCSFILE *rfp)
+{
+ struct rcs_delta *rdp;
+ struct rcs_access *rap;
+ struct rcs_lock *rlp;
+ struct rcs_sym *rsp;
+
+ if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
+ rcs_write(rfp);
+
+ while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
+ rdp = TAILQ_FIRST(&(rfp->rf_delta));
+ TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
+ rcs_freedelta(rdp);
+ }
+
+ while (!TAILQ_EMPTY(&(rfp->rf_access))) {
+ rap = TAILQ_FIRST(&(rfp->rf_access));
+ TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
+ xfree(rap->ra_name);
+ xfree(rap);
+ }
+
+ while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
+ rsp = TAILQ_FIRST(&(rfp->rf_symbols));
+ TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
+ rcsnum_free(rsp->rs_num);
+ xfree(rsp->rs_name);
+ xfree(rsp);
+ }
+
+ while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
+ rlp = TAILQ_FIRST(&(rfp->rf_locks));
+ TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
+ rcsnum_free(rlp->rl_num);
+ xfree(rlp->rl_name);
+ xfree(rlp);
+ }
+
+ if (rfp->rf_head != NULL)
+ rcsnum_free(rfp->rf_head);
+ if (rfp->rf_branch != NULL)
+ rcsnum_free(rfp->rf_branch);
+
+ if (rfp->rf_path != NULL)
+ xfree(rfp->rf_path);
+ if (rfp->rf_comment != NULL)
+ xfree(rfp->rf_comment);
+ if (rfp->rf_expand != NULL)
+ xfree(rfp->rf_expand);
+ if (rfp->rf_desc != NULL)
+ xfree(rfp->rf_desc);
+ if (rfp->rf_pdata != NULL)
+ rcs_freepdata(rfp->rf_pdata);
+ xfree(rfp);
+}
+
+/*
+ * rcs_write()
+ *
+ * Write the contents of the RCS file handle <rfp> to disk in the file whose
+ * path is in <rf_path>.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_write(RCSFILE *rfp)
+{
+ FILE *fp;
+ char buf[1024], numbuf[64], fn[19] = "";
+ void *bp;
+ struct rcs_access *ap;
+ struct rcs_sym *symp;
+ struct rcs_branch *brp;
+ struct rcs_delta *rdp;
+ struct rcs_lock *lkp;
+ ssize_t nread, nwritten;
+ size_t len;
+ int fd, from_fd, to_fd;
+
+ from_fd = to_fd = fd = -1;
+
+ if (rfp->rf_flags & RCS_SYNCED)
+ return (0);
+
+ /* Write operations need the whole file parsed */
+ rcs_parse_deltatexts(rfp, NULL);
+
+ strlcpy(fn, "/tmp/rcs.XXXXXXXXXX", sizeof(fn));
+ if ((fd = mkstemp(fn)) == -1)
+ err(1, "mkstemp: `%s'", fn);
+
+ if ((fp = fdopen(fd, "w+")) == NULL) {
+ fd = errno;
+ unlink(fn);
+ err(1, "fdopen: %s", fn);
+ }
+
+ if (rfp->rf_head != NULL)
+ rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
+ else
+ numbuf[0] = '\0';
+
+ fprintf(fp, "head\t%s;\n", numbuf);
+
+ if (rfp->rf_branch != NULL) {
+ rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
+ fprintf(fp, "branch\t%s;\n", numbuf);
+ }
+
+ fputs("access", fp);
+ TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
+ fprintf(fp, "\n\t%s", ap->ra_name);
+ }
+ fputs(";\n", fp);
+
+ fprintf(fp, "symbols");
+ TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
+ rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
+ strlcpy(buf, symp->rs_name, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+ strlcat(buf, numbuf, sizeof(buf));
+ fprintf(fp, "\n\t%s", buf);
+ }
+ fprintf(fp, ";\n");
+
+ fprintf(fp, "locks");
+ TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
+ rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
+ fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
+ }
+
+ fprintf(fp, ";");
+
+ if (rfp->rf_flags & RCS_SLOCK)
+ fprintf(fp, " strict;");
+ fputc('\n', fp);
+
+ fputs("comment\t@", fp);
+ if (rfp->rf_comment != NULL) {
+ rcs_strprint((const u_char *)rfp->rf_comment,
+ strlen(rfp->rf_comment), fp);
+ fputs("@;\n", fp);
+ } else
+ fputs("# @;\n", fp);
+
+ if (rfp->rf_expand != NULL) {
+ fputs("expand @", fp);
+ rcs_strprint((const u_char *)rfp->rf_expand,
+ strlen(rfp->rf_expand), fp);
+ fputs("@;\n", fp);
+ }
+
+ fputs("\n\n", fp);
+
+ TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
+ fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
+ sizeof(numbuf)));
+ fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
+ rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
+ rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
+ rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
+ fprintf(fp, "\tauthor %s;\tstate %s;\n",
+ rdp->rd_author, rdp->rd_state);
+ fputs("branches", fp);
+ TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
+ fprintf(fp, " %s", rcsnum_tostr(brp->rb_num, numbuf,
+ sizeof(numbuf)));
+ }
+ fputs(";\n", fp);
+ fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
+ numbuf, sizeof(numbuf)));
+ }
+
+ fputs("\ndesc\n@", fp);
+ if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
+ rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
+ if (rfp->rf_desc[len-1] != '\n')
+ fputc('\n', fp);
+ }
+ fputs("@\n", fp);
+
+ /* deltatexts */
+ TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
+ fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
+ sizeof(numbuf)));
+ fputs("log\n@", fp);
+ if (rdp->rd_log != NULL) {
+ len = strlen(rdp->rd_log);
+ rcs_strprint((const u_char *)rdp->rd_log, len, fp);
+ if (rdp->rd_log[len-1] != '\n')
+ fputc('\n', fp);
+ }
+ fputs("@\ntext\n@", fp);
+ if (rdp->rd_text != NULL) {
+ rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
+
+ if (rdp->rd_tlen != 0) {
+ if (rdp->rd_text[rdp->rd_tlen-1] != '\n')
+ fputc('\n', fp);
+ }
+ }
+ fputs("@\n", fp);
+ }
+ fclose(fp);
+
+ /*
+ * We try to use rename() to atomically put the new file in place.
+ * If that fails, we try a copy.
+ */
+ if (rename(fn, rfp->rf_path) == -1) {
+ if (errno == EXDEV) {
+ /* rename() not supported so we have to copy. */
+ if (chmod(rfp->rf_path, S_IWUSR) == -1 &&
+ !(rfp->rf_flags & RCS_CREATE)) {
+ errx(1, "chmod(%s, 0%o) failed",
+ rfp->rf_path, S_IWUSR);
+ }
+
+ if ((from_fd = open(fn, O_RDONLY)) == -1) {
+ warn("failed to open `%s'",
+ rfp->rf_path);
+ return (-1);
+ }
+
+ if ((to_fd = open(rfp->rf_path,
+ O_WRONLY|O_TRUNC|O_CREAT)) == -1) {
+ warn("failed to open `%s'", fn);
+ close(from_fd);
+ return (-1);
+ }
+
+ bp = xmalloc(MAXBSIZE);
+ for (;;) {
+ if ((nread = read(from_fd, bp, MAXBSIZE)) == 0)
+ break;
+ if (nread == -1)
+ goto err;
+ nwritten = write(to_fd, bp, (size_t)nread);
+ if (nwritten == -1 || nwritten != nread)
+ goto err;
+ }
+
+ if (nread < 0) {
+err: if (unlink(rfp->rf_path) == -1)
+ warn("failed to unlink `%s'",
+ rfp->rf_path);
+ close(from_fd);
+ close(to_fd);
+ xfree(bp);
+ return (-1);
+ }
+
+ close(from_fd);
+ close(to_fd);
+ xfree(bp);
+
+ if (unlink(fn) == -1) {
+ warn("failed to unlink `%s'", fn);
+ return (-1);
+ }
+ } else {
+ warn("failed to access temp RCS output file");
+ return (-1);
+ }
+ }
+
+ if (chmod(rfp->rf_path, rfp->rf_mode) == -1) {
+ warn("failed to chmod `%s'", rfp->rf_path);
+ return (-1);
+ }
+
+ rfp->rf_flags |= RCS_SYNCED;
+
+ return (0);
+}
+
+/*
+ * rcs_head_get()
+ *
+ * Retrieve the revision number of the head revision for the RCS file <file>.
+ */
+const RCSNUM *
+rcs_head_get(RCSFILE *file)
+{
+ return (file->rf_head);
+}
+
+/*
+ * rcs_head_set()
+ *
+ * Set the revision number of the head revision for the RCS file <file> to
+ * <rev>, which must reference a valid revision within the file.
+ */
+int
+rcs_head_set(RCSFILE *file, RCSNUM *rev)
+{
+ if (rcs_findrev(file, rev) == NULL)
+ return (-1);
+
+ if (file->rf_head == NULL)
+ file->rf_head = rcsnum_alloc();
+
+ rcsnum_cpy(rev, file->rf_head, 0);
+ file->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+
+
+/*
+ * rcs_branch_get()
+ *
+ * Retrieve the default branch number for the RCS file <file>.
+ * Returns the number on success. If NULL is returned, then there is no
+ * default branch for this file.
+ */
+const RCSNUM *
+rcs_branch_get(RCSFILE *file)
+{
+ return (file->rf_branch);
+}
+
+/*
+ * rcs_branch_set()
+ *
+ * Set the default branch for the RCS file <file> to <bnum>.
+ * Returns 0 on success, -1 on failure.
+ */
+int
+rcs_branch_set(RCSFILE *file, const RCSNUM *bnum)
+{
+ if (file->rf_branch == NULL)
+ file->rf_branch = rcsnum_alloc();
+
+ rcsnum_cpy(bnum, file->rf_branch, 0);
+ file->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+
+/*
+ * rcs_access_add()
+ *
+ * Add the login name <login> to the access list for the RCS file <file>.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_access_add(RCSFILE *file, const char *login)
+{
+ struct rcs_access *ap;
+
+ /* first look for duplication */
+ TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
+ if (strcmp(ap->ra_name, login) == 0) {
+ rcs_errno = RCS_ERR_DUPENT;
+ return (-1);
+ }
+ }
+
+ ap = xmalloc(sizeof(*ap));
+ ap->ra_name = xstrdup(login);
+ TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
+
+ /* not synced anymore */
+ file->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+
+/*
+ * rcs_access_remove()
+ *
+ * Remove an entry with login name <login> from the access list of the RCS
+ * file <file>.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_access_remove(RCSFILE *file, const char *login)
+{
+ struct rcs_access *ap;
+
+ TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
+ if (strcmp(ap->ra_name, login) == 0)
+ break;
+
+ if (ap == NULL) {
+ rcs_errno = RCS_ERR_NOENT;
+ return (-1);
+ }
+
+ TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
+ xfree(ap->ra_name);
+ xfree(ap);
+
+ /* not synced anymore */
+ file->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+
+/*
+ * rcs_sym_add()
+ *
+ * Add a symbol to the list of symbols for the RCS file <rfp>. The new symbol
+ * is named <sym> and is bound to the RCS revision <snum>.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
+{
+ struct rcs_sym *symp;
+
+ if (!rcs_sym_check(sym)) {
+ rcs_errno = RCS_ERR_BADSYM;
+ return (-1);
+ }
+
+ /* first look for duplication */
+ TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
+ if (strcmp(symp->rs_name, sym) == 0) {
+ rcs_errno = RCS_ERR_DUPENT;
+ return (-1);
+ }
+ }
+
+ symp = xmalloc(sizeof(*symp));
+ symp->rs_name = xstrdup(sym);
+ symp->rs_num = rcsnum_alloc();
+ rcsnum_cpy(snum, symp->rs_num, 0);
+
+ TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
+
+ /* not synced anymore */
+ rfp->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+
+/*
+ * rcs_sym_remove()
+ *
+ * Remove the symbol with name <sym> from the symbol list for the RCS file
+ * <file>. If no such symbol is found, the call fails and returns with an
+ * error.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_sym_remove(RCSFILE *file, const char *sym)
+{
+ struct rcs_sym *symp;
+
+ if (!rcs_sym_check(sym)) {
+ rcs_errno = RCS_ERR_BADSYM;
+ return (-1);
+ }
+
+ TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
+ if (strcmp(symp->rs_name, sym) == 0)
+ break;
+
+ if (symp == NULL) {
+ rcs_errno = RCS_ERR_NOENT;
+ return (-1);
+ }
+
+ TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
+ xfree(symp->rs_name);
+ rcsnum_free(symp->rs_num);
+ xfree(symp);
+
+ /* not synced anymore */
+ file->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+
+/*
+ * rcs_sym_getrev()
+ *
+ * Retrieve the RCS revision number associated with the symbol <sym> for the
+ * RCS file <file>. The returned value is a dynamically-allocated copy and
+ * should be freed by the caller once they are done with it.
+ * Returns the RCSNUM on success, or NULL on failure.
+ */
+RCSNUM *
+rcs_sym_getrev(RCSFILE *file, const char *sym)
+{
+ RCSNUM *num;
+ struct rcs_sym *symp;
+
+ if (!rcs_sym_check(sym)) {
+ rcs_errno = RCS_ERR_BADSYM;
+ return (NULL);
+ }
+
+ num = NULL;
+ TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
+ if (strcmp(symp->rs_name, sym) == 0)
+ break;
+
+ if (symp == NULL) {
+ rcs_errno = RCS_ERR_NOENT;
+ } else {
+ num = rcsnum_alloc();
+ rcsnum_cpy(symp->rs_num, num, 0);
+ }
+
+ return (num);
+}
+
+/*
+ * rcs_sym_check()
+ *
+ * Check the RCS symbol name <sym> for any unsupported characters.
+ * Returns 1 if the tag is correct, 0 if it isn't valid.
+ */
+int
+rcs_sym_check(const char *sym)
+{
+ int ret;
+ const char *cp;
+
+ ret = 1;
+ cp = sym;
+ if (!isalpha(*cp++))
+ return (0);
+
+ for (; *cp != '\0'; cp++)
+ if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
+ ret = 0;
+ break;
+ }
+
+ return (ret);
+}
+
+/*
+ * rcs_lock_getmode()
+ *
+ * Retrieve the locking mode of the RCS file <file>.
+ */
+int
+rcs_lock_getmode(RCSFILE *file)
+{
+ return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
+}
+
+/*
+ * rcs_lock_setmode()
+ *
+ * Set the locking mode of the RCS file <file> to <mode>, which must either
+ * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
+ * Returns the previous mode on success, or -1 on failure.
+ */
+int
+rcs_lock_setmode(RCSFILE *file, int mode)
+{
+ int pmode;
+ pmode = rcs_lock_getmode(file);
+
+ if (mode == RCS_LOCK_STRICT)
+ file->rf_flags |= RCS_SLOCK;
+ else if (mode == RCS_LOCK_LOOSE)
+ file->rf_flags &= ~RCS_SLOCK;
+ else
+ errx(1, "rcs_lock_setmode: invalid mode `%d'", mode);
+
+ file->rf_flags &= ~RCS_SYNCED;
+ return (pmode);
+}
+
+/*
+ * rcs_lock_add()
+ *
+ * Add an RCS lock for the user <user> on revision <rev>.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
+{
+ struct rcs_lock *lkp;
+
+ /* first look for duplication */
+ TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
+ if (strcmp(lkp->rl_name, user) == 0 &&
+ rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
+ rcs_errno = RCS_ERR_DUPENT;
+ return (-1);
+ }
+ }
+
+ lkp = xmalloc(sizeof(*lkp));
+ lkp->rl_name = xstrdup(user);
+ lkp->rl_num = rcsnum_alloc();
+ rcsnum_cpy(rev, lkp->rl_num, 0);
+
+ TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
+
+ /* not synced anymore */
+ file->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+
+
+/*
+ * rcs_lock_remove()
+ *
+ * Remove the RCS lock on revision <rev>.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
+{
+ struct rcs_lock *lkp;
+
+ TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
+ if (strcmp(lkp->rl_name, user) == 0 &&
+ rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
+ break;
+ }
+
+ if (lkp == NULL) {
+ rcs_errno = RCS_ERR_NOENT;
+ return (-1);
+ }
+
+ TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
+ rcsnum_free(lkp->rl_num);
+ xfree(lkp->rl_name);
+ xfree(lkp);
+
+ /* not synced anymore */
+ file->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+
+/*
+ * rcs_desc_get()
+ *
+ * Retrieve the description for the RCS file <file>.
+ */
+const char *
+rcs_desc_get(RCSFILE *file)
+{
+ return (file->rf_desc);
+}
+
+/*
+ * rcs_desc_set()
+ *
+ * Set the description for the RCS file <file>.
+ */
+void
+rcs_desc_set(RCSFILE *file, const char *desc)
+{
+ char *tmp;
+
+ tmp = xstrdup(desc);
+ if (file->rf_desc != NULL)
+ xfree(file->rf_desc);
+ file->rf_desc = tmp;
+ file->rf_flags &= ~RCS_SYNCED;
+}
+
+/*
+ * rcs_comment_lookup()
+ *
+ * Lookup the assumed comment leader based on a file's suffix.
+ * Returns a pointer to the string on success, or NULL on failure.
+ */
+const char *
+rcs_comment_lookup(const char *filename)
+{
+ int i;
+ const char *sp;
+
+ if ((sp = strrchr(filename, '.')) == NULL) {
+ rcs_errno = RCS_ERR_NOENT;
+ return (NULL);
+ }
+ sp++;
+
+ for (i = 0; i < (int)NB_COMTYPES; i++)
+ if (strcmp(rcs_comments[i].rc_suffix, sp) == 0)
+ return (rcs_comments[i].rc_cstr);
+ return (NULL);
+}
+
+/*
+ * rcs_comment_get()
+ *
+ * Retrieve the comment leader for the RCS file <file>.
+ */
+const char *
+rcs_comment_get(RCSFILE *file)
+{
+ return (file->rf_comment);
+}
+
+/*
+ * rcs_comment_set()
+ *
+ * Set the comment leader for the RCS file <file>.
+ */
+void
+rcs_comment_set(RCSFILE *file, const char *comment)
+{
+ char *tmp;
+
+ tmp = xstrdup(comment);
+ if (file->rf_comment != NULL)
+ xfree(file->rf_comment);
+ file->rf_comment = tmp;
+ file->rf_flags &= ~RCS_SYNCED;
+}
+
+/*
+ * rcs_tag_resolve()
+ *
+ * Retrieve the revision number corresponding to the tag <tag> for the RCS
+ * file <file>.
+ */
+RCSNUM *
+rcs_tag_resolve(RCSFILE *file, const char *tag)
+{
+ RCSNUM *num;
+
+ if ((num = rcsnum_parse(tag)) == NULL) {
+ num = rcs_sym_getrev(file, tag);
+ }
+
+ return (num);
+}
+
+int
+rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
+{
+ char op, *ep;
+ struct rcs_line *lp, *dlp, *ndlp;
+ int i, lineno, nbln;
+
+ dlp = TAILQ_FIRST(&(dlines->l_lines));
+ lp = TAILQ_FIRST(&(plines->l_lines));
+
+ /* skip first bogus line */
+ for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
+ lp = TAILQ_NEXT(lp, l_list)) {
+ op = *(lp->l_line);
+ lineno = (int)strtol((lp->l_line + 1), &ep, 10);
+ if (lineno > dlines->l_nblines || lineno < 0 ||
+ *ep != ' ')
+ errx(1, "invalid line specification in RCS patch");
+ ep++;
+ nbln = (int)strtol(ep, &ep, 10);
+ if (nbln < 0 || *ep != '\0')
+ errx(1,
+ "invalid line number specification in RCS patch");
+
+ /* find the appropriate line */
+ for (;;) {
+ if (dlp == NULL)
+ break;
+ if (dlp->l_lineno == lineno)
+ break;
+ if (dlp->l_lineno > lineno) {
+ dlp = TAILQ_PREV(dlp, rcs_tqh, l_list);
+ } else if (dlp->l_lineno < lineno) {
+ if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
+ ndlp->l_lineno > lineno)
+ break;
+ dlp = ndlp;
+ }
+ }
+ if (dlp == NULL)
+ errx(1, "can't find referenced line in RCS patch");
+
+ if (op == 'd') {
+ for (i = 0; (i < nbln) && (dlp != NULL); i++) {
+ ndlp = TAILQ_NEXT(dlp, l_list);
+ TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
+ dlp = ndlp;
+ /* last line is gone - reset dlp */
+ if (dlp == NULL) {
+ ndlp = TAILQ_LAST(&(dlines->l_lines),
+ rcs_tqh);
+ dlp = ndlp;
+ }
+ }
+ } else if (op == 'a') {
+ for (i = 0; i < nbln; i++) {
+ ndlp = lp;
+ lp = TAILQ_NEXT(lp, l_list);
+ if (lp == NULL)
+ errx(1, "truncated RCS patch");
+ TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
+ TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
+ lp, l_list);
+ dlp = lp;
+
+ /* we don't want lookup to block on those */
+ lp->l_lineno = lineno;
+
+ lp = ndlp;
+ }
+ } else
+ errx(1, "unknown RCS patch operation `%c'", op);
+
+ /* last line of the patch, done */
+ if (lp->l_lineno == plines->l_nblines)
+ break;
+ }
+
+ /* once we're done patching, rebuild the line numbers */
+ lineno = 0;
+ TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
+ lp->l_lineno = lineno++;
+ dlines->l_nblines = lineno - 1;
+
+ return (0);
+}
+
+/*
+ * rcs_getrev()
+ *
+ * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The
+ * returned buffer is dynamically allocated and should be released using
+ * rcs_buf_free() once the caller is done using it.
+ */
+BUF*
+rcs_getrev(RCSFILE *rfp, RCSNUM *frev)
+{
+ u_int i, numlen;
+ int isbranch, lookonbranch;
+ size_t len;
+ void *bp;
+ RCSNUM *crev, *rev, *brev;
+ BUF *rbuf;
+ struct rcs_delta *rdp = NULL;
+ struct rcs_branch *rb;
+
+ if (rfp->rf_head == NULL)
+ return (NULL);
+
+ if (frev == RCS_HEAD_REV)
+ rev = rfp->rf_head;
+ else
+ rev = frev;
+
+ /* XXX rcsnum_cmp() */
+ for (i = 0; i < rfp->rf_head->rn_len; i++) {
+ if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) {
+ rcs_errno = RCS_ERR_NOENT;
+ return (NULL);
+ }
+ }
+
+ /* No matter what, we're going to need up the the description parsed */
+ rcs_parse_desc(rfp, NULL);
+
+ rdp = rcs_findrev(rfp, rfp->rf_head);
+ if (rdp == NULL) {
+ warnx("failed to get RCS HEAD revision");
+ return (NULL);
+ }
+
+ if (rdp->rd_tlen == 0)
+ rcs_parse_deltatexts(rfp, rfp->rf_head);
+
+ len = rdp->rd_tlen;
+ if (len == 0) {
+ rbuf = rcs_buf_alloc(1, 0);
+ rcs_buf_empty(rbuf);
+ return (rbuf);
+ }
+
+ rbuf = rcs_buf_alloc(len, BUF_AUTOEXT);
+ rcs_buf_append(rbuf, rdp->rd_text, len);
+
+ isbranch = 0;
+ brev = NULL;
+
+ /*
+ * If a branch was passed, get the latest revision on it.
+ */
+ if (RCSNUM_ISBRANCH(rev)) {
+ brev = rev;
+ rdp = rcs_findrev(rfp, rev);
+ if (rdp == NULL)
+ return (NULL);
+
+ rev = rdp->rd_num;
+ } else {
+ if (RCSNUM_ISBRANCHREV(rev)) {
+ brev = rcsnum_revtobr(rev);
+ isbranch = 1;
+ }
+ }
+
+ lookonbranch = 0;
+ crev = NULL;
+
+ /* Apply patches backwards to get the right version.
+ */
+ do {
+ if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0)
+ break;
+
+ if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len &&
+ !TAILQ_EMPTY(&(rdp->rd_branches)))
+ lookonbranch = 1;
+
+ if (isbranch && lookonbranch == 1) {
+ lookonbranch = 0;
+ TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
+ /* XXX rcsnum_cmp() is totally broken for
+ * this purpose.
+ */
+ numlen = MIN(brev->rn_len, rb->rb_num->rn_len);
+ for (i = 0; i < numlen; i++) {
+ if (rb->rb_num->rn_id[i] !=
+ brev->rn_id[i])
+ break;
+ }
+
+ if (i == numlen) {
+ crev = rb->rb_num;
+ break;
+ }
+ }
+ } else {
+ crev = rdp->rd_next;
+ }
+
+ rdp = rcs_findrev(rfp, crev);
+ if (rdp == NULL) {
+ rcs_buf_free(rbuf);
+ return (NULL);
+ }
+
+ rcs_buf_putc(rbuf, '\0');
+
+ /* check if we have parsed this rev's deltatext */
+ if (rdp->rd_tlen == 0)
+ rcs_parse_deltatexts(rfp, rdp->rd_num);
+
+ bp = rcs_buf_release(rbuf);
+ rbuf = rcs_patchfile((char *)bp, (char *)rdp->rd_text,
+ rcs_patch_lines);
+ xfree(bp);
+
+ if (rbuf == NULL)
+ break;
+ } while (rcsnum_cmp(crev, rev, 0) != 0);
+
+ if (rcs_buf_getc(rbuf, rcs_buf_len(rbuf)-1) != '\n' &&
+ rbuf != NULL)
+ rcs_buf_putc(rbuf, '\n');
+
+ return (rbuf);
+}
+
+/*
+ * rcs_rev_add()
+ *
+ * Add a revision to the RCS file <rf>. The new revision's number can be
+ * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
+ * new revision will have a number equal to the previous head revision plus
+ * one). The <msg> argument specifies the log message for that revision, and
+ * <date> specifies the revision's date (a value of -1 is
+ * equivalent to using the current time).
+ * If <username> is NULL, set the author for this revision to the current user.
+ * Otherwise, set it to <username>.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
+ const char *username)
+{
+ time_t now;
+ struct passwd *pw;
+ struct rcs_delta *ordp, *rdp;
+
+ if (rev == RCS_HEAD_REV) {
+ if (rf->rf_flags & RCS_CREATE) {
+ if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
+ return (-1);
+ rf->rf_head = rcsnum_alloc();
+ rcsnum_cpy(rev, rf->rf_head, 0);
+ } else {
+ rev = rcsnum_inc(rf->rf_head);
+ }
+ } else {
+ if ((rdp = rcs_findrev(rf, rev)) != NULL) {
+ rcs_errno = RCS_ERR_DUPENT;
+ return (-1);
+ }
+ }
+
+ if ((pw = getpwuid(getuid())) == NULL)
+ errx(1, "getpwuid failed");
+
+ rdp = xcalloc(1, sizeof(*rdp));
+
+ TAILQ_INIT(&(rdp->rd_branches));
+
+ rdp->rd_num = rcsnum_alloc();
+ rcsnum_cpy(rev, rdp->rd_num, 0);
+
+ rdp->rd_next = rcsnum_alloc();
+
+ if (!(rf->rf_flags & RCS_CREATE)) {
+ /* next should point to the previous HEAD */
+ ordp = TAILQ_FIRST(&(rf->rf_delta));
+ rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
+ }
+
+
+ if (username == NULL)
+ username = pw->pw_name;
+
+ rdp->rd_author = xstrdup(username);
+ rdp->rd_state = xstrdup(RCS_STATE_EXP);
+ rdp->rd_log = xstrdup(msg);
+
+ if (date != (time_t)(-1))
+ now = date;
+ else
+ time(&now);
+ gmtime_r(&now, &(rdp->rd_date));
+
+ TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
+ rf->rf_ndelta++;
+
+ /* not synced anymore */
+ rf->rf_flags &= ~RCS_SYNCED;
+
+ return (0);
+}
+
+/*
+ * rcs_rev_remove()
+ *
+ * Remove the revision whose number is <rev> from the RCS file <rf>.
+ */
+int
+rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
+{
+ size_t len;
+ char *tmpdir;
+ char *newdeltatext, path_tmp1[MAXPATHLEN], path_tmp2[MAXPATHLEN];
+ struct rcs_delta *rdp, *prevrdp, *nextrdp;
+ BUF *nextbuf, *prevbuf, *newdiff;
+
+ tmpdir = rcs_tmpdir;
+
+ if (rev == RCS_HEAD_REV)
+ rev = rf->rf_head;
+
+ /* do we actually have that revision? */
+ if ((rdp = rcs_findrev(rf, rev)) == NULL) {
+ rcs_errno = RCS_ERR_NOENT;
+ return (-1);
+ }
+
+ /*
+ * This is confusing, the previous delta is next in the TAILQ list.
+ * the next delta is the previous one in the TAILQ list.
+ *
+ * When the HEAD revision got specified, nextrdp will be NULL.
+ * When the first revision got specified, prevrdp will be NULL.
+ */
+ prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
+ nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, rcs_tqh, rd_list);
+
+ newdeltatext = NULL;
+ prevbuf = nextbuf = NULL;
+
+ if (prevrdp != NULL) {
+ if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL)
+ errx(1, "error getting revision");
+ }
+
+ if (prevrdp != NULL && nextrdp != NULL) {
+ if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL)
+ errx(1, "error getting revision");
+
+ newdiff = rcs_buf_alloc(64, BUF_AUTOEXT);
+
+ /* calculate new diff */
+ len = strlcpy(path_tmp1, tmpdir, sizeof(path_tmp1));
+ if (len >= sizeof(path_tmp1))
+ errx(1, "path truncation in rcs_rev_remove");
+
+ len = strlcat(path_tmp1, "/diff1.XXXXXXXXXX",
+ sizeof(path_tmp1));
+ if (len >= sizeof(path_tmp1))
+ errx(1, "path truncation in rcs_rev_remove");
+
+ rcs_buf_write_stmp(nextbuf, path_tmp1, 0600);
+ rcs_buf_free(nextbuf);
+
+ len = strlcpy(path_tmp2, tmpdir, sizeof(path_tmp2));
+ if (len >= sizeof(path_tmp2))
+ errx(1, "path truncation in rcs_rev_remove");
+
+ len = strlcat(path_tmp2, "/diff2.XXXXXXXXXX",
+ sizeof(path_tmp2));
+ if (len >= sizeof(path_tmp2))
+ errx(1, "path truncation in rcs_rev_remove");
+
+ rcs_buf_write_stmp(prevbuf, path_tmp2, 0600);
+ rcs_buf_free(prevbuf);
+
+ diff_format = D_RCSDIFF;
+ rcs_diffreg(path_tmp1, path_tmp2, newdiff);
+
+ newdeltatext = rcs_buf_release(newdiff);
+ } else if (nextrdp == NULL && prevrdp != NULL) {
+ newdeltatext = rcs_buf_release(prevbuf);
+ }
+
+ if (newdeltatext != NULL) {
+ if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
+ errx(1, "error setting new deltatext");
+ }
+
+ TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
+
+ /* update pointers */
+ if (prevrdp != NULL && nextrdp != NULL) {
+ rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
+ } else if (prevrdp != NULL) {
+ if (rcs_head_set(rf, prevrdp->rd_num) < 0)
+ errx(1, "rcs_head_set failed");
+ } else if (nextrdp != NULL) {
+ rcsnum_free(nextrdp->rd_next);
+ nextrdp->rd_next = rcsnum_alloc();
+ } else {
+ rcsnum_free(rf->rf_head);
+ rf->rf_head = NULL;
+ }
+
+ rf->rf_ndelta--;
+ rf->rf_flags &= ~RCS_SYNCED;
+
+ rcs_freedelta(rdp);
+
+ if (newdeltatext != NULL)
+ xfree(newdeltatext);
+
+ return (0);
+}
+
+/*
+ * rcs_findrev()
+ *
+ * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
+ * The revision number is given in <rev>.
+ *
+ * If the given revision is a branch number, we translate it into the latest
+ * revision on the branch.
+ *
+ * Returns a pointer to the delta on success, or NULL on failure.
+ */
+struct rcs_delta *
+rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
+{
+ u_int cmplen;
+ struct rcs_delta *rdp;
+ RCSNUM *brev, *frev;
+
+ /*
+ * We need to do more parsing if the last revision in the linked list
+ * is greater than the requested revision.
+ */
+ rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
+ if (rdp == NULL ||
+ rcsnum_cmp(rdp->rd_num, rev, 0) == -1) {
+ rcs_parse_deltas(rfp, rev);
+ }
+
+ /*
+ * Translate a branch into the latest revision on the branch itself.
+ */
+ if (RCSNUM_ISBRANCH(rev)) {
+ brev = rcsnum_brtorev(rev);
+ frev = brev;
+ for (;;) {
+ rdp = rcs_findrev(rfp, frev);
+ if (rdp == NULL)
+ return (NULL);
+
+ if (rdp->rd_next->rn_len == 0)
+ break;
+
+ frev = rdp->rd_next;
+ }
+
+ rcsnum_free(brev);
+ return (rdp);
+ }
+
+ cmplen = rev->rn_len;
+
+ TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
+ if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0)
+ return (rdp);
+ }
+
+ return (NULL);
+}
+
+/*
+ * rcs_kwexp_set()
+ *
+ * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
+ */
+void
+rcs_kwexp_set(RCSFILE *file, int mode)
+{
+ int i;
+ char *tmp, buf[8] = "";
+
+ if (RCS_KWEXP_INVAL(mode))
+ return;
+
+ i = 0;
+ if (mode == RCS_KWEXP_NONE)
+ buf[0] = 'b';
+ else if (mode == RCS_KWEXP_OLD)
+ buf[0] = 'o';
+ else {
+ if (mode & RCS_KWEXP_NAME)
+ buf[i++] = 'k';
+ if (mode & RCS_KWEXP_VAL)
+ buf[i++] = 'v';
+ if (mode & RCS_KWEXP_LKR)
+ buf[i++] = 'l';
+ }
+
+ tmp = xstrdup(buf);
+ if (file->rf_expand != NULL)
+ xfree(file->rf_expand);
+ file->rf_expand = tmp;
+ /* not synced anymore */
+ file->rf_flags &= ~RCS_SYNCED;
+}
+
+/*
+ * rcs_kwexp_get()
+ *
+ * Retrieve the keyword expansion mode to be used for the RCS file <file>.
+ */
+int
+rcs_kwexp_get(RCSFILE *file)
+{
+ return rcs_kflag_get(file->rf_expand);
+}
+
+/*
+ * rcs_kflag_get()
+ *
+ * Get the keyword expansion mode from a set of character flags given in
+ * <flags> and return the appropriate flag mask. In case of an error, the
+ * returned mask will have the RCS_KWEXP_ERR bit set to 1.
+ */
+int
+rcs_kflag_get(const char *flags)
+{
+ int fl;
+ size_t len;
+ const char *fp;
+
+ fl = 0;
+ len = strlen(flags);
+
+ for (fp = flags; *fp != '\0'; fp++) {
+ if (*fp == 'k')
+ fl |= RCS_KWEXP_NAME;
+ else if (*fp == 'v')
+ fl |= RCS_KWEXP_VAL;
+ else if (*fp == 'l')
+ fl |= RCS_KWEXP_LKR;
+ else if (*fp == 'o') {
+ if (len != 1)
+ fl |= RCS_KWEXP_ERR;
+ fl |= RCS_KWEXP_OLD;
+ } else if (*fp == 'b') {
+ if (len != 1)
+ fl |= RCS_KWEXP_ERR;
+ } else /* unknown letter */
+ fl |= RCS_KWEXP_ERR;
+ }
+
+ return (fl);
+}
+
+/*
+ * rcs_errstr()
+ *
+ * Get the error string matching the RCS error code <code>.
+ */
+const char *
+rcs_errstr(int code)
+{
+ const char *esp;
+
+ if (code < 0 || (code >= (int)RCS_NERR && code != RCS_ERR_ERRNO))
+ esp = NULL;
+ else if (code == RCS_ERR_ERRNO)
+ esp = strerror(errno);
+ else
+ esp = rcs_errstrs[code];
+ return (esp);
+}
+
+/* rcs_parse_deltas()
+ *
+ * Parse deltas. If <rev> is not NULL, parse only as far as that
+ * revision. If <rev> is NULL, parse all deltas.
+ */
+static void
+rcs_parse_deltas(RCSFILE *rfp, RCSNUM *rev)
+{
+ int ret;
+ struct rcs_delta *enddelta;
+
+ if ((rfp->rf_flags & PARSED_DELTAS) || (rfp->rf_flags & RCS_CREATE))
+ return;
+
+ for (;;) {
+ ret = rcs_parse_delta(rfp);
+ if (rev != NULL) {
+ enddelta = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
+ if (rcsnum_cmp(enddelta->rd_num, rev, 0) == 0)
+ break;
+ }
+ if (ret == 0) {
+ rfp->rf_flags |= PARSED_DELTAS;
+ break;
+ }
+ else if (ret == -1)
+ errx(1, "error parsing deltas");
+ }
+}
+
+/* rcs_parse_deltatexts()
+ *
+ * Parse deltatexts. If <rev> is not NULL, parse only as far as that
+ * revision. If <rev> is NULL, parse everything.
+ */
+static void
+rcs_parse_deltatexts(RCSFILE *rfp, RCSNUM *rev)
+{
+ int ret;
+ struct rcs_delta *rdp;
+
+ if ((rfp->rf_flags & PARSED_DELTATEXTS) ||
+ (rfp->rf_flags & RCS_CREATE))
+ return;
+
+ if (!(rfp->rf_flags & PARSED_DESC))
+ rcs_parse_desc(rfp, rev);
+ for (;;) {
+ if (rev != NULL) {
+ rdp = rcs_findrev(rfp, rev);
+ if (rdp->rd_text != NULL)
+ break;
+ else
+ ret = rcs_parse_deltatext(rfp);
+ } else
+ ret = rcs_parse_deltatext(rfp);
+ if (ret == 0) {
+ rfp->rf_flags |= PARSED_DELTATEXTS;
+ break;
+ }
+ else if (ret == -1)
+ errx(1, "problem parsing deltatexts");
+ }
+}
+
+/* rcs_parse_desc()
+ *
+ * Parse RCS description.
+ */
+static void
+rcs_parse_desc(RCSFILE *rfp, RCSNUM *rev)
+{
+ int ret = 0;
+
+ if ((rfp->rf_flags & PARSED_DESC) || (rfp->rf_flags & RCS_CREATE))
+ return;
+ if (!(rfp->rf_flags & PARSED_DELTAS))
+ rcs_parse_deltas(rfp, rev);
+ /* do parsing */
+ ret = rcs_gettok(rfp);
+ if (ret != RCS_TOK_DESC)
+ errx(1, "token `%s' found where RCS desc expected",
+ RCS_TOKSTR(rfp));
+
+ ret = rcs_gettok(rfp);
+ if (ret != RCS_TOK_STRING)
+ errx(1, "token `%s' found where RCS desc expected",
+ RCS_TOKSTR(rfp));
+
+ rfp->rf_desc = xstrdup(RCS_TOKSTR(rfp));
+ rfp->rf_flags |= PARSED_DESC;
+}
+
+/*
+ * rcs_parse_init()
+ *
+ * Initial parsing of file <path>, which are in the RCS format.
+ * Just does admin section.
+ */
+static void
+rcs_parse_init(RCSFILE *rfp)
+{
+ struct rcs_pdata *pdp;
+
+ if (rfp->rf_flags & RCS_PARSED)
+ return;
+
+ pdp = xcalloc(1, sizeof(*pdp));
+
+ pdp->rp_lines = 0;
+ pdp->rp_pttype = RCS_TOK_ERR;
+
+ if ((pdp->rp_file = fopen(rfp->rf_path, "r")) == NULL)
+ err(1, "fopen: `%s'", rfp->rf_path);
+
+ pdp->rp_buf = xmalloc((size_t)RCS_BUFSIZE);
+ pdp->rp_blen = RCS_BUFSIZE;
+ pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1;
+
+ /* ditch the strict lock */
+ rfp->rf_flags &= ~RCS_SLOCK;
+ rfp->rf_pdata = pdp;
+
+ if (rcs_parse_admin(rfp) < 0) {
+ rcs_freepdata(pdp);
+ errx(1, "could not parse admin data");
+ }
+
+ if (rfp->rf_flags & RCS_PARSE_FULLY)
+ rcs_parse_deltatexts(rfp, NULL);
+
+ rfp->rf_flags |= RCS_SYNCED;
+}
+
+/*
+ * rcs_parse_admin()
+ *
+ * Parse the administrative portion of an RCS file.
+ * Returns the type of the first token found after the admin section on
+ * success, or -1 on failure.
+ */
+static int
+rcs_parse_admin(RCSFILE *rfp)
+{
+ u_int i;
+ int tok, ntok, hmask;
+ struct rcs_key *rk;
+
+ /* hmask is a mask of the headers already encountered */
+ hmask = 0;
+ for (;;) {
+ tok = rcs_gettok(rfp);
+ if (tok == RCS_TOK_ERR) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("parse error in RCS admin section");
+ goto fail;
+ } else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) {
+ /*
+ * Assume this is the start of the first delta or
+ * that we are dealing with an empty RCS file and
+ * we just found the description.
+ */
+ rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
+ return (tok);
+ }
+
+ rk = NULL;
+ for (i = 0; i < RCS_NKEYS; i++)
+ if (rcs_keys[i].rk_id == tok)
+ rk = &(rcs_keys[i]);
+
+ if (hmask & (1 << tok)) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("duplicate RCS key");
+ goto fail;
+ }
+ hmask |= (1 << tok);
+
+ switch (tok) {
+ case RCS_TOK_HEAD:
+ case RCS_TOK_BRANCH:
+ case RCS_TOK_COMMENT:
+ case RCS_TOK_EXPAND:
+ ntok = rcs_gettok(rfp);
+ if (ntok == RCS_TOK_SCOLON)
+ break;
+ if (ntok != rk->rk_val) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("invalid value type for RCS key `%s'",
+ rk->rk_str);
+ }
+
+ if (tok == RCS_TOK_HEAD) {
+ if (rfp->rf_head == NULL)
+ rfp->rf_head = rcsnum_alloc();
+ rcsnum_aton(RCS_TOKSTR(rfp), NULL,
+ rfp->rf_head);
+ } else if (tok == RCS_TOK_BRANCH) {
+ if (rfp->rf_branch == NULL)
+ rfp->rf_branch = rcsnum_alloc();
+ if (rcsnum_aton(RCS_TOKSTR(rfp), NULL,
+ rfp->rf_branch) < 0)
+ goto fail;
+ } else if (tok == RCS_TOK_COMMENT) {
+ rfp->rf_comment = xstrdup(RCS_TOKSTR(rfp));
+ } else if (tok == RCS_TOK_EXPAND) {
+ rfp->rf_expand = xstrdup(RCS_TOKSTR(rfp));
+ }
+
+ /* now get the expected semi-colon */
+ ntok = rcs_gettok(rfp);
+ if (ntok != RCS_TOK_SCOLON) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("missing semi-colon after RCS `%s' key",
+ rk->rk_str);
+ goto fail;
+ }
+ break;
+ case RCS_TOK_ACCESS:
+ if (rcs_parse_access(rfp) < 0)
+ goto fail;
+ break;
+ case RCS_TOK_SYMBOLS:
+ if (rcs_parse_symbols(rfp) < 0)
+ goto fail;
+ break;
+ case RCS_TOK_LOCKS:
+ if (rcs_parse_locks(rfp) < 0)
+ goto fail;
+ break;
+ default:
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in RCS admin section",
+ RCS_TOKSTR(rfp));
+ goto fail;
+ }
+ }
+
+fail:
+ return (-1);
+}
+
+/*
+ * rcs_parse_delta()
+ *
+ * Parse an RCS delta section and allocate the structure to store that delta's
+ * information in the <rfp> delta list.
+ * Returns 1 if the section was parsed OK, 0 if it is the last delta, and
+ * -1 on error.
+ */
+static int
+rcs_parse_delta(RCSFILE *rfp)
+{
+ int ret, tok, ntok, hmask;
+ u_int i;
+ char *tokstr;
+ RCSNUM *datenum;
+ struct rcs_delta *rdp;
+ struct rcs_key *rk;
+
+ rdp = xcalloc(1, sizeof(*rdp));
+
+ rdp->rd_num = rcsnum_alloc();
+ rdp->rd_next = rcsnum_alloc();
+
+ TAILQ_INIT(&(rdp->rd_branches));
+
+ tok = rcs_gettok(rfp);
+ if (tok == RCS_TOK_DESC) {
+ rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
+ return (0);
+ } else if (tok != RCS_TOK_NUM) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' at start of delta",
+ RCS_TOKSTR(rfp));
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+ rcsnum_aton(RCS_TOKSTR(rfp), NULL, rdp->rd_num);
+
+ hmask = 0;
+ ret = 0;
+ tokstr = NULL;
+
+ for (;;) {
+ tok = rcs_gettok(rfp);
+ if (tok == RCS_TOK_ERR) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("parse error in RCS delta section");
+ rcs_freedelta(rdp);
+ return (-1);
+ } else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) {
+ rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
+ ret = (tok == RCS_TOK_NUM ? 1 : 0);
+ break;
+ }
+
+ rk = NULL;
+ for (i = 0; i < RCS_NKEYS; i++)
+ if (rcs_keys[i].rk_id == tok)
+ rk = &(rcs_keys[i]);
+
+ if (hmask & (1 << tok)) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("duplicate RCS key");
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+ hmask |= (1 << tok);
+
+ switch (tok) {
+ case RCS_TOK_DATE:
+ case RCS_TOK_AUTHOR:
+ case RCS_TOK_STATE:
+ case RCS_TOK_NEXT:
+ ntok = rcs_gettok(rfp);
+ if (ntok == RCS_TOK_SCOLON) {
+ if (rk->rk_flags & RCS_VOPT)
+ break;
+ else {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("missing mandatory "
+ "value to RCS key `%s'",
+ rk->rk_str);
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+ }
+
+ if (ntok != rk->rk_val) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("invalid value type for RCS key `%s'",
+ rk->rk_str);
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+
+ if (tokstr != NULL)
+ xfree(tokstr);
+ tokstr = xstrdup(RCS_TOKSTR(rfp));
+ /* now get the expected semi-colon */
+ ntok = rcs_gettok(rfp);
+ if (ntok != RCS_TOK_SCOLON) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("missing semi-colon after RCS `%s' key",
+ rk->rk_str);
+ xfree(tokstr);
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+
+ if (tok == RCS_TOK_DATE) {
+ if ((datenum = rcsnum_parse(tokstr)) == NULL) {
+ xfree(tokstr);
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+ if (datenum->rn_len != 6) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("RCS date specification has %s "
+ "fields",
+ (datenum->rn_len > 6) ? "too many" :
+ "missing");
+ xfree(tokstr);
+ rcs_freedelta(rdp);
+ rcsnum_free(datenum);
+ return (-1);
+ }
+ rdp->rd_date.tm_year = datenum->rn_id[0];
+ if (rdp->rd_date.tm_year >= 1900)
+ rdp->rd_date.tm_year -= 1900;
+ rdp->rd_date.tm_mon = datenum->rn_id[1] - 1;
+ rdp->rd_date.tm_mday = datenum->rn_id[2];
+ rdp->rd_date.tm_hour = datenum->rn_id[3];
+ rdp->rd_date.tm_min = datenum->rn_id[4];
+ rdp->rd_date.tm_sec = datenum->rn_id[5];
+ rcsnum_free(datenum);
+ } else if (tok == RCS_TOK_AUTHOR) {
+ rdp->rd_author = tokstr;
+ tokstr = NULL;
+ } else if (tok == RCS_TOK_STATE) {
+ rdp->rd_state = tokstr;
+ tokstr = NULL;
+ } else if (tok == RCS_TOK_NEXT) {
+ rcsnum_aton(tokstr, NULL, rdp->rd_next);
+ }
+ break;
+ case RCS_TOK_BRANCHES:
+ if (rcs_parse_branches(rfp, rdp) < 0) {
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+ break;
+ default:
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in RCS delta",
+ RCS_TOKSTR(rfp));
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+ }
+
+ if (tokstr != NULL)
+ xfree(tokstr);
+
+ TAILQ_INSERT_TAIL(&(rfp->rf_delta), rdp, rd_list);
+ rfp->rf_ndelta++;
+
+ return (ret);
+}
+
+/*
+ * rcs_parse_deltatext()
+ *
+ * Parse an RCS delta text section and fill in the log and text field of the
+ * appropriate delta section.
+ * Returns 1 if the section was parsed OK, 0 if it is the last delta, and
+ * -1 on error.
+ */
+static int
+rcs_parse_deltatext(RCSFILE *rfp)
+{
+ int tok;
+ RCSNUM *tnum;
+ struct rcs_delta *rdp;
+
+ tok = rcs_gettok(rfp);
+ if (tok == RCS_TOK_EOF)
+ return (0);
+
+ if (tok != RCS_TOK_NUM) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' at start of RCS delta text",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ tnum = rcsnum_alloc();
+ rcsnum_aton(RCS_TOKSTR(rfp), NULL, tnum);
+
+ TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
+ if (rcsnum_cmp(tnum, rdp->rd_num, 0) == 0)
+ break;
+ }
+ rcsnum_free(tnum);
+
+ if (rdp == NULL) {
+ warnx("RCS delta text `%s' has no matching delta",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ tok = rcs_gettok(rfp);
+ if (tok != RCS_TOK_LOG) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' where RCS log expected",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ tok = rcs_gettok(rfp);
+ if (tok != RCS_TOK_STRING) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' where RCS log expected",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+ rdp->rd_log = xstrdup(RCS_TOKSTR(rfp));
+ tok = rcs_gettok(rfp);
+ if (tok != RCS_TOK_TEXT) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' where RCS text expected",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ tok = rcs_gettok(rfp);
+ if (tok != RCS_TOK_STRING) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' where RCS text expected",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ rdp->rd_text = xmalloc(RCS_TOKLEN(rfp) + 1);
+ strlcpy(rdp->rd_text, RCS_TOKSTR(rfp), (RCS_TOKLEN(rfp) + 1));
+ rdp->rd_tlen = RCS_TOKLEN(rfp);
+
+ return (1);
+}
+
+/*
+ * rcs_parse_access()
+ *
+ * Parse the access list given as value to the `access' keyword.
+ * Returns 0 on success, or -1 on failure.
+ */
+static int
+rcs_parse_access(RCSFILE *rfp)
+{
+ int type;
+
+ while ((type = rcs_gettok(rfp)) != RCS_TOK_SCOLON) {
+ if (type != RCS_TOK_ID) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in access list",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ if (rcs_access_add(rfp, RCS_TOKSTR(rfp)) < 0)
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * rcs_parse_symbols()
+ *
+ * Parse the symbol list given as value to the `symbols' keyword.
+ * Returns 0 on success, or -1 on failure.
+ */
+static int
+rcs_parse_symbols(RCSFILE *rfp)
+{
+ int type;
+ struct rcs_sym *symp;
+
+ for (;;) {
+ type = rcs_gettok(rfp);
+ if (type == RCS_TOK_SCOLON)
+ break;
+
+ if (type != RCS_TOK_ID) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ symp = xmalloc(sizeof(*symp));
+ symp->rs_name = xstrdup(RCS_TOKSTR(rfp));
+ symp->rs_num = rcsnum_alloc();
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_COLON) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ rcsnum_free(symp->rs_num);
+ xfree(symp->rs_name);
+ xfree(symp);
+ return (-1);
+ }
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_NUM) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ rcsnum_free(symp->rs_num);
+ xfree(symp->rs_name);
+ xfree(symp);
+ return (-1);
+ }
+
+ if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, symp->rs_num) < 0) {
+ warnx("failed to parse RCS NUM `%s'",
+ RCS_TOKSTR(rfp));
+ rcsnum_free(symp->rs_num);
+ xfree(symp->rs_name);
+ xfree(symp);
+ return (-1);
+ }
+
+ TAILQ_INSERT_TAIL(&(rfp->rf_symbols), symp, rs_list);
+ }
+
+ return (0);
+}
+
+/*
+ * rcs_parse_locks()
+ *
+ * Parse the lock list given as value to the `locks' keyword.
+ * Returns 0 on success, or -1 on failure.
+ */
+static int
+rcs_parse_locks(RCSFILE *rfp)
+{
+ int type;
+ struct rcs_lock *lkp;
+
+ for (;;) {
+ type = rcs_gettok(rfp);
+ if (type == RCS_TOK_SCOLON)
+ break;
+
+ if (type != RCS_TOK_ID) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in lock list",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ lkp = xmalloc(sizeof(*lkp));
+ lkp->rl_name = xstrdup(RCS_TOKSTR(rfp));
+ lkp->rl_num = rcsnum_alloc();
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_COLON) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ rcsnum_free(lkp->rl_num);
+ xfree(lkp->rl_name);
+ xfree(lkp);
+ return (-1);
+ }
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_NUM) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ rcsnum_free(lkp->rl_num);
+ xfree(lkp->rl_name);
+ xfree(lkp);
+ return (-1);
+ }
+
+ if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, lkp->rl_num) < 0) {
+ warnx("failed to parse RCS NUM `%s'",
+ RCS_TOKSTR(rfp));
+ rcsnum_free(lkp->rl_num);
+ xfree(lkp->rl_name);
+ xfree(lkp);
+ return (-1);
+ }
+
+ TAILQ_INSERT_HEAD(&(rfp->rf_locks), lkp, rl_list);
+ }
+
+ /* check if we have a `strict' */
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_STRICT) {
+ rcs_pushtok(rfp, RCS_TOKSTR(rfp), type);
+ } else {
+ rfp->rf_flags |= RCS_SLOCK;
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_SCOLON) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("missing semi-colon after `strict' keyword");
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * rcs_parse_branches()
+ *
+ * Parse the list of branches following a `branches' keyword in a delta.
+ * Returns 0 on success, or -1 on failure.
+ */
+static int
+rcs_parse_branches(RCSFILE *rfp, struct rcs_delta *rdp)
+{
+ int type;
+ struct rcs_branch *brp;
+
+ for (;;) {
+ type = rcs_gettok(rfp);
+ if (type == RCS_TOK_SCOLON)
+ break;
+
+ if (type != RCS_TOK_NUM) {
+ rcs_errno = RCS_ERR_PARSE;
+ warnx("unexpected token `%s' in list of branches",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ brp = xmalloc(sizeof(*brp));
+ brp->rb_num = rcsnum_parse(RCS_TOKSTR(rfp));
+ if (brp->rb_num == NULL) {
+ xfree(brp);
+ return (-1);
+ }
+
+ TAILQ_INSERT_TAIL(&(rdp->rd_branches), brp, rb_list);
+ }
+
+ return (0);
+}
+
+/*
+ * rcs_freedelta()
+ *
+ * Free the contents of a delta structure.
+ */
+static void
+rcs_freedelta(struct rcs_delta *rdp)
+{
+ struct rcs_branch *rb;
+
+ if (rdp->rd_num != NULL)
+ rcsnum_free(rdp->rd_num);
+ if (rdp->rd_next != NULL)
+ rcsnum_free(rdp->rd_next);
+
+ if (rdp->rd_author != NULL)
+ xfree(rdp->rd_author);
+ if (rdp->rd_locker != NULL)
+ xfree(rdp->rd_locker);
+ if (rdp->rd_state != NULL)
+ xfree(rdp->rd_state);
+ if (rdp->rd_log != NULL)
+ xfree(rdp->rd_log);
+ if (rdp->rd_text != NULL)
+ xfree(rdp->rd_text);
+
+ while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
+ TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
+ rcsnum_free(rb->rb_num);
+ xfree(rb);
+ }
+
+ xfree(rdp);
+}
+
+/*
+ * rcs_freepdata()
+ *
+ * Free the contents of the parser data structure.
+ */
+static void
+rcs_freepdata(struct rcs_pdata *pd)
+{
+ if (pd->rp_file != NULL)
+ (void)fclose(pd->rp_file);
+ if (pd->rp_buf != NULL)
+ xfree(pd->rp_buf);
+ xfree(pd);
+}
+
+/*
+ * rcs_gettok()
+ *
+ * Get the next RCS token from the string <str>.
+ */
+static int
+rcs_gettok(RCSFILE *rfp)
+{
+ u_int i;
+ int ch, last, type;
+ size_t len;
+ char *bp;
+ struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
+
+ type = RCS_TOK_ERR;
+ bp = pdp->rp_buf;
+ pdp->rp_tlen = 0;
+ *bp = '\0';
+
+ if (pdp->rp_pttype != RCS_TOK_ERR) {
+ type = pdp->rp_pttype;
+ strlcpy(pdp->rp_buf, pdp->rp_ptok, pdp->rp_blen);
+ pdp->rp_pttype = RCS_TOK_ERR;
+ return (type);
+ }
+
+ /* skip leading whitespace */
+ /* XXX we must skip backspace too for compatibility, should we? */
+ do {
+ ch = getc(pdp->rp_file);
+ if (ch == '\n')
+ pdp->rp_lines++;
+ } while (isspace(ch));
+
+ if (ch == EOF) {
+ type = RCS_TOK_EOF;
+ } else if (ch == ';') {
+ type = RCS_TOK_SCOLON;
+ } else if (ch == ':') {
+ type = RCS_TOK_COLON;
+ } else if (isalpha(ch)) {
+ type = RCS_TOK_ID;
+ *(bp++) = ch;
+ for (;;) {
+ ch = getc(pdp->rp_file);
+ if (!isalnum(ch) && ch != '_' && ch != '-' &&
+ ch != '/') {
+ ungetc(ch, pdp->rp_file);
+ break;
+ }
+ *(bp++) = ch;
+ pdp->rp_tlen++;
+ if (bp == pdp->rp_bufend - 1) {
+ len = bp - pdp->rp_buf;
+ rcs_growbuf(rfp);
+ bp = pdp->rp_buf + len;
+ }
+ }
+ *bp = '\0';
+
+ if (type != RCS_TOK_ERR) {
+ for (i = 0; i < RCS_NKEYS; i++) {
+ if (strcmp(rcs_keys[i].rk_str,
+ pdp->rp_buf) == 0) {
+ type = rcs_keys[i].rk_id;
+ break;
+ }
+ }
+ }
+ } else if (ch == '@') {
+ /* we have a string */
+ type = RCS_TOK_STRING;
+ for (;;) {
+ ch = getc(pdp->rp_file);
+ if (ch == '@') {
+ ch = getc(pdp->rp_file);
+ if (ch != '@') {
+ ungetc(ch, pdp->rp_file);
+ break;
+ }
+ } else if (ch == '\n')
+ pdp->rp_lines++;
+
+ *(bp++) = ch;
+ pdp->rp_tlen++;
+ if (bp == pdp->rp_bufend - 1) {
+ len = bp - pdp->rp_buf;
+ rcs_growbuf(rfp);
+ bp = pdp->rp_buf + len;
+ }
+ }
+
+ *bp = '\0';
+ } else if (isdigit(ch)) {
+ *(bp++) = ch;
+ last = ch;
+ type = RCS_TOK_NUM;
+
+ for (;;) {
+ ch = getc(pdp->rp_file);
+ if (bp == pdp->rp_bufend)
+ break;
+ if (!isdigit(ch) && ch != '.') {
+ ungetc(ch, pdp->rp_file);
+ break;
+ }
+
+ if (last == '.' && ch == '.') {
+ type = RCS_TOK_ERR;
+ break;
+ }
+ last = ch;
+ *(bp++) = ch;
+ pdp->rp_tlen++;
+ }
+ *bp = '\0';
+ }
+
+ return (type);
+}
+
+/*
+ * rcs_pushtok()
+ *
+ * Push a token back in the parser's token buffer.
+ */
+static int
+rcs_pushtok(RCSFILE *rfp, const char *tok, int type)
+{
+ struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
+
+ if (pdp->rp_pttype != RCS_TOK_ERR)
+ return (-1);
+
+ pdp->rp_pttype = type;
+ strlcpy(pdp->rp_ptok, tok, sizeof(pdp->rp_ptok));
+ return (0);
+}
+
+
+/*
+ * rcs_growbuf()
+ *
+ * Attempt to grow the internal parse buffer for the RCS file <rf> by
+ * RCS_BUFEXTSIZE.
+ * In case of failure, the original buffer is left unmodified.
+ */
+static void
+rcs_growbuf(RCSFILE *rf)
+{
+ void *tmp;
+ struct rcs_pdata *pdp = (struct rcs_pdata *)rf->rf_pdata;
+
+ tmp = xrealloc(pdp->rp_buf, 1, pdp->rp_blen + RCS_BUFEXTSIZE);
+ pdp->rp_buf = tmp;
+ pdp->rp_blen += RCS_BUFEXTSIZE;
+ pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1;
+}
+
+/*
+ * rcs_strprint()
+ *
+ * Output an RCS string <str> of size <slen> to the stream <stream>. Any
+ * '@' characters are escaped. Otherwise, the string can contain arbitrary
+ * binary data.
+ */
+static void
+rcs_strprint(const u_char *str, size_t slen, FILE *stream)
+{
+ const u_char *ap, *ep, *sp;
+
+ if (slen == 0)
+ return;
+
+ ep = str + slen - 1;
+
+ for (sp = str; sp <= ep;) {
+ ap = memchr(sp, '@', ep - sp);
+ if (ap == NULL)
+ ap = ep;
+ (void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
+
+ if (*ap == '@')
+ putc('@', stream);
+ sp = ap + 1;
+ }
+}
+
+/*
+ * rcs_expand_keywords()
+ *
+ * Return expansion any RCS keywords in <data>
+ *
+ * On error, return NULL.
+ */
+static char *
+rcs_expand_keywords(char *rcsfile, struct rcs_delta *rdp, char *data,
+ size_t len, int mode)
+{
+ ptrdiff_t c_offset, sizdiff, start_offset;
+ size_t i;
+ int kwtype;
+ u_int j, found;
+ char *c, *kwstr, *start, *end, *tbuf;
+ char expbuf[256], buf[256];
+ struct tm tb;
+ char *fmt;
+
+ kwtype = 0;
+ kwstr = NULL;
+ i = 0;
+
+ /*
+ * -z support for RCS
+ */
+ tb = rdp->rd_date;
+ if (timezone_flag != NULL)
+ rcs_set_tz(timezone_flag, rdp, &tb);
+
+ /*
+ * Keyword formats:
+ * $Keyword$
+ * $Keyword: value$
+ */
+ for (c = data; *c != '\0' && i < len; c++) {
+ if (*c == '$') {
+ /* remember start of this possible keyword */
+ start = c;
+ start_offset = start - data;
+
+ /* first following character has to be alphanumeric */
+ c++;
+ if (!isalpha(*c)) {
+ c = start;
+ continue;
+ }
+
+ /* look for any matching keywords */
+ found = 0;
+ for (j = 0; j < RCS_NKWORDS; j++) {
+ if (!strncmp(c, rcs_expkw[j].kw_str,
+ strlen(rcs_expkw[j].kw_str))) {
+ found = 1;
+ kwstr = rcs_expkw[j].kw_str;
+ kwtype = rcs_expkw[j].kw_type;
+ break;
+ }
+ }
+
+ /* unknown keyword, continue looking */
+ if (found == 0) {
+ c = start;
+ continue;
+ }
+
+ /* next character has to be ':' or '$' */
+ c += strlen(kwstr);
+ if (*c != ':' && *c != '$') {
+ c = start;
+ continue;
+ }
+
+ /*
+ * if the next character was ':' we need to look for
+ * an '$' before the end of the line to be sure it is
+ * in fact a keyword.
+ */
+ if (*c == ':') {
+ while (*c++) {
+ if (*c == '$' || *c == '\n')
+ break;
+ }
+
+ if (*c != '$') {
+ c = start;
+ continue;
+ }
+ }
+ c_offset = c - data;
+ end = c + 1;
+
+ /* start constructing the expansion */
+ expbuf[0] = '\0';
+
+ if (mode & RCS_KWEXP_NAME) {
+ strlcat(expbuf, "$", sizeof(expbuf));
+ strlcat(expbuf, kwstr, sizeof(expbuf));
+ if (mode & RCS_KWEXP_VAL)
+ strlcat(expbuf, ": ", sizeof(expbuf));
+ }
+
+ /*
+ * order matters because of RCS_KW_ID and
+ * RCS_KW_HEADER here
+ */
+ if (mode & RCS_KWEXP_VAL) {
+ if (kwtype & RCS_KW_RCSFILE) {
+ if (!(kwtype & RCS_KW_FULLPATH))
+ strlcat(expbuf,
+ basename(rcsfile),
+ sizeof(expbuf));
+ else
+ strlcat(expbuf, rcsfile,
+ sizeof(expbuf));
+ strlcat(expbuf, " ", sizeof(expbuf));
+ }
+
+ if (kwtype & RCS_KW_REVISION) {
+ rcsnum_tostr(rdp->rd_num, buf,
+ sizeof(buf));
+ strlcat(buf, " ", sizeof(buf));
+ strlcat(expbuf, buf, sizeof(expbuf));
+ }
+
+ if (kwtype & RCS_KW_DATE) {
+ if (timezone_flag != NULL)
+ fmt = "%Y/%m/%d %H:%M:%S%z ";
+ else
+ fmt = "%Y/%m/%d %H:%M:%S ";
+
+ strftime(buf, sizeof(buf), fmt, &tb);
+ strlcat(expbuf, buf, sizeof(expbuf));
+ }
+
+ if (kwtype & RCS_KW_AUTHOR) {
+ strlcat(expbuf, rdp->rd_author,
+ sizeof(expbuf));
+ strlcat(expbuf, " ", sizeof(expbuf));
+ }
+
+ if (kwtype & RCS_KW_STATE) {
+ strlcat(expbuf, rdp->rd_state,
+ sizeof(expbuf));
+ strlcat(expbuf, " ", sizeof(expbuf));
+ }
+
+ /* order does not matter anymore below */
+ if (kwtype & RCS_KW_LOG)
+ strlcat(expbuf, " ", sizeof(expbuf));
+
+ if (kwtype & RCS_KW_SOURCE) {
+ strlcat(expbuf, rcsfile,
+ sizeof(expbuf));
+ strlcat(expbuf, " ", sizeof(expbuf));
+ }
+
+ if (kwtype & RCS_KW_NAME)
+ strlcat(expbuf, " ", sizeof(expbuf));
+ }
+
+ /* end the expansion */
+ if (mode & RCS_KWEXP_NAME)
+ strlcat(expbuf, "$", sizeof(expbuf));
+
+ sizdiff = strlen(expbuf) - (end - start);
+ tbuf = xstrdup(end);
+ /* only realloc if we have to */
+ if (sizdiff > 0) {
+ char *newdata;
+
+ len += sizdiff;
+ newdata = xrealloc(data, 1, len);
+ data = newdata;
+ /*
+ * ensure string pointers are not invalidated
+ * after realloc()
+ */
+ start = data + start_offset;
+ c = data + c_offset;
+ }
+ strlcpy(start, expbuf, len);
+ strlcat(data, tbuf, len);
+ xfree(tbuf);
+ i += strlen(expbuf);
+ }
+ }
+
+ return (data);
+}
+
+/*
+ * rcs_deltatext_set()
+ *
+ * Set deltatext for <rev> in RCS file <rfp> to <dtext>
+ * Returns -1 on error, 0 on success.
+ */
+int
+rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, const char *dtext)
+{
+ size_t len;
+ struct rcs_delta *rdp;
+
+ /* Write operations require full parsing */
+ rcs_parse_deltatexts(rfp, NULL);
+
+ if ((rdp = rcs_findrev(rfp, rev)) == NULL)
+ return (-1);
+
+ if (rdp->rd_text != NULL)
+ xfree(rdp->rd_text);
+
+ len = strlen(dtext);
+ if (len != 0) {
+ /* XXX - use xstrdup() if rd_text changes to char *. */
+ rdp->rd_text = xmalloc(len + 1);
+ rdp->rd_tlen = len;
+ (void)memcpy(rdp->rd_text, dtext, len + 1);
+ } else {
+ rdp->rd_text = NULL;
+ rdp->rd_tlen = 0;
+ }
+
+ return (0);
+}
+
+/*
+ * rcs_rev_setlog()
+ *
+ * Sets the log message of revision <rev> to <logtext>
+ */
+int
+rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
+{
+ struct rcs_delta *rdp;
+ char buf[16];
+
+ rcsnum_tostr(rev, buf, sizeof(buf));
+
+ if ((rdp = rcs_findrev(rfp, rev)) == NULL)
+ return (-1);
+
+ if (rdp->rd_log != NULL)
+ xfree(rdp->rd_log);
+
+ rdp->rd_log = xstrdup(logtext);
+ rfp->rf_flags &= ~RCS_SYNCED;
+ return (0);
+}
+/*
+ * rcs_rev_getdate()
+ *
+ * Get the date corresponding to a given revision.
+ * Returns the date on success, -1 on failure.
+ */
+time_t
+rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
+{
+ struct rcs_delta *rdp;
+
+ if ((rdp = rcs_findrev(rfp, rev)) == NULL)
+ return (-1);
+
+ return (mktime(&rdp->rd_date));
+}
+
+/*
+ * rcs_state_set()
+ *
+ * Sets the state of revision <rev> to <state>
+ * NOTE: default state is 'Exp'. States may not contain spaces.
+ *
+ * Returns -1 on failure, 0 on success.
+ */
+int
+rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
+{
+ struct rcs_delta *rdp;
+
+ if ((rdp = rcs_findrev(rfp, rev)) == NULL)
+ return (-1);
+
+ if (rdp->rd_state != NULL)
+ xfree(rdp->rd_state);
+
+ rdp->rd_state = xstrdup(state);
+
+ rfp->rf_flags &= ~RCS_SYNCED;
+
+ return (0);
+}
+
+/*
+ * rcs_state_check()
+ *
+ * Check if string <state> is valid.
+ *
+ * Returns 0 if the string is valid, -1 otherwise.
+ */
+int
+rcs_state_check(const char *state)
+{
+ if (strchr(state, ' ') != NULL)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * rcs_state_get()
+ *
+ * Get the state for a given revision of a specified RCSFILE.
+ *
+ * Returns NULL on failure.
+ */
+const char *
+rcs_state_get(RCSFILE *rfp, RCSNUM *rev)
+{
+ struct rcs_delta *rdp;
+
+ if ((rdp = rcs_findrev(rfp, rev)) == NULL)
+ return (NULL);
+
+ return (rdp->rd_state);
+}
+
+/*
+ * rcs_kwexp_buf()
+ *
+ * Do keyword expansion on a buffer if necessary
+ *
+ */
+BUF *
+rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
+{
+ struct rcs_delta *rdp;
+ char *expanded, *tbuf;
+ int expmode;
+ size_t len;
+
+ /*
+ * Do keyword expansion if required.
+ */
+ if (rf->rf_expand != NULL)
+ expmode = rcs_kwexp_get(rf);
+ else
+ expmode = RCS_KWEXP_DEFAULT;
+
+ if (!(expmode & RCS_KWEXP_NONE)) {
+ if ((rdp = rcs_findrev(rf, rev)) == NULL)
+ errx(1, "could not fetch revision");
+ rcs_buf_putc(bp, '\0');
+ len = rcs_buf_len(bp);
+ tbuf = rcs_buf_release(bp);
+ expanded = rcs_expand_keywords(rf->rf_path, rdp,
+ tbuf, len, expmode);
+ bp = rcs_buf_alloc(len, BUF_AUTOEXT);
+ rcs_buf_set(bp, expanded, strlen(expanded), 0);
+ xfree(expanded);
+ }
+ return (bp);
+}
diff --git a/usr.bin/rcs/rcs.h b/usr.bin/rcs/rcs.h
new file mode 100644
index 00000000000..4ae27d19e0b
--- /dev/null
+++ b/usr.bin/rcs/rcs.h
@@ -0,0 +1,280 @@
+/* $OpenBSD: rcs.h,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#ifndef RCS_H
+#define RCS_H
+
+#include "buf.h"
+
+#define RCS_DIFF_MAXARG 32
+#define RCS_DIFF_DIV \
+ "==================================================================="
+
+#define RCSDIR "RCS"
+#define RCS_FILE_EXT ",v"
+
+#define RCS_HEAD_BRANCH "HEAD"
+#define RCS_HEAD_INIT "1.1"
+#define RCS_HEAD_REV ((RCSNUM *)(-1))
+
+
+#define RCS_SYM_INVALCHAR "$,.:;@"
+
+
+#define RCS_MAGIC_BRANCH ".0."
+#define RCS_STATE_EXP "Exp"
+#define RCS_STATE_DEAD "dead"
+
+/* lock types */
+#define RCS_LOCK_INVAL (-1)
+#define RCS_LOCK_LOOSE 0
+#define RCS_LOCK_STRICT 1
+
+
+/*
+ * Keyword expansion table
+ */
+#define RCS_KW_AUTHOR 0x1000
+#define RCS_KW_DATE 0x2000
+#define RCS_KW_LOG 0x4000
+#define RCS_KW_NAME 0x8000
+#define RCS_KW_RCSFILE 0x0100
+#define RCS_KW_REVISION 0x0200
+#define RCS_KW_SOURCE 0x0400
+#define RCS_KW_STATE 0x0800
+#define RCS_KW_FULLPATH 0x0010
+
+#define RCS_KW_ID \
+ (RCS_KW_RCSFILE | RCS_KW_REVISION | RCS_KW_DATE \
+ | RCS_KW_AUTHOR | RCS_KW_STATE)
+
+#define RCS_KW_HEADER (RCS_KW_ID | RCS_KW_FULLPATH)
+
+/* RCS keyword expansion modes (kflags) */
+#define RCS_KWEXP_NONE 0x00
+#define RCS_KWEXP_NAME 0x01 /* include keyword name */
+#define RCS_KWEXP_VAL 0x02 /* include keyword value */
+#define RCS_KWEXP_LKR 0x04 /* include name of locker */
+#define RCS_KWEXP_OLD 0x08 /* generate old keyword string */
+#define RCS_KWEXP_ERR 0x10 /* mode has an error */
+
+#define RCS_KWEXP_DEFAULT (RCS_KWEXP_NAME | RCS_KWEXP_VAL)
+#define RCS_KWEXP_KVL (RCS_KWEXP_NAME | RCS_KWEXP_VAL | RCS_KWEXP_LKR)
+
+#define RCS_KWEXP_INVAL(k) \
+ ((k & RCS_KWEXP_ERR) || \
+ ((k & RCS_KWEXP_OLD) && (k & ~RCS_KWEXP_OLD)))
+
+
+struct rcs_kw {
+ char kw_str[16];
+ int kw_type;
+};
+
+#define RCS_NKWORDS (sizeof(rcs_expkw)/sizeof(rcs_expkw[0]))
+
+#define RCSNUM_MAXNUM USHRT_MAX
+#define RCSNUM_MAXLEN 64
+
+#define RCSNUM_ISBRANCH(n) ((n)->rn_len % 2)
+#define RCSNUM_ISBRANCHREV(n) (!((n)->rn_len % 2) && ((n)->rn_len >= 4))
+#define RCSNUM_NO_MAGIC (1<<0)
+
+/* file flags */
+#define RCS_READ (1<<0)
+#define RCS_WRITE (1<<1)
+#define RCS_RDWR (RCS_READ|RCS_WRITE)
+#define RCS_CREATE (1<<2) /* create the file */
+#define RCS_PARSE_FULLY (1<<3) /* fully parse it on open */
+
+/* internal flags */
+#define RCS_PARSED (1<<4) /* file has been parsed */
+#define RCS_SYNCED (1<<5) /* in-mem copy is sync with disk copy */
+#define RCS_SLOCK (1<<6) /* strict lock */
+
+/* parser flags */
+#define PARSED_DELTAS (1<<7) /* all deltas are parsed */
+#define PARSED_DESC (1<<8) /* the description is parsed */
+#define PARSED_DELTATEXTS (1<<9) /* all delta texts are parsed */
+
+/* delta flags */
+#define RCS_RD_DEAD 0x01 /* dead */
+#define RCS_RD_SELECT 0x02 /* select for operation */
+
+/* RCS error codes */
+#define RCS_ERR_NOERR 0
+#define RCS_ERR_NOENT 1
+#define RCS_ERR_DUPENT 2
+#define RCS_ERR_BADNUM 3
+#define RCS_ERR_BADSYM 4
+#define RCS_ERR_PARSE 5
+#define RCS_ERR_ERRNO 255
+
+/* used for rcs_checkout_rev */
+#define CHECKOUT_REV_CREATED 1
+#define CHECKOUT_REV_MERGED 2
+#define CHECKOUT_REV_REMOVED 3
+#define CHECKOUT_REV_UPDATED 4
+
+typedef struct rcs_num {
+ u_int rn_len;
+ u_int16_t *rn_id;
+} RCSNUM;
+
+
+struct rcs_access {
+ char *ra_name;
+ uid_t ra_uid;
+ TAILQ_ENTRY(rcs_access) ra_list;
+};
+
+struct rcs_sym {
+ char *rs_name;
+ RCSNUM *rs_num;
+ TAILQ_ENTRY(rcs_sym) rs_list;
+};
+
+struct rcs_lock {
+ char *rl_name;
+ RCSNUM *rl_num;
+
+ TAILQ_ENTRY(rcs_lock) rl_list;
+};
+
+
+struct rcs_branch {
+ RCSNUM *rb_num;
+ TAILQ_ENTRY(rcs_branch) rb_list;
+};
+
+TAILQ_HEAD(rcs_dlist, rcs_delta);
+
+struct rcs_delta {
+ RCSNUM *rd_num;
+ RCSNUM *rd_next;
+ u_int rd_flags;
+ struct tm rd_date;
+ char *rd_author;
+ char *rd_state;
+ char *rd_log;
+ char *rd_locker;
+ u_char *rd_text;
+ size_t rd_tlen;
+
+ TAILQ_HEAD(, rcs_branch) rd_branches;
+ TAILQ_ENTRY(rcs_delta) rd_list;
+};
+
+
+typedef struct rcs_file {
+ char *rf_path;
+ mode_t rf_mode;
+ u_int rf_flags;
+
+ RCSNUM *rf_head;
+ RCSNUM *rf_branch;
+ char *rf_comment;
+ char *rf_expand;
+ char *rf_desc;
+
+ u_int rf_ndelta;
+ struct rcs_dlist rf_delta;
+ TAILQ_HEAD(rcs_alist, rcs_access) rf_access;
+ TAILQ_HEAD(rcs_slist, rcs_sym) rf_symbols;
+ TAILQ_HEAD(rcs_llist, rcs_lock) rf_locks;
+
+ void *rf_pdata;
+} RCSFILE;
+
+
+extern int rcs_errno;
+
+
+RCSFILE *rcs_open(const char *, int, ...);
+void rcs_close(RCSFILE *);
+const RCSNUM *rcs_head_get(RCSFILE *);
+int rcs_head_set(RCSFILE *, RCSNUM *);
+const RCSNUM *rcs_branch_get(RCSFILE *);
+int rcs_branch_set(RCSFILE *, const RCSNUM *);
+int rcs_access_add(RCSFILE *, const char *);
+int rcs_access_remove(RCSFILE *, const char *);
+int rcs_access_check(RCSFILE *, const char *);
+struct rcs_delta *rcs_findrev(RCSFILE *, RCSNUM *);
+int rcs_sym_add(RCSFILE *, const char *, RCSNUM *);
+int rcs_sym_remove(RCSFILE *, const char *);
+RCSNUM *rcs_sym_getrev(RCSFILE *, const char *);
+int rcs_sym_check(const char *);
+int rcs_lock_getmode(RCSFILE *);
+int rcs_lock_setmode(RCSFILE *, int);
+int rcs_lock_add(RCSFILE *, const char *, RCSNUM *);
+int rcs_lock_remove(RCSFILE *, const char *, RCSNUM *);
+BUF *rcs_getrev(RCSFILE *, RCSNUM *);
+int rcs_deltatext_set(RCSFILE *, RCSNUM *, const char *);
+const char *rcs_desc_get(RCSFILE *);
+void rcs_desc_set(RCSFILE *, const char *);
+const char *rcs_comment_lookup(const char *);
+const char *rcs_comment_get(RCSFILE *);
+void rcs_comment_set(RCSFILE *, const char *);
+BUF *rcs_kwexp_buf(BUF *, RCSFILE *, RCSNUM *);
+void rcs_kwexp_set(RCSFILE *, int);
+int rcs_kwexp_get(RCSFILE *);
+int rcs_rev_add(RCSFILE *, RCSNUM *, const char *, time_t,
+ const char *);
+time_t rcs_rev_getdate(RCSFILE *, RCSNUM *);
+int rcs_rev_setlog(RCSFILE *, RCSNUM *, const char *);
+int rcs_rev_remove(RCSFILE *, RCSNUM *);
+int rcs_state_set(RCSFILE *, RCSNUM *, const char *);
+const char *rcs_state_get(RCSFILE *, RCSNUM *);
+int rcs_state_check(const char *);
+RCSNUM *rcs_tag_resolve(RCSFILE *, const char *);
+const char *rcs_errstr(int);
+int rcs_write(RCSFILE *);
+
+
+int rcs_kflag_get(const char *);
+void rcs_kflag_usage(void);
+int rcs_kw_expand(RCSFILE *, u_char *, size_t, size_t *);
+
+RCSNUM *rcsnum_alloc(void);
+RCSNUM *rcsnum_parse(const char *);
+RCSNUM *rcsnum_brtorev(const RCSNUM *);
+RCSNUM *rcsnum_revtobr(const RCSNUM *);
+RCSNUM *rcsnum_inc(RCSNUM *);
+RCSNUM *rcsnum_dec(RCSNUM *);
+void rcsnum_free(RCSNUM *);
+int rcsnum_aton(const char *, char **, RCSNUM *);
+char *rcsnum_tostr(const RCSNUM *, char *, size_t);
+void rcsnum_cpy(const RCSNUM *, RCSNUM *, u_int);
+int rcsnum_cmp(const RCSNUM *, const RCSNUM *, u_int);
+
+/* rcstime.c */
+void rcs_set_tz(char *, struct rcs_delta *, struct tm *);
+
+extern char *timezone_flag;
+
+extern int rcsnum_flags;
+
+#endif /* RCS_H */
diff --git a/usr.bin/rcs/rcsclean.c b/usr.bin/rcs/rcsclean.c
index c0a9762c811..9e20f1e55dc 100644
--- a/usr.bin/rcs/rcsclean.c
+++ b/usr.bin/rcs/rcsclean.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: rcsclean.c,v 1.43 2006/04/25 13:36:35 xsa Exp $ */
+/* $OpenBSD: rcsclean.c,v 1.44 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005 Joris Vink <joris@openbsd.org>
* All rights reserved.
@@ -160,22 +160,22 @@ rcsclean_file(char *fname, const char *rev_str)
warnx("failed to get needed revision");
goto out;
}
- if ((b2 = cvs_buf_load(fname, 0)) == NULL) {
+ if ((b2 = rcs_buf_load(fname, 0)) == NULL) {
warnx("failed to load `%s'", fname);
goto out;
}
/* If buffer lengths are the same, compare contents as well. */
- if (cvs_buf_len(b1) != cvs_buf_len(b2))
+ if (rcs_buf_len(b1) != rcs_buf_len(b2))
match = 0;
else {
size_t len, n;
- len = cvs_buf_len(b1);
+ len = rcs_buf_len(b1);
match = 1;
for (n = 0; n < len; ++n)
- if (cvs_buf_getc(b1, n) != cvs_buf_getc(b2, n)) {
+ if (rcs_buf_getc(b1, n) != rcs_buf_getc(b2, n)) {
match = 0;
break;
}
@@ -205,9 +205,9 @@ rcsclean_file(char *fname, const char *rev_str)
out:
if (b1 != NULL)
- cvs_buf_free(b1);
+ rcs_buf_free(b1);
if (b2 != NULL)
- cvs_buf_free(b2);
+ rcs_buf_free(b2);
if (file != NULL)
rcs_close(file);
}
diff --git a/usr.bin/rcs/rcsdiff.c b/usr.bin/rcs/rcsdiff.c
index d3cc9d20bb3..034c4814848 100644
--- a/usr.bin/rcs/rcsdiff.c
+++ b/usr.bin/rcs/rcsdiff.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: rcsdiff.c,v 1.56 2006/04/25 13:36:35 xsa Exp $ */
+/* $OpenBSD: rcsdiff.c,v 1.57 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005 Joris Vink <joris@openbsd.org>
* All rights reserved.
@@ -211,7 +211,7 @@ rcsdiff_file(RCSFILE *file, RCSNUM *rev, const char *filename)
tv[0].tv_sec = (long)rcs_rev_getdate(file, rev);
tv[1].tv_sec = tv[0].tv_sec;
- if ((b2 = cvs_buf_load(filename, BUF_AUTOEXT)) == NULL) {
+ if ((b2 = rcs_buf_load(filename, BUF_AUTOEXT)) == NULL) {
warnx("failed to load file: `%s'", filename);
goto out;
}
@@ -225,9 +225,9 @@ rcsdiff_file(RCSFILE *file, RCSNUM *rev, const char *filename)
strlcpy(path1, rcs_tmpdir, sizeof(path1));
strlcat(path1, "/diff1.XXXXXXXXXX", sizeof(path1));
- cvs_buf_write_stmp(b1, path1, 0600);
+ rcs_buf_write_stmp(b1, path1, 0600);
- cvs_buf_free(b1);
+ rcs_buf_free(b1);
b1 = NULL;
if (utimes(path1, (const struct timeval *)&tv) < 0)
@@ -235,22 +235,22 @@ rcsdiff_file(RCSFILE *file, RCSNUM *rev, const char *filename)
strlcpy(path2, rcs_tmpdir, sizeof(path2));
strlcat(path2, "/diff2.XXXXXXXXXX", sizeof(path2));
- cvs_buf_write_stmp(b2, path2, 0600);
+ rcs_buf_write_stmp(b2, path2, 0600);
- cvs_buf_free(b2);
+ rcs_buf_free(b2);
b2 = NULL;
if (utimes(path2, (const struct timeval *)&tv2) < 0)
warn("utimes");
- cvs_diffreg(path1, path2, NULL);
+ rcs_diffreg(path1, path2, NULL);
ret = 0;
out:
if (b1 != NULL)
- cvs_buf_free(b1);
+ rcs_buf_free(b1);
if (b2 != NULL)
- cvs_buf_free(b2);
+ rcs_buf_free(b2);
return (ret);
}
@@ -303,9 +303,9 @@ rcsdiff_rev(RCSFILE *file, RCSNUM *rev1, RCSNUM *rev2)
strlcpy(path1, rcs_tmpdir, sizeof(path1));
strlcat(path1, "/diff1.XXXXXXXXXX", sizeof(path1));
- cvs_buf_write_stmp(b1, path1, 0600);
+ rcs_buf_write_stmp(b1, path1, 0600);
- cvs_buf_free(b1);
+ rcs_buf_free(b1);
b1 = NULL;
if (utimes(path1, (const struct timeval *)&tv) < 0)
@@ -313,22 +313,22 @@ rcsdiff_rev(RCSFILE *file, RCSNUM *rev1, RCSNUM *rev2)
strlcpy(path2, rcs_tmpdir, sizeof(path2));
strlcat(path2, "/diff2.XXXXXXXXXX", sizeof(path2));
- cvs_buf_write_stmp(b2, path2, 0600);
+ rcs_buf_write_stmp(b2, path2, 0600);
- cvs_buf_free(b2);
+ rcs_buf_free(b2);
b2 = NULL;
if (utimes(path2, (const struct timeval *)&tv2) < 0)
warn("utimes");
- cvs_diffreg(path1, path2, NULL);
+ rcs_diffreg(path1, path2, NULL);
ret = 0;
out:
if (b1 != NULL)
- cvs_buf_free(b1);
+ rcs_buf_free(b1);
if (b2 != NULL)
- cvs_buf_free(b2);
+ rcs_buf_free(b2);
return (ret);
}
diff --git a/usr.bin/rcs/rcsmerge.c b/usr.bin/rcs/rcsmerge.c
index e07347ffe3a..9335838e503 100644
--- a/usr.bin/rcs/rcsmerge.c
+++ b/usr.bin/rcs/rcsmerge.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: rcsmerge.c,v 1.35 2006/04/25 13:36:35 xsa Exp $ */
+/* $OpenBSD: rcsmerge.c,v 1.36 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org>
* All rights reserved.
@@ -151,7 +151,7 @@ rcsmerge_main(int argc, char **argv)
(flags & PIPEOUT) ? "; result to stdout":"");
}
- if ((bp = cvs_diff3(file, argv[i], rev1, rev2,
+ if ((bp = rcs_diff3(file, argv[i], rev1, rev2,
!(flags & QUIET))) == NULL) {
warnx("failed to merge");
rcs_close(file);
@@ -159,16 +159,16 @@ rcsmerge_main(int argc, char **argv)
}
if (flags & PIPEOUT) {
- cvs_buf_putc(bp, '\0');
- fcont = cvs_buf_release(bp);
+ rcs_buf_putc(bp, '\0');
+ fcont = rcs_buf_release(bp);
printf("%s", fcont);
xfree(fcont);
} else {
/* XXX mode */
- if (cvs_buf_write(bp, argv[i], 0644) < 0)
- warnx("cvs_buf_write failed");
+ if (rcs_buf_write(bp, argv[i], 0644) < 0)
+ warnx("rcs_buf_write failed");
- cvs_buf_free(bp);
+ rcs_buf_free(bp);
}
rcs_close(file);
}
diff --git a/usr.bin/rcs/rcsnum.c b/usr.bin/rcs/rcsnum.c
new file mode 100644
index 00000000000..1c6aa5af620
--- /dev/null
+++ b/usr.bin/rcs/rcsnum.c
@@ -0,0 +1,398 @@
+/* $OpenBSD: rcsnum.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#include "includes.h"
+
+#include "rcs.h"
+#include "xmalloc.h"
+
+static void rcsnum_setsize(RCSNUM *, u_int);
+static char *rcsnum_itoa(u_int16_t, char *, size_t);
+
+int rcsnum_flags;
+
+/*
+ * rcsnum_alloc()
+ *
+ * Allocate an RCS number structure and return a pointer to it.
+ */
+RCSNUM *
+rcsnum_alloc(void)
+{
+ RCSNUM *rnp;
+
+ rnp = xmalloc(sizeof(*rnp));
+ rnp->rn_len = 0;
+ rnp->rn_id = NULL;
+
+ return (rnp);
+}
+
+/*
+ * rcsnum_parse()
+ *
+ * Parse a string specifying an RCS number and return the corresponding RCSNUM.
+ */
+RCSNUM *
+rcsnum_parse(const char *str)
+{
+ char *ep;
+ RCSNUM *num;
+
+ num = rcsnum_alloc();
+ if (rcsnum_aton(str, &ep, num) < 0 || *ep != '\0') {
+ rcsnum_free(num);
+ num = NULL;
+ if (*ep != '\0')
+ rcs_errno = RCS_ERR_BADNUM;
+ }
+
+ return (num);
+}
+
+/*
+ * rcsnum_free()
+ *
+ * Free an RCSNUM structure previously allocated with rcsnum_alloc().
+ */
+void
+rcsnum_free(RCSNUM *rn)
+{
+ if (rn->rn_id != NULL)
+ xfree(rn->rn_id);
+ xfree(rn);
+}
+
+/*
+ * rcsnum_tostr()
+ *
+ * Format the RCS number <nump> into a human-readable dot-separated
+ * representation and store the resulting string in <buf>, which is of size
+ * <blen>.
+ * Returns a pointer to the start of <buf>. On failure <buf> is set to
+ * an empty string.
+ */
+char *
+rcsnum_tostr(const RCSNUM *nump, char *buf, size_t blen)
+{
+ u_int i;
+ char tmp[8];
+
+ if (nump == NULL || nump->rn_len == 0) {
+ buf[0] = '\0';
+ return (buf);
+ }
+
+ strlcpy(buf, rcsnum_itoa(nump->rn_id[0], buf, blen), blen);
+ for (i = 1; i < nump->rn_len; i++) {
+ strlcat(buf, ".", blen);
+ strlcat(buf, rcsnum_itoa(nump->rn_id[i], tmp, sizeof(tmp)),
+ blen);
+ }
+
+ return (buf);
+}
+
+static char *
+rcsnum_itoa(u_int16_t num, char *buf, size_t len)
+{
+ u_int16_t i;
+ char *p;
+
+ if (num == 0)
+ return "0";
+
+ p = buf + len - 1;
+ i = num;
+ bzero(buf, len);
+ while (i) {
+ *--p = '0' + (i % 10);
+ i /= 10;
+ }
+ return (p);
+}
+
+/*
+ * rcsnum_cpy()
+ *
+ * Copy the number stored in <nsrc> in the destination <ndst> up to <depth>
+ * numbers deep. If <depth> is 0, there is no depth limit.
+ */
+void
+rcsnum_cpy(const RCSNUM *nsrc, RCSNUM *ndst, u_int depth)
+{
+ u_int len;
+ void *tmp;
+
+ len = nsrc->rn_len;
+ if (depth != 0 && len > depth)
+ len = depth;
+
+ tmp = xrealloc(ndst->rn_id, len, sizeof(len));
+ ndst->rn_id = tmp;
+ ndst->rn_len = len;
+ /* Overflow checked in xrealloc(). */
+ (void)memcpy(ndst->rn_id, nsrc->rn_id, len * sizeof(len));
+}
+
+/*
+ * rcsnum_cmp()
+ *
+ * Compare the two numbers <n1> and <n2>. Returns -1 if <n1> is larger than
+ * <n2>, 0 if they are both the same, and 1 if <n2> is larger than <n1>.
+ * The <depth> argument specifies how many numbers deep should be checked for
+ * the result. A value of 0 means that the depth will be the minimum of the
+ * two numbers.
+ */
+int
+rcsnum_cmp(const RCSNUM *n1, const RCSNUM *n2, u_int depth)
+{
+ int res;
+ u_int i;
+ size_t slen;
+
+ slen = MIN(n1->rn_len, n2->rn_len);
+ if (depth != 0 && slen > depth)
+ slen = depth;
+
+ for (i = 0; i < slen; i++) {
+ res = n1->rn_id[i] - n2->rn_id[i];
+ if (res < 0)
+ return (1);
+ else if (res > 0)
+ return (-1);
+ }
+
+ if (n1->rn_len > n2->rn_len)
+ return (-1);
+ else if (n2->rn_len > n1->rn_len)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * rcsnum_aton()
+ *
+ * Translate the string <str> containing a sequence of digits and periods into
+ * its binary representation, which is stored in <nump>. The address of the
+ * first byte not part of the number is stored in <ep> on return, if it is not
+ * NULL.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcsnum_aton(const char *str, char **ep, RCSNUM *nump)
+{
+ u_int32_t val;
+ const char *sp;
+ void *tmp;
+ char *s;
+
+ if (nump->rn_id == NULL)
+ nump->rn_id = xmalloc(sizeof(*(nump->rn_id)));
+
+ nump->rn_len = 0;
+ nump->rn_id[0] = 0;
+
+ for (sp = str;; sp++) {
+ if (!isdigit(*sp) && (*sp != '.'))
+ break;
+
+ if (*sp == '.') {
+ if (nump->rn_len >= RCSNUM_MAXLEN - 1) {
+ rcs_errno = RCS_ERR_BADNUM;
+ goto rcsnum_aton_failed;
+ }
+
+ nump->rn_len++;
+ tmp = xrealloc(nump->rn_id,
+ nump->rn_len + 1, sizeof(*(nump->rn_id)));
+ nump->rn_id = tmp;
+ nump->rn_id[nump->rn_len] = 0;
+ continue;
+ }
+
+ val = (nump->rn_id[nump->rn_len] * 10) + (*sp - 0x30);
+ if (val > RCSNUM_MAXNUM)
+ errx(1, "RCSNUM overflow!");
+
+ nump->rn_id[nump->rn_len] = val;
+ }
+
+ if (ep != NULL)
+ *(const char **)ep = sp;
+
+ /*
+ * Handle "magic" RCS branch numbers.
+ *
+ * What are they?
+ *
+ * Magic branch numbers have an extra .0. at the second farmost
+ * rightside of the branch number, so instead of having an odd
+ * number of dot-separated decimals, it will have an even number.
+ *
+ * Now, according to all the documentation i've found on the net
+ * about this, cvs does this for "efficiency reasons", i'd like
+ * to hear one.
+ *
+ * We just make sure we remove the .0. from in the branch number.
+ *
+ * XXX - for compatibility reasons with GNU cvs we _need_
+ * to skip this part for the 'log' command, apparently it does
+ * show the magic branches for an unknown and probably
+ * completely insane and not understandable reason in that output.
+ *
+ */
+ if (nump->rn_len > 2 && nump->rn_id[nump->rn_len - 1] == 0
+ && !(rcsnum_flags & RCSNUM_NO_MAGIC)) {
+ /*
+ * Look for ".0.x" at the end of the branch number.
+ */
+ if ((s = strrchr(str, '.')) != NULL) {
+ s--;
+ while (*s != '.')
+ s--;
+
+ /*
+ * If we have a "magic" branch, adjust it
+ * so the .0. is removed.
+ */
+ if (!strncmp(s, RCS_MAGIC_BRANCH,
+ strlen(RCS_MAGIC_BRANCH))) {
+ nump->rn_id[nump->rn_len - 1] =
+ nump->rn_id[nump->rn_len];
+ nump->rn_len--;
+ }
+ }
+ }
+
+ /* We can't have a single-digit rcs number. */
+ if (nump->rn_len == 0) {
+ tmp = xrealloc(nump->rn_id,
+ nump->rn_len + 1, sizeof(*(nump->rn_id)));
+ nump->rn_id = tmp;
+ nump->rn_id[nump->rn_len + 1] = 0;
+ nump->rn_len++;
+ }
+
+ nump->rn_len++;
+ return (nump->rn_len);
+
+rcsnum_aton_failed:
+ nump->rn_len = 0;
+ xfree(nump->rn_id);
+ nump->rn_id = NULL;
+ return (-1);
+}
+
+/*
+ * rcsnum_inc()
+ *
+ * Increment the revision number specified in <num>.
+ * Returns a pointer to the <num> on success, or NULL on failure.
+ */
+RCSNUM *
+rcsnum_inc(RCSNUM *num)
+{
+ if (num->rn_id[num->rn_len - 1] == RCSNUM_MAXNUM)
+ return (NULL);
+ num->rn_id[num->rn_len - 1]++;
+ return (num);
+}
+
+/*
+ * rcsnum_dec()
+ *
+ * Decreases the revision number specified in <num>, if doing so will not
+ * result in an ending value below 1. E.g. 4.2 will go to 4.1 but 4.1 will
+ * be returned as 4.1.
+ */
+RCSNUM *
+rcsnum_dec(RCSNUM *num)
+{
+ /* XXX - Is it an error for the number to be 0? */
+ if (num->rn_id[num->rn_len - 1] <= 1)
+ return (num);
+ num->rn_id[num->rn_len - 1]--;
+ return (num);
+}
+
+/*
+ * rcsnum_revtobr()
+ *
+ * Retrieve the branch number associated with the revision number <num>.
+ * If <num> is a branch revision, the returned value will be the same
+ * number as the argument.
+ */
+RCSNUM *
+rcsnum_revtobr(const RCSNUM *num)
+{
+ RCSNUM *brnum;
+
+ if (num->rn_len < 2)
+ return (NULL);
+
+ brnum = rcsnum_alloc();
+ rcsnum_cpy(num, brnum, 0);
+
+ if (!RCSNUM_ISBRANCH(brnum))
+ brnum->rn_len--;
+
+ return (brnum);
+}
+
+/*
+ * rcsnum_brtorev()
+ *
+ * Retrieve the initial revision number associated with the branch number <num>.
+ * If <num> is a revision number, an error will be returned.
+ */
+RCSNUM *
+rcsnum_brtorev(const RCSNUM *brnum)
+{
+ RCSNUM *num;
+
+ if (!RCSNUM_ISBRANCH(brnum)) {
+ return (NULL);
+ }
+
+ num = rcsnum_alloc();
+ rcsnum_setsize(num, brnum->rn_len + 1);
+ rcsnum_cpy(brnum, num, brnum->rn_len);
+ num->rn_id[num->rn_len++] = 1;
+
+ return (num);
+}
+
+static void
+rcsnum_setsize(RCSNUM *num, u_int len)
+{
+ void *tmp;
+
+ tmp = xrealloc(num->rn_id, len, sizeof(*(num->rn_id)));
+ num->rn_id = tmp;
+ num->rn_len = len;
+}
diff --git a/usr.bin/rcs/rcsprog.c b/usr.bin/rcs/rcsprog.c
index 9b0dc0480b1..54592cfc129 100644
--- a/usr.bin/rcs/rcsprog.c
+++ b/usr.bin/rcs/rcsprog.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: rcsprog.c,v 1.116 2006/04/25 13:55:49 xsa Exp $ */
+/* $OpenBSD: rcsprog.c,v 1.117 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005 Jean-Francois Brousseau <jfb@openbsd.org>
* All rights reserved.
@@ -54,7 +54,7 @@ struct rcs_prog {
{ "ident", ident_main, ident_usage },
};
-struct cvs_wklhead rcs_temp_files;
+struct rcs_wklhead rcs_temp_files;
void sighdlr(int);
static void rcs_attach_symbol(RCSFILE *, const char *);
@@ -63,7 +63,7 @@ static void rcs_attach_symbol(RCSFILE *, const char *);
void
sighdlr(int sig)
{
- cvs_worklist_clean(&rcs_temp_files, cvs_worklist_unlink);
+ rcs_worklist_clean(&rcs_temp_files, rcs_worklist_unlink);
_exit(1);
}
@@ -148,7 +148,7 @@ main(int argc, char **argv)
}
/* clean up temporary files */
- cvs_worklist_run(&rcs_temp_files, cvs_worklist_unlink);
+ rcs_worklist_run(&rcs_temp_files, rcs_worklist_unlink);
exit(ret);
/* NOTREACHED */
@@ -359,26 +359,26 @@ rcs_main(int argc, char **argv)
/* entries to add to the access list */
if (alist != NULL) {
- struct cvs_argvector *aargv;
+ struct rcs_argvector *aargv;
- aargv = cvs_strsplit(alist, ",");
+ aargv = rcs_strsplit(alist, ",");
for (j = 0; aargv->argv[j] != NULL; j++)
rcs_access_add(file, aargv->argv[j]);
- cvs_argv_destroy(aargv);
+ rcs_argv_destroy(aargv);
}
if (comment != NULL)
rcs_comment_set(file, comment);
if (elist != NULL) {
- struct cvs_argvector *eargv;
+ struct rcs_argvector *eargv;
- eargv = cvs_strsplit(elist, ",");
+ eargv = rcs_strsplit(elist, ",");
for (j = 0; eargv->argv[j] != NULL; j++)
rcs_access_remove(file, eargv->argv[j]);
- cvs_argv_destroy(eargv);
+ rcs_argv_destroy(eargv);
} else if (rcsflags & RCSPROG_EFLAG) {
struct rcs_access *rap;
diff --git a/usr.bin/rcs/rcsprog.h b/usr.bin/rcs/rcsprog.h
index d06ce4f5f84..1f34a904565 100644
--- a/usr.bin/rcs/rcsprog.h
+++ b/usr.bin/rcs/rcsprog.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: rcsprog.h,v 1.54 2006/04/24 04:51:57 ray Exp $ */
+/* $OpenBSD: rcsprog.h,v 1.55 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005 Joris Vink <joris@openbsd.org>
* All rights reserved.
@@ -29,7 +29,6 @@
#include <err.h>
-#include "log.h"
#include "rcs.h"
#include "rcsutil.h"
#include "worklist.h"
@@ -77,10 +76,10 @@ extern int rcs_optind;
extern char *rcs_optarg;
extern char *rcs_suffixes;
extern char *rcs_tmpdir;
-extern struct cvs_wklhead rcs_temp_files;
+extern struct rcs_wklhead rcs_temp_files;
/* date.y */
-time_t cvs_date_parse(const char *);
+time_t rcs_date_parse(const char *);
/* ci.c */
int checkin_main(int, char **);
diff --git a/usr.bin/rcs/rcstime.c b/usr.bin/rcs/rcstime.c
new file mode 100644
index 00000000000..4e070a3ec11
--- /dev/null
+++ b/usr.bin/rcs/rcstime.c
@@ -0,0 +1,92 @@
+/* $OpenBSD: rcstime.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2006 Joris Vink <joris@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#include "includes.h"
+
+#include "rcs.h"
+
+void
+rcs_set_tz(char *tz, struct rcs_delta *rdp, struct tm *tb)
+{
+ int tzone;
+ int pos;
+ char *h, *m;
+ struct tm *ltb;
+ time_t now;
+
+ if (!strcmp(tz, "LT")) {
+ now = mktime(&rdp->rd_date);
+ ltb = localtime(&now);
+ ltb->tm_hour += ((int)ltb->tm_gmtoff/3600);
+ memcpy(tb, ltb, sizeof(struct tm));
+ } else {
+ pos = 0;
+ switch (*tz) {
+ case '-':
+ break;
+ case '+':
+ pos = 1;
+ break;
+ default:
+ errx(1, "%s: not a known time zone", tz);
+ }
+
+ h = (tz + 1);
+ if ((m = strrchr(tz, ':')) != NULL)
+ *(m++) = '\0';
+
+ memcpy(tb, &rdp->rd_date, sizeof(struct tm));
+
+ tzone = atoi(h);
+ if ((tzone >= 24) && (tzone <= -24))
+ errx(1, "%s: not a known time zone", tz);
+
+ if (pos) {
+ tb->tm_hour += tzone;
+ tb->tm_gmtoff += (tzone * 3600);
+ } else {
+ tb->tm_hour -= tzone;
+ tb->tm_gmtoff -= (tzone * 3600);
+ }
+
+ if ((tb->tm_hour >= 24) || (tb->tm_hour <= -24))
+ tb->tm_hour = 0;
+
+ if (m != NULL) {
+ tzone = atoi(m);
+ if (tzone >= 60)
+ errx(1, "%s: not a known time zone", tz);
+
+ if ((tb->tm_min + tzone) >= 60) {
+ tb->tm_hour++;
+ tb->tm_min -= (60 - tzone);
+ } else
+ tb->tm_min += tzone;
+
+ tb->tm_gmtoff += (tzone*60);
+ }
+ }
+}
diff --git a/usr.bin/rcs/rcsutil.c b/usr.bin/rcs/rcsutil.c
index 8153966c88b..cd6c9ff086a 100644
--- a/usr.bin/rcs/rcsutil.c
+++ b/usr.bin/rcs/rcsutil.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: rcsutil.c,v 1.4 2006/04/25 13:55:49 xsa Exp $ */
+/* $OpenBSD: rcsutil.c,v 1.5 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org>
* Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org>
@@ -349,7 +349,7 @@ rcs_prompt(const char *prompt)
size_t len;
char *buf;
- bp = cvs_buf_alloc(0, BUF_AUTOEXT);
+ bp = rcs_buf_alloc(0, BUF_AUTOEXT);
if (isatty(STDIN_FILENO))
(void)fprintf(stderr, "%s", prompt);
if (isatty(STDIN_FILENO))
@@ -359,14 +359,14 @@ rcs_prompt(const char *prompt)
if (buf[0] == '.' && (len == 1 || buf[1] == '\n'))
break;
else
- cvs_buf_append(bp, buf, len);
+ rcs_buf_append(bp, buf, len);
if (isatty(STDIN_FILENO))
(void)fprintf(stderr, ">> ");
}
- cvs_buf_putc(bp, '\0');
+ rcs_buf_putc(bp, '\0');
- return (cvs_buf_release(bp));
+ return (rcs_buf_release(bp));
}
u_int
@@ -377,7 +377,7 @@ rcs_rev_select(RCSFILE *file, char *range)
char *ep;
char *lstr, *rstr;
struct rcs_delta *rdp;
- struct cvs_argvector *revargv, *revrange;
+ struct rcs_argvector *revargv, *revrange;
RCSNUM lnum, rnum;
nrev = 0;
@@ -393,9 +393,9 @@ rcs_rev_select(RCSFILE *file, char *range)
return (0);
}
- revargv = cvs_strsplit(range, ",");
+ revargv = rcs_strsplit(range, ",");
for (i = 0; revargv->argv[i] != NULL; i++) {
- revrange = cvs_strsplit(revargv->argv[i], ":");
+ revrange = rcs_strsplit(revargv->argv[i], ":");
if (revrange->argv[0] == NULL)
/* should not happen */
errx(1, "invalid revision range: %s", revargv->argv[i]);
@@ -424,7 +424,7 @@ rcs_rev_select(RCSFILE *file, char *range)
} else
rcsnum_cpy(file->rf_head, &rnum, 0);
- cvs_argv_destroy(revrange);
+ rcs_argv_destroy(revrange);
TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)
if (rcsnum_cmp(rdp->rd_num, &lnum, 0) <= 0 &&
@@ -434,7 +434,7 @@ rcs_rev_select(RCSFILE *file, char *range)
nrev++;
}
}
- cvs_argv_destroy(revargv);
+ rcs_argv_destroy(revargv);
if (lnum.rn_id != NULL)
xfree(lnum.rn_id);
@@ -461,9 +461,9 @@ rcs_set_description(RCSFILE *file, const char *in)
/* Description is in file <in>. */
if (in != NULL && *in != '-') {
- bp = cvs_buf_load(in, BUF_AUTOEXT);
- cvs_buf_putc(bp, '\0');
- content = cvs_buf_release(bp);
+ bp = rcs_buf_load(in, BUF_AUTOEXT);
+ rcs_buf_putc(bp, '\0');
+ content = rcs_buf_release(bp);
/* Description is in <in>. */
} else if (in != NULL)
/* Skip leading `-'. */
diff --git a/usr.bin/rcs/rlog.c b/usr.bin/rcs/rlog.c
index bbf299d988e..4fb1c917274 100644
--- a/usr.bin/rcs/rlog.c
+++ b/usr.bin/rcs/rlog.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: rlog.c,v 1.50 2006/04/25 13:36:36 xsa Exp $ */
+/* $OpenBSD: rlog.c,v 1.51 2006/04/26 02:55:13 joris Exp $ */
/*
* Copyright (c) 2005 Joris Vink <joris@openbsd.org>
* Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org>
@@ -250,7 +250,7 @@ rlog_rev_print(struct rcs_delta *rdp)
int i, found;
struct tm t;
char *author, numb[64], *fmt, timeb[64];
- struct cvs_argvector *largv, *sargv, *wargv;
+ struct rcs_argvector *largv, *sargv, *wargv;
i = found = 0;
author = NULL;
@@ -264,7 +264,7 @@ rlog_rev_print(struct rcs_delta *rdp)
/* if locker is empty, no need to go further. */
if (rdp->rd_locker == NULL)
return;
- largv = cvs_strsplit(llist, ",");
+ largv = rcs_strsplit(llist, ",");
for (i = 0; largv->argv[i] != NULL; i++) {
if (strcmp(rdp->rd_locker, largv->argv[i])
== 0) {
@@ -273,13 +273,13 @@ rlog_rev_print(struct rcs_delta *rdp)
}
found = 0;
}
- cvs_argv_destroy(largv);
+ rcs_argv_destroy(largv);
}
}
/* -sstates */
if (slist != NULL) {
- sargv = cvs_strsplit(slist, ",");
+ sargv = rcs_strsplit(slist, ",");
for (i = 0; sargv->argv[i] != NULL; i++) {
if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) {
found++;
@@ -287,13 +287,13 @@ rlog_rev_print(struct rcs_delta *rdp)
}
found = 0;
}
- cvs_argv_destroy(sargv);
+ rcs_argv_destroy(sargv);
}
/* -w[logins] */
if (wflag == 1) {
if (wlist != NULL) {
- wargv = cvs_strsplit(wlist, ",");
+ wargv = rcs_strsplit(wlist, ",");
for (i = 0; wargv->argv[i] != NULL; i++) {
if (strcmp(rdp->rd_author, wargv->argv[i])
== 0) {
@@ -302,7 +302,7 @@ rlog_rev_print(struct rcs_delta *rdp)
}
found = 0;
}
- cvs_argv_destroy(wargv);
+ rcs_argv_destroy(wargv);
} else {
if ((author = getlogin()) == NULL)
err(1, "getlogin");
diff --git a/usr.bin/rcs/util.c b/usr.bin/rcs/util.c
new file mode 100644
index 00000000000..fbbef666b5f
--- /dev/null
+++ b/usr.bin/rcs/util.c
@@ -0,0 +1,187 @@
+/* $OpenBSD: util.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
+ * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org>
+ * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#include "includes.h"
+
+#include "buf.h"
+#include "util.h"
+#include "xmalloc.h"
+
+/*
+ * Split the contents of a file into a list of lines.
+ */
+struct rcs_lines *
+rcs_splitlines(const char *fcont)
+{
+ char *dcp;
+ struct rcs_lines *lines;
+ struct rcs_line *lp;
+
+ lines = xmalloc(sizeof(*lines));
+ TAILQ_INIT(&(lines->l_lines));
+ lines->l_nblines = 0;
+ lines->l_data = xstrdup(fcont);
+
+ lp = xmalloc(sizeof(*lp));
+ lp->l_line = NULL;
+ lp->l_lineno = 0;
+ TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
+
+ for (dcp = lines->l_data; *dcp != '\0';) {
+ lp = xmalloc(sizeof(*lp));
+ lp->l_line = dcp;
+ lp->l_lineno = ++(lines->l_nblines);
+ TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
+
+ dcp = strchr(dcp, '\n');
+ if (dcp == NULL)
+ break;
+ *(dcp++) = '\0';
+ }
+
+ return (lines);
+}
+
+void
+rcs_freelines(struct rcs_lines *lines)
+{
+ struct rcs_line *lp;
+
+ while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
+ TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
+ xfree(lp);
+ }
+
+ xfree(lines->l_data);
+ xfree(lines);
+}
+
+BUF *
+rcs_patchfile(const char *data, const char *patch,
+ int (*p)(struct rcs_lines *, struct rcs_lines *))
+{
+ struct rcs_lines *dlines, *plines;
+ struct rcs_line *lp;
+ size_t len;
+ int lineno;
+ BUF *res;
+
+ len = strlen(data);
+
+ if ((dlines = rcs_splitlines(data)) == NULL)
+ return (NULL);
+
+ if ((plines = rcs_splitlines(patch)) == NULL)
+ return (NULL);
+
+ if (p(dlines, plines) < 0) {
+ rcs_freelines(dlines);
+ rcs_freelines(plines);
+ return (NULL);
+ }
+
+ lineno = 0;
+ res = rcs_buf_alloc(len, BUF_AUTOEXT);
+ TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
+ if (lineno != 0)
+ rcs_buf_fappend(res, "%s\n", lp->l_line);
+ lineno++;
+ }
+
+ rcs_freelines(dlines);
+ rcs_freelines(plines);
+ return (res);
+}
+
+/*
+ * rcs_yesno()
+ *
+ * Read from standart input for `y' or `Y' character.
+ * Returns 0 on success, or -1 on failure.
+ */
+int
+rcs_yesno(void)
+{
+ int c, ret;
+
+ ret = 0;
+
+ fflush(stderr);
+ fflush(stdout);
+
+ if ((c = getchar()) != 'y' && c != 'Y')
+ ret = -1;
+ else
+ while (c != EOF && c != '\n')
+ c = getchar();
+
+ return (ret);
+}
+
+/*
+ * rcs_strsplit()
+ *
+ * Split a string <str> of <sep>-separated values and allocate
+ * an argument vector for the values found.
+ */
+struct rcs_argvector *
+rcs_strsplit(char *str, const char *sep)
+{
+ struct rcs_argvector *av;
+ size_t i = 0;
+ char **nargv;
+ char *cp, *p;
+
+ cp = xstrdup(str);
+ av = xmalloc(sizeof(*av));
+ av->str = cp;
+ av->argv = xcalloc(i + 1, sizeof(*(av->argv)));
+
+ while ((p = strsep(&cp, sep)) != NULL) {
+ av->argv[i++] = p;
+ nargv = xrealloc(av->argv,
+ i + 1, sizeof(*(av->argv)));
+ av->argv = nargv;
+ }
+ av->argv[i] = NULL;
+
+ return (av);
+}
+
+/*
+ * rcs_argv_destroy()
+ *
+ * Free an argument vector previously allocated by rcs_strsplit().
+ */
+void
+rcs_argv_destroy(struct rcs_argvector *av)
+{
+ xfree(av->str);
+ xfree(av->argv);
+ xfree(av);
+}
diff --git a/usr.bin/rcs/util.h b/usr.bin/rcs/util.h
new file mode 100644
index 00000000000..3439c602a98
--- /dev/null
+++ b/usr.bin/rcs/util.h
@@ -0,0 +1,58 @@
+/* $OpenBSD: util.h,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+struct rcs_line {
+ char *l_line;
+ int l_lineno;
+ TAILQ_ENTRY(rcs_line) l_list;
+};
+
+TAILQ_HEAD(rcs_tqh, rcs_line);
+
+struct rcs_lines {
+ int l_nblines;
+ char *l_data;
+ struct rcs_tqh l_lines;
+};
+
+struct rcs_argvector {
+ char *str;
+ char **argv;
+};
+
+BUF *rcs_patchfile(const char *, const char *,
+ int (*p)(struct rcs_lines *, struct rcs_lines *));
+struct rcs_lines *rcs_splitlines(const char *);
+void rcs_freelines(struct rcs_lines *);
+int rcs_yesno(void);
+struct rcs_argvector *rcs_strsplit(char *, const char *);
+
+void rcs_argv_destroy(struct rcs_argvector *);
+
+#endif /* UTIL_H */
diff --git a/usr.bin/rcs/worklist.c b/usr.bin/rcs/worklist.c
new file mode 100644
index 00000000000..910bcea18d2
--- /dev/null
+++ b/usr.bin/rcs/worklist.c
@@ -0,0 +1,93 @@
+/* $OpenBSD: worklist.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2006 Joris Vink <joris@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#include "includes.h"
+
+#include "worklist.h"
+#include "xmalloc.h"
+
+/*
+ * adds a path to a worklist.
+ */
+void
+rcs_worklist_add(const char *path, struct rcs_wklhead *worklist)
+{
+ size_t len;
+ struct rcs_worklist *wkl;
+ sigset_t old, new;
+
+ wkl = xcalloc(1, sizeof(*wkl));
+
+ len = strlcpy(wkl->wkl_path, path, sizeof(wkl->wkl_path));
+ if (len >= sizeof(wkl->wkl_path))
+ errx(1, "path truncation in rcs_worklist_add");
+
+ sigfillset(&new);
+ sigprocmask(SIG_BLOCK, &new, &old);
+ SLIST_INSERT_HEAD(worklist, wkl, wkl_list);
+ sigprocmask(SIG_SETMASK, &old, NULL);
+}
+
+/*
+ * run over the given worklist, calling cb for each element.
+ * this is just like rcs_worklist_clean(), except we block signals first.
+ */
+void
+rcs_worklist_run(struct rcs_wklhead *list, void (*cb)(struct rcs_worklist *))
+{
+ sigset_t old, new;
+ struct rcs_worklist *wkl;
+
+ sigfillset(&new);
+ sigprocmask(SIG_BLOCK, &new, &old);
+
+ rcs_worklist_clean(list, cb);
+
+ while ((wkl = SLIST_FIRST(list)) != NULL) {
+ SLIST_REMOVE_HEAD(list, wkl_list);
+ xfree(wkl);
+ }
+
+ sigprocmask(SIG_SETMASK, &old, NULL);
+}
+
+/*
+ * pass elements to the specified callback, which has to be signal safe.
+ */
+void
+rcs_worklist_clean(struct rcs_wklhead *list, void (*cb)(struct rcs_worklist *))
+{
+ struct rcs_worklist *wkl;
+
+ SLIST_FOREACH(wkl, list, wkl_list)
+ cb(wkl);
+}
+
+void
+rcs_worklist_unlink(struct rcs_worklist *wkl)
+{
+ (void)unlink(wkl->wkl_path);
+}
diff --git a/usr.bin/rcs/worklist.h b/usr.bin/rcs/worklist.h
new file mode 100644
index 00000000000..fcbe1c14b1e
--- /dev/null
+++ b/usr.bin/rcs/worklist.h
@@ -0,0 +1,45 @@
+/* $OpenBSD: worklist.h,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Copyright (c) 2006 Joris Vink <joris@openbsd.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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 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.
+ */
+
+#ifndef WORKLIST_H
+#define WORKLIST_H
+
+struct rcs_worklist {
+ char wkl_path[MAXPATHLEN];
+ volatile SLIST_ENTRY(rcs_worklist) wkl_list;
+};
+
+SLIST_HEAD(rcs_wklhead, rcs_worklist);
+
+void rcs_worklist_add(const char *, struct rcs_wklhead *);
+void rcs_worklist_run(struct rcs_wklhead *, void (*cb)(struct rcs_worklist *));
+void rcs_worklist_clean(struct rcs_wklhead *, void (*cb)(struct rcs_worklist *));
+
+void rcs_worklist_unlink(struct rcs_worklist *);
+
+extern struct rcs_wklhead rcs_temp_files;
+
+#endif
diff --git a/usr.bin/rcs/xmalloc.c b/usr.bin/rcs/xmalloc.c
new file mode 100644
index 00000000000..3d90ca09170
--- /dev/null
+++ b/usr.bin/rcs/xmalloc.c
@@ -0,0 +1,105 @@
+/* $OpenBSD: xmalloc.c,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ * All rights reserved
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose. Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#include "includes.h"
+
+#include "xmalloc.h"
+
+void *
+xmalloc(size_t size)
+{
+ void *ptr;
+
+ if (size == 0)
+ errx(1, "xmalloc: zero size");
+ ptr = malloc(size);
+ if (ptr == NULL)
+ errx(1,
+ "xmalloc: out of memory (allocating %lu bytes)",
+ (u_long) size);
+ return ptr;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ if (size == 0 || nmemb == 0)
+ errx(1, "xcalloc: zero size");
+ if (SIZE_T_MAX / nmemb < size)
+ errx(1, "xcalloc: nmemb * size > SIZE_T_MAX");
+ ptr = calloc(nmemb, size);
+ if (ptr == NULL)
+ errx(1, "xcalloc: out of memory (allocating %lu bytes)",
+ (u_long)(size * nmemb));
+ return ptr;
+}
+
+void *
+xrealloc(void *ptr, size_t nmemb, size_t size)
+{
+ void *new_ptr;
+ size_t new_size = nmemb * size;
+
+ if (new_size == 0)
+ errx(1, "xrealloc: zero size");
+ if (SIZE_T_MAX / nmemb < size)
+ errx(1, "xrealloc: nmemb * size > SIZE_T_MAX");
+ if (ptr == NULL)
+ new_ptr = malloc(new_size);
+ else
+ new_ptr = realloc(ptr, new_size);
+ if (new_ptr == NULL)
+ errx(1, "xrealloc: out of memory (new_size %lu bytes)",
+ (u_long) new_size);
+ return new_ptr;
+}
+
+void
+xfree(void *ptr)
+{
+ if (ptr == NULL)
+ errx(1, "xfree: NULL pointer given as argument");
+ free(ptr);
+}
+
+char *
+xstrdup(const char *str)
+{
+ size_t len;
+ char *cp;
+
+ len = strlen(str) + 1;
+ cp = xmalloc(len);
+ strlcpy(cp, str, len);
+ return cp;
+}
+
+int
+xasprintf(char **ret, const char *fmt, ...)
+{
+ va_list ap;
+ int i;
+
+ va_start(ap, fmt);
+ i = vasprintf(ret, fmt, ap);
+ va_end(ap);
+
+ if (i < 0 || *ret == NULL)
+ errx(1, "xasprintf: could not allocate memory");
+
+ return (i);
+}
diff --git a/usr.bin/rcs/xmalloc.h b/usr.bin/rcs/xmalloc.h
new file mode 100644
index 00000000000..51dfb5564cd
--- /dev/null
+++ b/usr.bin/rcs/xmalloc.h
@@ -0,0 +1,31 @@
+/* $OpenBSD: xmalloc.h,v 1.1 2006/04/26 02:55:13 joris Exp $ */
+
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ * All rights reserved
+ * Created: Mon Mar 20 22:09:17 1995 ylo
+ *
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose. Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#ifndef XMALLOC_H
+#define XMALLOC_H
+
+void *xmalloc(size_t);
+void *xcalloc(size_t, size_t);
+void *xrealloc(void *, size_t, size_t);
+void xfree(void *);
+char *xstrdup(const char *);
+int xasprintf(char **, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)))
+ __attribute__((__nonnull__ (2)));
+
+#endif /* XMALLOC_H */