summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin')
-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 */