summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjfb <jfb@openbsd.org>2004-07-13 22:02:40 +0000
committerjfb <jfb@openbsd.org>2004-07-13 22:02:40 +0000
commit08f906739825fad4da9c8247830adb99233ee6c7 (patch)
treee465c4883f7d65e4d9c25f0d6fbf55f00474fb6d
parentbump these again, depending on gcc2/gcc3 a different library is installed (diff)
downloadwireguard-openbsd-08f906739825fad4da9c8247830adb99233ee6c7.tar.xz
wireguard-openbsd-08f906739825fad4da9c8247830adb99233ee6c7.zip
* initial import from the cvs-tools module
-rw-r--r--usr.bin/cvs/checkout.c80
-rw-r--r--usr.bin/cvs/client.c595
-rw-r--r--usr.bin/cvs/commit.c145
-rw-r--r--usr.bin/cvs/cvs.h374
-rw-r--r--usr.bin/cvs/diff.c1678
-rw-r--r--usr.bin/cvs/rcs.c1557
-rw-r--r--usr.bin/cvs/server.c95
7 files changed, 4524 insertions, 0 deletions
diff --git a/usr.bin/cvs/checkout.c b/usr.bin/cvs/checkout.c
new file mode 100644
index 00000000000..aa32f6389c5
--- /dev/null
+++ b/usr.bin/cvs/checkout.c
@@ -0,0 +1,80 @@
+/* $OpenBSD: checkout.c,v 1.1.1.1 2004/07/13 22:02:40 jfb 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 <sys/types.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "cvs.h"
+#include "log.h"
+
+
+
+extern struct cvsroot *cvs_root;
+
+
+
+/*
+ * cvs_checkout()
+ *
+ * Handler for the `cvs checkout' command.
+ * Returns 0 on success, or one of the known system exit codes on failure.
+ */
+
+int
+cvs_checkout(int argc, char **argv)
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ return (EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ cvs_log(LP_ERR,
+ "must specify at least one module or directory");
+ return (EX_USAGE);
+ }
+
+ if (cvs_root->cr_method == CVS_METHOD_LOCAL) {
+ return (0);
+ }
+
+ cvs_client_sendreq(CVS_REQ_ARGUMENT, argv[0], 0);
+
+ return (0);
+}
diff --git a/usr.bin/cvs/client.c b/usr.bin/cvs/client.c
new file mode 100644
index 00000000000..53ae0d35c11
--- /dev/null
+++ b/usr.bin/cvs/client.c
@@ -0,0 +1,595 @@
+/* $OpenBSD: client.c,v 1.1.1.1 2004/07/13 22:02:40 jfb 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 <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <sysexits.h>
+#ifdef CVS_ZLIB
+#include <zlib.h>
+#endif
+
+#include "cvs.h"
+#include "log.h"
+
+
+
+extern int verbosity;
+extern int cvs_compress;
+extern char *cvs_rsh;
+extern int cvs_trace;
+extern int cvs_nolog;
+extern int cvs_readonly;
+
+extern struct cvsroot *cvs_root;
+
+
+
+
+static int cvs_client_sendinfo (void);
+static int cvs_client_initlog (void);
+
+
+
+static int cvs_server_infd = -1;
+static int cvs_server_outfd = -1;
+static FILE *cvs_server_in;
+static FILE *cvs_server_out;
+
+/* protocol log files, if the CVS_CLIENT_LOG environment variable is used */
+static FILE *cvs_server_inlog;
+static FILE *cvs_server_outlog;
+
+static char cvs_client_buf[4096];
+
+/* last directory sent with `Directory' */
+static char cvs_lastdir[MAXPATHLEN] = "";
+
+
+
+/*
+ * cvs_client_connect()
+ *
+ * Open a client connection to the cvs server whose address is given in
+ * the global <cvs_root> variable. The method used to connect depends on the
+ * setting of the CVS_RSH variable.
+ */
+
+int
+cvs_client_connect(void)
+{
+ int argc, infd[2], outfd[2];
+ pid_t pid;
+ char *argv[16], *cvs_server_cmd;
+
+ if (pipe(infd) == -1) {
+ cvs_log(LP_ERRNO,
+ "failed to create input pipe for client connection");
+ return (-1);
+ }
+
+ if (pipe(outfd) == -1) {
+ cvs_log(LP_ERRNO,
+ "failed to create output pipe for client connection");
+ (void)close(infd[0]);
+ (void)close(infd[1]);
+ return (-1);
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ cvs_log(LP_ERRNO, "failed to fork for cvs server connection");
+ return (-1);
+ }
+ if (pid == 0) {
+ if ((dup2(infd[0], STDIN_FILENO) == -1) ||
+ (dup2(outfd[1], STDOUT_FILENO) == -1)) {
+ cvs_log(LP_ERRNO,
+ "failed to setup standard streams for cvs server");
+ return (-1);
+ }
+ (void)close(infd[1]);
+ (void)close(outfd[0]);
+
+ argc = 0;
+ argv[argc++] = cvs_rsh;
+
+ if (cvs_root->cr_user != NULL) {
+ argv[argc++] = "-l";
+ argv[argc++] = cvs_root->cr_user;
+ }
+
+
+ cvs_server_cmd = getenv("CVS_SERVER");
+ if (cvs_server_cmd == NULL)
+ cvs_server_cmd = "cvs";
+
+ argv[argc++] = cvs_root->cr_host;
+ argv[argc++] = cvs_server_cmd;
+ argv[argc++] = "server";
+ argv[argc] = NULL;
+
+ execvp(argv[0], argv);
+ cvs_log(LP_ERRNO, "failed to exec");
+ exit(EX_OSERR);
+ }
+
+ /* we are the parent */
+ cvs_server_infd = infd[1];
+ cvs_server_outfd = outfd[0];
+
+ cvs_server_in = fdopen(cvs_server_infd, "w");
+ if (cvs_server_in == NULL) {
+ cvs_log(LP_ERRNO, "failed to create pipe stream");
+ return (-1);
+ }
+
+ cvs_server_out = fdopen(cvs_server_outfd, "r");
+ if (cvs_server_out == NULL) {
+ cvs_log(LP_ERRNO, "failed to create pipe stream");
+ return (-1);
+ }
+
+ /* make the streams line-buffered */
+ setvbuf(cvs_server_in, NULL, _IOLBF, 0);
+ setvbuf(cvs_server_out, NULL, _IOLBF, 0);
+
+ (void)close(infd[0]);
+ (void)close(outfd[1]);
+
+ cvs_client_initlog();
+
+ cvs_client_sendinfo();
+
+#ifdef CVS_ZLIB
+ /* if compression was requested, initialize it */
+#endif
+
+ return (0);
+}
+
+
+/*
+ * cvs_client_disconnect()
+ *
+ * Disconnect from the cvs server.
+ */
+
+void
+cvs_client_disconnect(void)
+{
+ cvs_log(LP_DEBUG, "closing client connection");
+ (void)fclose(cvs_server_in);
+ (void)fclose(cvs_server_out);
+ cvs_server_in = NULL;
+ cvs_server_out = NULL;
+ cvs_server_infd = -1;
+ cvs_server_outfd = -1;
+
+ if (cvs_server_inlog != NULL)
+ fclose(cvs_server_inlog);
+ if (cvs_server_outlog != NULL)
+ fclose(cvs_server_outlog);
+}
+
+
+/*
+ * cvs_client_sendreq()
+ *
+ * Send a request to the server of type <rid>, with optional arguments
+ * contained in <arg>, which should not be terminated by a newline.
+ * The <resp> argument is 0 if no response is expected, or any other value if
+ * a response is expected.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_client_sendreq(u_int rid, const char *arg, int resp)
+{
+ int ret;
+ size_t len;
+ char *rbp;
+ const char *reqp;
+
+ if (cvs_server_in == NULL) {
+ cvs_log(LP_ERR, "cannot send request: Not connected");
+ return (-1);
+ }
+
+ reqp = cvs_req_getbyid(rid);
+ if (reqp == NULL) {
+ cvs_log(LP_ERR, "unsupported request type %u", rid);
+ return (-1);
+ }
+
+ snprintf(cvs_client_buf, sizeof(cvs_client_buf), "%s %s\n", reqp,
+ (arg == NULL) ? "" : arg);
+
+ rbp = cvs_client_buf;
+
+ if (cvs_server_inlog != NULL)
+ fputs(cvs_client_buf, cvs_server_inlog);
+
+ ret = fputs(cvs_client_buf, cvs_server_in);
+ if (ret == EOF) {
+ cvs_log(LP_ERRNO, "failed to send request to server");
+ return (-1);
+ }
+
+ if (resp) {
+ do {
+ /* wait for incoming data */
+ if (fgets(cvs_client_buf, sizeof(cvs_client_buf),
+ cvs_server_out) == NULL) {
+ if (feof(cvs_server_out))
+ return (0);
+ cvs_log(LP_ERRNO,
+ "failed to read response from server");
+ return (-1);
+ }
+
+ if (cvs_server_outlog != NULL)
+ fputs(cvs_client_buf, cvs_server_outlog);
+
+ if ((len = strlen(cvs_client_buf)) != 0) {
+ if (cvs_client_buf[len - 1] != '\n') {
+ /* truncated line */
+ }
+ else
+ cvs_client_buf[--len] = '\0';
+ }
+
+ ret = cvs_resp_handle(cvs_client_buf);
+ } while (ret == 0);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_client_sendln()
+ *
+ * Send a single line <line> string to the server. The line is sent as is,
+ * without any modifications.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_client_sendln(const char *line)
+{
+ int nl;
+ size_t len;
+
+ nl = 0;
+ len = strlen(line);
+
+ if ((len > 0) && (line[len - 1] != '\n'))
+ nl = 1;
+
+ if (cvs_server_inlog != NULL) {
+ fputs(line, cvs_server_inlog);
+ if (nl)
+ fputc('\n', cvs_server_inlog);
+ }
+ fputs(line, cvs_server_in);
+ if (nl)
+ fputc('\n', cvs_server_in);
+
+ return (0);
+}
+
+
+/*
+ * cvs_client_sendraw()
+ *
+ * Send the first <len> bytes from the buffer <src> to the server.
+ */
+
+int
+cvs_client_sendraw(const void *src, size_t len)
+{
+ if (cvs_server_inlog != NULL)
+ fwrite(src, sizeof(char), len, cvs_server_inlog);
+ if (fwrite(src, sizeof(char), len, cvs_server_in) < len) {
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_client_recvraw()
+ *
+ * Receive the first <len> bytes from the buffer <src> to the server.
+ */
+
+ssize_t
+cvs_client_recvraw(void *dst, size_t len)
+{
+ size_t ret;
+
+ ret = fread(dst, sizeof(char), len, cvs_server_out);
+ if (ret == 0)
+ return (-1);
+ if (cvs_server_outlog != NULL)
+ fwrite(dst, sizeof(char), len, cvs_server_outlog);
+ return (ssize_t)ret;
+}
+
+
+/*
+ * cvs_client_getln()
+ *
+ * Get a line from the server's output and store it in <lbuf>. The terminating
+ * newline character is stripped from the result.
+ */
+
+int
+cvs_client_getln(char *lbuf, size_t len)
+{
+ size_t rlen;
+
+ if (fgets(lbuf, len, cvs_server_out) == NULL) {
+ if (ferror(cvs_server_out)) {
+ cvs_log(LP_ERRNO, "failed to read line from server");
+ return (-1);
+ }
+
+ if (feof(cvs_server_out))
+ *lbuf = '\0';
+ }
+
+ if (cvs_server_outlog != NULL)
+ fputs(lbuf, cvs_server_outlog);
+
+ rlen = strlen(lbuf);
+ if ((rlen > 0) && (lbuf[rlen - 1] == '\n'))
+ lbuf[--rlen] = '\0';
+
+ return (0);
+}
+
+
+/*
+ * cvs_client_sendinfo()
+ *
+ * Initialize the connection status by first requesting the list of
+ * supported requests from the server. Then, we send the CVSROOT variable
+ * with the `Root' request.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+static int
+cvs_client_sendinfo(void)
+{
+ char *vresp;
+ /*
+ * First, send the server the list of valid responses, then ask
+ * for valid requests
+ */
+
+ vresp = cvs_resp_getvalid();
+ if (vresp == NULL) {
+ cvs_log(LP_ERR, "can't generate list of valid responses");
+ return (-1);
+ }
+
+ if (cvs_client_sendreq(CVS_REQ_VALIDRESP, vresp, 0) < 0) {
+ }
+ free(vresp);
+
+ if (cvs_client_sendreq(CVS_REQ_VALIDREQ, NULL, 1) < 0) {
+ cvs_log(LP_ERR, "failed to get valid requests from server");
+ return (-1);
+ }
+
+ /* now share our global options with the server */
+ if (verbosity == 1)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-q", 0);
+ else if (verbosity == 0)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-Q", 0);
+
+ if (cvs_nolog)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-l", 0);
+ if (cvs_readonly)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-r", 0);
+ if (cvs_trace)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-t", 0);
+
+ /* now send the CVSROOT to the server */
+ if (cvs_client_sendreq(CVS_REQ_ROOT, cvs_root->cr_dir, 0) < 0)
+ return (-1);
+
+ /* not sure why, but we have to send this */
+ if (cvs_client_sendreq(CVS_REQ_USEUNCHANGED, NULL, 0) < 0)
+ return (-1);
+
+ return (0);
+}
+
+
+/*
+ * cvs_client_senddir()
+ *
+ * Send a `Directory' request along with the 2 paths that follow it.
+ */
+
+int
+cvs_client_senddir(const char *dir)
+{
+ char repo[MAXPATHLEN], buf[MAXPATHLEN];
+
+ /* don't bother sending if it's the same as the last Directory sent */
+ if (strcmp(dir, cvs_lastdir) == 0)
+ return (0);
+
+ if (cvs_readrepo(dir, repo, sizeof(repo)) < 0)
+ return (-1);
+
+ snprintf(buf, sizeof(buf), "%s/%s", cvs_root->cr_dir, repo);
+
+ if ((cvs_client_sendreq(CVS_REQ_DIRECTORY, dir, 0) < 0) ||
+ (cvs_client_sendln(buf) < 0))
+ return (-1);
+ strlcpy(cvs_lastdir, dir, sizeof(cvs_lastdir));
+
+ return (0);
+}
+
+
+/*
+ * cvs_client_sendarg()
+ *
+ * Send the argument <arg> to the server. The argument <append> is used to
+ * determine if the argument should be simply appended to the last argument
+ * sent or if it should be created as a new argument (0).
+ */
+
+int
+cvs_client_sendarg(const char *arg, int append)
+{
+ return cvs_client_sendreq(((append == 0) ?
+ CVS_REQ_ARGUMENT : CVS_REQ_ARGUMENTX), arg, 0);
+}
+
+
+/*
+ * cvs_client_sendentry()
+ *
+ * Send an `Entry' request to the server along with the mandatory fields from
+ * the CVS entry <ent> (which are the name and revision).
+ */
+
+int
+cvs_client_sendentry(const struct cvs_ent *ent)
+{
+ char ebuf[128], numbuf[64];
+
+ snprintf(ebuf, sizeof(ebuf), "/%s/%s///", ent->ce_name,
+ rcsnum_tostr(ent->ce_rev, numbuf, sizeof(numbuf)));
+
+ return cvs_client_sendreq(CVS_REQ_ENTRY, ebuf, 0);
+}
+
+
+/*
+ * cvs_client_initlog()
+ *
+ * Initialize protocol logging if the CVS_CLIENT_LOG environment variable is
+ * set. In this case, the variable's value is used as a path to which the
+ * appropriate suffix is added (".in" for server input and ".out" for server
+ * output.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+static int
+cvs_client_initlog(void)
+{
+ char *env, fpath[MAXPATHLEN];
+
+ env = getenv("CVS_CLIENT_LOG");
+ if (env == NULL)
+ return (0);
+
+ strlcpy(fpath, env, sizeof(fpath));
+ strlcat(fpath, ".in", sizeof(fpath));
+ cvs_server_inlog = fopen(fpath, "w");
+ if (cvs_server_inlog == NULL) {
+ cvs_log(LP_ERRNO, "failed to open server input log `%s'",
+ fpath);
+ return (-1);
+ }
+
+ strlcpy(fpath, env, sizeof(fpath));
+ strlcat(fpath, ".out", sizeof(fpath));
+ cvs_server_outlog = fopen(fpath, "w");
+ if (cvs_server_outlog == NULL) {
+ cvs_log(LP_ERRNO, "failed to open server output log `%s'",
+ fpath);
+ return (-1);
+ }
+
+ /* make the streams line-buffered */
+ setvbuf(cvs_server_inlog, NULL, _IOLBF, 0);
+ setvbuf(cvs_server_outlog, NULL, _IOLBF, 0);
+
+ return (0);
+}
+
+
+/*
+ * cvs_client_sendfile()
+ *
+ */
+
+int
+cvs_client_sendfile(const char *path)
+{
+ int fd;
+ ssize_t ret;
+ char buf[4096];
+ struct stat st;
+
+ if (stat(path, &st) == -1) {
+ cvs_log(LP_ERRNO, "failed to stat `%s'", path);
+ return (-1);
+ }
+
+ fd = open(path, O_RDONLY, 0);
+ if (fd == -1) {
+ return (-1);
+ }
+
+ if (cvs_modetostr(st.st_mode, buf, sizeof(buf)) < 0)
+ return (-1);
+
+ cvs_client_sendln(buf);
+ snprintf(buf, sizeof(buf), "%lld\n", st.st_size);
+ cvs_client_sendln(buf);
+
+ while ((ret = read(fd, buf, sizeof(buf))) != 0) {
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "failed to read file `%s'", path);
+ return (-1);
+ }
+
+ cvs_client_sendraw(buf, (size_t)ret);
+
+ }
+
+ (void)close(fd);
+
+ return (0);
+}
diff --git a/usr.bin/cvs/commit.c b/usr.bin/cvs/commit.c
new file mode 100644
index 00000000000..8e6a3b99463
--- /dev/null
+++ b/usr.bin/cvs/commit.c
@@ -0,0 +1,145 @@
+/* $OpenBSD: commit.c,v 1.1.1.1 2004/07/13 22:02:40 jfb 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 <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "cvs.h"
+#include "log.h"
+
+
+
+
+static char* cvs_commit_openmsg (const char *);
+
+
+
+
+/*
+ * cvs_commit()
+ *
+ * Handler for the `cvs commit' command.
+ */
+
+int
+cvs_commit(int argc, char **argv)
+{
+ int ch, recurse;
+ char *msg, *mfile;
+
+ recurse = 1;
+ mfile = NULL;
+ msg = NULL;
+
+ while ((ch = getopt(argc, argv, "F:flm:R")) != -1) {
+ switch (ch) {
+ case 'F':
+ mfile = optarg;
+ break;
+ case 'f':
+ recurse = 0;
+ break;
+ case 'l':
+ recurse = 0;
+ break;
+ case 'm':
+ msg = optarg;
+ break;
+ case 'R':
+ recurse = 1;
+ break;
+ default:
+ return (EX_USAGE);
+ }
+ }
+
+ if ((msg != NULL) && (mfile != NULL)) {
+ cvs_log(LP_ERR, "the -F and -m flags are mutually exclusive");
+ return (EX_USAGE);
+ }
+
+ if ((mfile != NULL) && (msg = cvs_commit_openmsg(mfile)) == NULL)
+ return (EX_DATAERR);
+
+ argc -= optind;
+ argv += optind;
+
+ return (0);
+}
+
+
+/*
+ * cvs_commit_openmsg()
+ *
+ * Open the file specified by <path> and allocate a buffer large enough to
+ * hold all of the file's contents. The returned value must later be freed
+ * using the free() function.
+ * Returns a pointer to the allocated buffer on success, or NULL on failure.
+ */
+
+static char*
+cvs_commit_openmsg(const char *path)
+{
+ int fd;
+ size_t sz;
+ char *msg;
+ struct stat st;
+
+ if (stat(path, &st) == -1) {
+ cvs_log(LP_ERRNO, "failed to stat `%s'", path);
+ return (NULL);
+ }
+
+ sz = st.st_size + 1;
+
+ msg = (char *)malloc(sz);
+ if (msg == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate message buffer");
+ return (NULL);
+ }
+
+ fd = open(path, O_RDONLY, 0);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to open message file `%s'", path);
+ return (NULL);
+ }
+
+ if (read(fd, msg, sz - 1) == -1) {
+ cvs_log(LP_ERRNO, "failed to read CVS commit message");
+ return (NULL);
+ }
+ msg[sz - 1] = '\0';
+
+ return (msg);
+}
diff --git a/usr.bin/cvs/cvs.h b/usr.bin/cvs/cvs.h
new file mode 100644
index 00000000000..6689d03510f
--- /dev/null
+++ b/usr.bin/cvs/cvs.h
@@ -0,0 +1,374 @@
+/* $OpenBSD: cvs.h,v 1.1.1.1 2004/07/13 22:02:40 jfb 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 CVS_H
+#define CVS_H
+
+#include <sys/param.h>
+
+#include "rcs.h"
+
+#define CVS_VERSION "OpenCVS 0.1"
+
+
+#define CVS_HIST_CACHE 128
+#define CVS_HIST_NBFLD 6
+
+
+#define CVS_REQ_TIMEOUT 300
+
+
+
+#define CVS_CKSUM_LEN 33 /* length of a CVS checksum string */
+
+
+/* operations */
+#define CVS_OP_ADD 1
+#define CVS_OP_ANNOTATE 2
+#define CVS_OP_COMMIT 3
+#define CVS_OP_DIFF 4
+#define CVS_OP_TAG 5
+#define CVS_OP_UPDATE 6
+
+
+
+
+/* methods */
+#define CVS_METHOD_NONE 0
+#define CVS_METHOD_LOCAL 1 /* local access */
+#define CVS_METHOD_SERVER 2 /* tunnel through CVS_RSH */
+#define CVS_METHOD_PSERVER 3 /* cvs pserver */
+#define CVS_METHOD_KSERVER 4 /* kerberos */
+#define CVS_METHOD_GSERVER 5 /* gssapi server */
+#define CVS_METHOD_EXT 6
+#define CVS_METHOD_FORK 7 /* local but fork */
+
+/* client/server protocol requests */
+#define CVS_REQ_NONE 0
+#define CVS_REQ_ROOT 1
+#define CVS_REQ_VALIDREQ 2
+#define CVS_REQ_VALIDRESP 3
+#define CVS_REQ_DIRECTORY 4
+#define CVS_REQ_MAXDOTDOT 5
+#define CVS_REQ_STATICDIR 6
+#define CVS_REQ_STICKY 7
+#define CVS_REQ_ENTRY 8
+#define CVS_REQ_ENTRYEXTRA 9
+#define CVS_REQ_CHECKINTIME 10
+#define CVS_REQ_MODIFIED 11
+#define CVS_REQ_ISMODIFIED 12
+#define CVS_REQ_UNCHANGED 13
+#define CVS_REQ_USEUNCHANGED 14
+#define CVS_REQ_NOTIFY 15
+#define CVS_REQ_NOTIFYUSER 16
+#define CVS_REQ_QUESTIONABLE 17
+#define CVS_REQ_CASE 18
+#define CVS_REQ_UTF8 19
+#define CVS_REQ_ARGUMENT 20
+#define CVS_REQ_ARGUMENTX 21
+#define CVS_REQ_GLOBALOPT 22
+#define CVS_REQ_GZIPSTREAM 23
+#define CVS_REQ_KERBENCRYPT 24
+#define CVS_REQ_GSSENCRYPT 25
+#define CVS_REQ_PROTOENCRYPT 26
+#define CVS_REQ_GSSAUTH 27
+#define CVS_REQ_PROTOAUTH 28
+#define CVS_REQ_READCVSRC2 29
+#define CVS_REQ_READWRAP 30
+#define CVS_REQ_ERRIFREADER 31
+#define CVS_REQ_VALIDRCSOPT 32
+#define CVS_REQ_READIGNORE 33
+#define CVS_REQ_SET 34
+#define CVS_REQ_XPANDMOD 35
+#define CVS_REQ_CI 36
+#define CVS_REQ_CHOWN 37
+#define CVS_REQ_SETOWN 38
+#define CVS_REQ_SETPERM 39
+#define CVS_REQ_CHACL 40
+#define CVS_REQ_LISTPERM 41
+#define CVS_REQ_LISTACL 42
+#define CVS_REQ_SETPASS 43
+#define CVS_REQ_PASSWD 44
+#define CVS_REQ_DIFF 45
+#define CVS_REQ_STATUS 46
+#define CVS_REQ_LS 47
+#define CVS_REQ_TAG 48
+#define CVS_REQ_IMPORT 49
+#define CVS_REQ_ADMIN 50
+#define CVS_REQ_HISTORY 51
+#define CVS_REQ_WATCHERS 52
+#define CVS_REQ_EDITORS 53
+#define CVS_REQ_ANNOTATE 54
+#define CVS_REQ_LOG 55
+#define CVS_REQ_CO 56
+#define CVS_REQ_EXPORT 57
+#define CVS_REQ_RANNOTATE 58
+#define CVS_REQ_INIT 59
+#define CVS_REQ_UPDATE 60
+#define CVS_REQ_ADD 62
+#define CVS_REQ_REMOVE 63
+#define CVS_REQ_NOOP 64
+#define CVS_REQ_RTAG 65
+#define CVS_REQ_RELEASE 66
+#define CVS_REQ_RLOG 67
+#define CVS_REQ_RDIFF 68
+#define CVS_REQ_VERSION 69
+
+#define CVS_REQ_MAX 69
+
+
+/* responses */
+#define CVS_RESP_OK 1
+#define CVS_RESP_ERROR 2
+#define CVS_RESP_VALIDREQ 3
+#define CVS_RESP_CHECKEDIN 4
+#define CVS_RESP_NEWENTRY 5
+#define CVS_RESP_CKSUM 6
+#define CVS_RESP_COPYFILE 7
+#define CVS_RESP_UPDATED 8
+#define CVS_RESP_CREATED 9
+#define CVS_RESP_UPDEXIST 10
+#define CVS_RESP_MERGED 11
+#define CVS_RESP_PATCHED 12
+#define CVS_RESP_RCSDIFF 13
+#define CVS_RESP_MODE 14
+#define CVS_RESP_MODTIME 15
+#define CVS_RESP_REMOVED 16
+#define CVS_RESP_RMENTRY 17
+#define CVS_RESP_SETSTATDIR 18
+#define CVS_RESP_CLRSTATDIR 19
+#define CVS_RESP_SETSTICKY 20
+#define CVS_RESP_CLRSTICKY 21
+#define CVS_RESP_TEMPLATE 22
+#define CVS_RESP_SETCIPROG 23
+#define CVS_RESP_SETUPDPROG 24
+#define CVS_RESP_NOTIFIED 25
+#define CVS_RESP_MODXPAND 26
+#define CVS_RESP_WRAPRCSOPT 27
+#define CVS_RESP_M 28
+#define CVS_RESP_MBINARY 29
+#define CVS_RESP_E 30
+#define CVS_RESP_F 31
+#define CVS_RESP_MT 32
+
+
+
+
+#define CVS_CMD_MAXNAMELEN 16
+#define CVS_CMD_MAXALIAS 2
+#define CVS_CMD_MAXDESCRLEN 64
+
+
+/* defaults */
+#define CVS_RSH_DEFAULT "ssh"
+#define CVS_EDITOR_DEFAULT "vi"
+
+
+/* server-side paths */
+#define CVS_PATH_ROOT "CVSROOT"
+#define CVS_PATH_COMMITINFO CVS_PATH_ROOT "/commitinfo"
+#define CVS_PATH_CONFIG CVS_PATH_ROOT "/config"
+#define CVS_PATH_CVSIGNORE CVS_PATH_ROOT "/cvsignore"
+#define CVS_PATH_CVSWRAPPERS CVS_PATH_ROOT "/cvswrappers"
+#define CVS_PATH_EDITINFO CVS_PATH_ROOT "/editinfo"
+#define CVS_PATH_HISTORY CVS_PATH_ROOT "/history"
+#define CVS_PATH_LOGINFO CVS_PATH_ROOT "/loginfo"
+#define CVS_PATH_MODULES CVS_PATH_ROOT "/modules"
+#define CVS_PATH_NOTIFY CVS_PATH_ROOT "/notify"
+#define CVS_PATH_RCSINFO CVS_PATH_ROOT "/rcsinfo"
+#define CVS_PATH_TAGINFO CVS_PATH_ROOT "/taginfo"
+#define CVS_PATH_VERIFYMSG CVS_PATH_ROOT "/verifymsg"
+
+
+/* client-side paths */
+#define CVS_PATH_RC ".cvsrc"
+#define CVS_PATH_CVSDIR "CVS"
+#define CVS_PATH_ENTRIES CVS_PATH_CVSDIR "/Entries"
+#define CVS_PATH_STATICENTRIES CVS_PATH_CVSDIR "/Entries.Static"
+#define CVS_PATH_LOGENTRIES CVS_PATH_CVSDIR "/Entries.Log"
+#define CVS_PATH_ROOTSPEC CVS_PATH_CVSDIR "/Root"
+
+
+struct cvs_op {
+ u_int co_op;
+ uid_t co_uid; /* user performing the operation */
+ char *co_path; /* target path of the operation */
+ char *co_tag; /* tag or branch, NULL if HEAD */
+};
+
+
+
+
+
+
+struct cvsroot {
+ u_int cr_method;
+ char *cr_buf;
+ char *cr_user;
+ char *cr_pass;
+ char *cr_host;
+ char *cr_dir;
+ u_int cr_port;
+};
+
+
+#define CVS_HIST_ADDED 'A'
+#define CVS_HIST_EXPORT 'E'
+#define CVS_HIST_RELEASE 'F'
+#define CVS_HIST_MODIFIED 'M'
+#define CVS_HIST_CHECKOUT 'O'
+#define CVS_HIST_COMMIT 'R'
+#define CVS_HIST_TAG 'T'
+
+
+#define CVS_ENT_NONE 0
+#define CVS_ENT_FILE 1
+#define CVS_ENT_DIR 2
+
+
+struct cvs_ent {
+ char *ce_line;
+ char *ce_buf;
+ u_int ce_type;
+ char *ce_name;
+ RCSNUM *ce_rev;
+ char *ce_timestamp;
+ char *ce_opts;
+ char *ce_tag;
+};
+
+typedef struct cvs_entries {
+ char *cef_path;
+
+ u_int cef_nid; /* next entry index to return for next() */
+
+ struct cvs_ent **cef_entries;
+ u_int cef_nbent;
+} CVSENTRIES;
+
+
+
+struct cvs_hent {
+ char ch_event;
+ time_t ch_date;
+ uid_t ch_uid;
+ char *ch_user;
+ char *ch_curdir;
+ char *ch_repo;
+ RCSNUM *ch_rev;
+ char *ch_arg;
+};
+
+
+typedef struct cvs_histfile {
+ int chf_fd;
+ char *chf_buf; /* read buffer */
+ size_t chf_blen; /* buffer size */
+ size_t chf_bused; /* bytes used in buffer */
+
+ off_t chf_off; /* next read */
+ u_int chf_sindex; /* history entry index of first in array */
+ u_int chf_cindex; /* current index (for getnext()) */
+ u_int chf_nbhent; /* number of valid entries in the array */
+
+ struct cvs_hent chf_hent[CVS_HIST_CACHE];
+
+} CVSHIST;
+
+
+
+/* client command handlers */
+int cvs_add (int, char **);
+int cvs_commit (int, char **);
+int cvs_diff (int, char **);
+int cvs_getlog (int, char **);
+int cvs_history (int, char **);
+int cvs_init (int, char **);
+int cvs_server (int, char **);
+int cvs_update (int, char **);
+int cvs_version (int, char **);
+
+
+/* proto.c */
+int cvs_req_handle (char *);
+const char* cvs_req_getbyid (int);
+int cvs_req_getbyname (const char *);
+char* cvs_req_getvalid (void);
+
+int cvs_resp_handle (char *);
+const char* cvs_resp_getbyid (int);
+int cvs_resp_getbyname (const char *);
+char* cvs_resp_getvalid (void);
+
+int cvs_sendfile (const char *);
+int cvs_recvfile (const char *);
+
+
+/* from client.c */
+int cvs_client_connect (void);
+void cvs_client_disconnect (void);
+int cvs_client_sendreq (u_int, const char *, int);
+int cvs_client_sendarg (const char *, int);
+int cvs_client_sendln (const char *);
+int cvs_client_sendraw (const void *, size_t);
+ssize_t cvs_client_recvraw (void *, size_t);
+int cvs_client_getln (char *, size_t);
+int cvs_client_senddir (const char *);
+
+
+/* from root.c */
+struct cvsroot* cvsroot_parse (const char *);
+void cvsroot_free (struct cvsroot *);
+struct cvsroot* cvsroot_get (const char *);
+
+
+/* Entries API */
+CVSENTRIES* cvs_ent_open (const char *);
+struct cvs_ent* cvs_ent_get (CVSENTRIES *, const char *);
+struct cvs_ent* cvs_ent_next (CVSENTRIES *);
+int cvs_ent_add (CVSENTRIES *, struct cvs_ent *);
+int cvs_ent_remove (CVSENTRIES *, const char *);
+struct cvs_ent* cvs_ent_parse (const char *);
+void cvs_ent_close (CVSENTRIES *);
+
+/* history API */
+CVSHIST* cvs_hist_open (const char *);
+void cvs_hist_close (CVSHIST *);
+int cvs_hist_parse (CVSHIST *);
+struct cvs_hent* cvs_hist_getnext (CVSHIST *);
+int cvs_hist_append (CVSHIST *, struct cvs_hent *);
+
+
+/* from util.c */
+int cvs_readrepo (const char *, char *, size_t);
+int cvs_splitpath (const char *, char *, size_t, char *, size_t);
+int cvs_modetostr (mode_t, char *, size_t);
+int cvs_strtomode (const char *, mode_t *);
+int cvs_cksum (const char *, char *, size_t);
+int cvs_exec (int, char **, int []);
+
+
+#endif /* CVS_H */
diff --git a/usr.bin/cvs/diff.c b/usr.bin/cvs/diff.c
new file mode 100644
index 00000000000..7dab01ee33d
--- /dev/null
+++ b/usr.bin/cvs/diff.c
@@ -0,0 +1,1678 @@
+/* $OpenBSD: diff.c,v 1.1.1.1 2004/07/13 22:02:40 jfb 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 <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <regex.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "cvs.h"
+#include "log.h"
+#include "buf.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 */
+
+/*
+ * Status values for print_status() and 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 cand {
+ int x;
+ int y;
+ int pred;
+} cand;
+
+struct line {
+ int serial;
+ int value;
+} *file[2];
+
+/*
+ * The following struct is used to record change information 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 excludes {
+ char *pattern;
+ struct excludes *next;
+};
+
+
+char *splice(char *, char *);
+int cvs_diffreg(const char *, const char *);
+int cvs_diff_file (const char *, const char *, const char *);
+int cvs_diff_dir (const char *, int);
+static void output(const char *, FILE *, const char *, 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 void 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(const char *, FILE *, const char *, FILE *, int, int, int, int);
+static void sort(struct line *, int);
+static int ignoreline(char *);
+static int asciifile(FILE *);
+static int 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 __inline int min(int, int);
+static __inline int max(int, int);
+static char *match_function(const long *, int, FILE *);
+static char *preadline(int, size_t, off_t);
+
+
+
+extern int cvs_client;
+extern struct cvsroot *cvs_root;
+
+
+
+static int aflag, bflag, dflag, iflag, tflag, Tflag, wflag;
+static int context, status;
+static int format = D_NORMAL;
+static struct stat stb1, stb2;
+static char *ifdefname, *diffargs, *ignore_pats, *diff_file;
+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 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;
+
+
+
+
+/*
+ * 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
+};
+
+
+
+/*
+ * cvs_diff()
+ *
+ * Handler for the `cvs diff' command.
+ *
+ * SYNOPSIS: cvs [args] diff [-clipu] [-D date] [-r rev]
+ */
+
+int
+cvs_diff(int argc, char **argv)
+{
+ int i, ch, recurse;
+ size_t dalen;
+ char dir[MAXPATHLEN], file[MAXPATHLEN], *d1, *d2, *r1, *r2;
+
+ context = CVS_DIFF_DEFCTX;
+
+ d1 = d2 = NULL;
+ r1 = r2 = NULL;
+ recurse = 1;
+
+ while ((ch = getopt(argc, argv, "cD:lir:u")) != -1) {
+ switch (ch) {
+ case 'c':
+ format = D_CONTEXT;
+ break;
+ case 'D':
+ if (d1 == NULL && r1 == NULL)
+ d1 = optarg;
+ else if (d2 == NULL && r2 == NULL)
+ d2 = optarg;
+ else {
+ cvs_log(LP_ERR,
+ "no more than two revisions/dates can "
+ "be specified");
+ }
+ break;
+ case 'l':
+ recurse = 0;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'r':
+ if ((r1 == NULL) && (d1 == NULL))
+ r1 = optarg;
+ else if ((r2 == NULL) && (d2 == NULL))
+ r2 = optarg;
+ else {
+ cvs_log(LP_ERR,
+ "no more than two revisions/dates can "
+ "be specified");
+ return (EX_USAGE);
+ }
+ break;
+ case 'u':
+ format = D_UNIFIED;
+ break;
+ default:
+ return (EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ /* get the CVSROOT from current dir */
+ strlcpy(dir, ".", sizeof(dir));
+
+ cvs_root = cvsroot_get(dir);
+ if (cvs_root == NULL)
+ return (EX_USAGE);
+
+ if (cvs_root->cr_method != CVS_METHOD_LOCAL) {
+ cvs_client_connect();
+
+ /* send the flags */
+ if (format == D_CONTEXT)
+ cvs_client_sendarg("-c", 0);
+ else if (format == D_UNIFIED)
+ cvs_client_sendarg("-u", 0);
+
+ if (r1 != NULL) {
+ cvs_client_sendarg("-r", 0);
+ cvs_client_sendarg(r1, 1);
+ }
+ else if (d1 != NULL) {
+ cvs_client_sendarg("-D", 0);
+ cvs_client_sendarg(d1, 1);
+ }
+ if (r2 != NULL) {
+ cvs_client_sendarg("-r", 0);
+ cvs_client_sendarg(r2, 1);
+ }
+ else if (d2 != NULL) {
+ cvs_client_sendarg("-D", 0);
+ cvs_client_sendarg(d2, 1);
+ }
+ }
+
+ cvs_diff_dir(dir, recurse);
+ }
+ else {
+ for (i = 0; i < argc; i++) {
+ cvs_splitpath(argv[i], dir, sizeof(dir),
+ file, sizeof(file));
+ cvs_root = cvsroot_get(dir);
+ if (cvs_root == NULL)
+ return (EX_USAGE);
+
+ if (cvs_root->cr_method != CVS_METHOD_LOCAL) {
+ cvs_client_connect();
+
+ if (i == 0) {
+ /* send the flags */
+ if (format == D_CONTEXT)
+ cvs_client_sendarg("-c", 0);
+ else if (format == D_UNIFIED)
+ cvs_client_sendarg("-u", 0);
+ }
+ }
+
+ cvs_diff_file(argv[i], r1, r2);
+ }
+ }
+
+ if (cvs_root->cr_method != CVS_METHOD_LOCAL)
+ cvs_client_sendreq(CVS_REQ_DIFF, NULL, 1);
+
+ return (0);
+}
+
+
+/*
+ * cvs_diff_file()
+ *
+ * Diff a single file.
+ */
+
+int
+cvs_diff_file(const char *path, const char *rev1, const char *rev2)
+{
+ int modif;
+ char dir[MAXPATHLEN], file[MAXPATHLEN], rcspath[MAXPATHLEN];
+ char repo[MAXPATHLEN], buf[64];
+ time_t tsec;
+ BUF *b1, *b2;
+ RCSNUM *r1, *r2;
+ RCSFILE *rf;
+ CVSENTRIES *entf;
+ struct tm tmstamp;
+ struct stat fst;
+ struct cvs_ent *entp;
+
+ rf = NULL;
+ diff_file = path;
+
+ if (stat(path, &fst) == -1) {
+ cvs_log(LP_ERRNO, "cannot find %s", path);
+ return (-1);
+ }
+
+ cvs_splitpath(path, dir, sizeof(dir), file, sizeof(file));
+ cvs_readrepo(dir, repo, sizeof(repo));
+
+ entf = cvs_ent_open(dir);
+ if (entf == NULL) {
+ cvs_log(LP_ERR, "no CVS/Entries file in `%s'", dir);
+ return (-1);
+ }
+
+ entp = cvs_ent_get(entf, file);
+ if ((entp == NULL) && (cvs_root->cr_method == CVS_METHOD_LOCAL)) {
+ cvs_log(LP_WARN, "I know nothing about %s", path);
+ return (-1);
+ }
+
+ if (cvs_root->cr_method != CVS_METHOD_LOCAL) {
+ if (cvs_client_senddir(dir) < 0)
+ return (-1);
+ }
+
+ tsec = (time_t)fst.st_mtimespec.tv_sec;
+
+ if ((gmtime_r(&tsec, &tmstamp) == NULL) ||
+ (asctime_r(&tmstamp, buf) == NULL)) {
+ cvs_log(LP_ERR, "failed to generate file timestamp");
+ return (-1);
+ }
+ modif = (strcmp(buf, entp->ce_timestamp) == 0) ? 0 : 1;
+
+ if (cvs_root->cr_method != CVS_METHOD_LOCAL)
+ cvs_client_sendentry(entp);
+
+ if (!modif) {
+ if (cvs_root->cr_method != CVS_METHOD_LOCAL)
+ cvs_client_sendreq(CVS_REQ_UNCHANGED, file, 0);
+ cvs_ent_close(entf);
+ return (0);
+ }
+
+ /* at this point, the file is modified */
+ if (cvs_root->cr_method != CVS_METHOD_LOCAL) {
+ cvs_client_sendreq(CVS_REQ_MODIFIED, file, 0);
+ cvs_sendfile(path);
+ }
+ else {
+ snprintf(rcspath, sizeof(rcspath), "%s/%s/%s%s",
+ cvs_root->cr_dir, repo, path, RCS_FILE_EXT);
+
+ rf = rcs_open(rcspath, RCS_MODE_READ);
+ if (rf == NULL)
+ return (-1);
+
+ printf("Index: %s\n%s\nRCS file: %s\n", path,
+ RCS_DIFF_DIV, rcspath);
+
+ if (rev1 == NULL)
+ r1 = entp->ce_rev;
+ else {
+ r1 = rcsnum_alloc();
+ rcsnum_aton(rev1, NULL, r1);
+ }
+
+ printf("retrieving revision %s\n",
+ rcsnum_tostr(r1, buf, sizeof(buf)));
+ b1 = rcs_getrev(rf, r1);
+
+ if (rev2 != NULL) {
+ printf("retrieving revision %s\n", rev2);
+ r2 = rcsnum_alloc();
+ rcsnum_aton(rev2, NULL, r2);
+ b2 = rcs_getrev(rf, r2);
+ }
+ else {
+ b2 = cvs_buf_load(path, BUF_AUTOEXT);
+ }
+
+ printf("%s\n", diffargs);
+ cvs_buf_write(b1, "/tmp/diff1", 0600);
+ cvs_buf_write(b2, "/tmp/diff2", 0600);
+ cvs_diffreg("/tmp/diff1", "/tmp/diff2");
+ }
+
+ cvs_ent_close(entf);
+
+ return (0);
+}
+
+
+/*
+ * cvs_diff_dir()
+ *
+ */
+
+int
+cvs_diff_dir(const char *dir, int recurse)
+{
+ char path[MAXPATHLEN];
+ DIR *dirp;
+ CVSENTRIES *entf;
+ struct dirent *dentp;
+ struct cvs_ent *entp;
+
+ printf("cvs_diff_dir(%s)\n", dir);
+
+ dirp = opendir(dir);
+ if (dirp == NULL) {
+ cvs_log(LP_ERRNO, "failed to open directory `%s'", dir);
+ return (-1);
+ }
+
+ entf = cvs_ent_open(dir);
+ if (entf == NULL) {
+ cvs_log(LP_ERR, "no CVS/Entries file in `%s'", dir);
+ (void)closedir(dirp);
+ return (-1);
+ }
+
+ while ((dentp = readdir(dirp)) != NULL) {
+ if ((strcmp(dentp->d_name, "CVS") == 0) ||
+ (dentp->d_name[0] == '.'))
+ continue;
+
+ if (strcmp(dir, ".") != 0) {
+ strlcpy(path, dir, sizeof(path));
+ strlcat(path, "/", sizeof(path));
+ }
+ else
+ path[0] = '\0';
+ strlcat(path, dentp->d_name, sizeof(path));
+ if (dentp->d_type == DT_DIR) {
+ if (!recurse)
+ continue;
+ cvs_diff_dir(path, recurse);
+ }
+ else {
+ entp = cvs_ent_get(entf, dentp->d_name);
+ if (entp == NULL) {
+ cvs_client_sendreq(CVS_REQ_QUESTIONABLE, path,
+ 0);
+ }
+ else {
+#if 0
+ cvs_diff_file(path);
+#endif
+ }
+ }
+ }
+
+ return (0);
+}
+
+
+
+
+int
+cvs_diffreg(const char *file1, const char *file2)
+{
+ FILE *f1, *f2;
+ int i, rval;
+
+ f1 = f2 = NULL;
+ rval = D_SAME;
+ anychange = 0;
+ lastline = 0;
+ lastmatchline = 0;
+ context_vec_ptr = context_vec_start - 1;
+ chrtran = (iflag ? cup2low : clow2low);
+
+ f1 = fopen(file1, "r");
+ if (f1 == NULL) {
+ cvs_log(LP_ERRNO, "%s", file1);
+ status |= 2;
+ goto closem;
+ }
+
+ f2 = fopen(file2, "r");
+ if (f2 == NULL) {
+ cvs_log(LP_ERRNO, "%s", file2);
+ status |= 2;
+ goto closem;
+ }
+
+ switch (files_differ(f1, f2)) {
+ case 0:
+ goto closem;
+ case 1:
+ break;
+ default:
+ /* error */
+ status |= 2;
+ goto closem;
+ }
+
+ if (!asciifile(f1) || !asciifile(f2)) {
+ rval = D_BINARY;
+ status |= 1;
+ goto closem;
+ }
+ prepare(0, f1, stb1.st_size);
+ prepare(1, f2, stb2.st_size);
+ 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);
+ member = realloc(member, (slen[1] + 2) * sizeof(int));
+
+ class = (int *)file[0];
+ unsort(sfile[0], slen[0], class);
+ class = realloc(class, (slen[0] + 2) * sizeof(int));
+
+ klist = malloc((slen[0] + 2) * sizeof(int));
+ clen = 0;
+ clistlen = 100;
+ clist = malloc(clistlen * sizeof(cand));
+ i = stone(class, slen[0], member, klist);
+ free(member);
+ free(class);
+
+ J = realloc(J, (len[0] + 2) * sizeof(int));
+ unravel(klist[i]);
+ free(clist);
+ free(klist);
+
+ ixold = realloc(ixold, (len[0] + 2) * sizeof(long));
+ ixnew = realloc(ixnew, (len[1] + 2) * sizeof(long));
+ check(f1, f2);
+ output(file1, f1, file2, f2);
+
+closem:
+ if (anychange) {
+ status |= 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, 1, sizeof(buf1), f1);
+ j = fread(buf2, 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);
+ }
+}
+
+
+char *
+splice(char *dir, char *file)
+{
+ char *tail, *buf;
+
+ if ((tail = strrchr(file, '/')) == NULL)
+ tail = file;
+ else
+ tail++;
+ asprintf(&buf, "%s/%s", dir, tail);
+ return (buf);
+}
+
+static void
+prepare(int i, FILE *fd, off_t filesize)
+{
+ struct line *p;
+ int j, h;
+ size_t sz;
+
+ rewind(fd);
+
+ sz = (filesize <= SIZE_MAX ? filesize : SIZE_MAX) / 25;
+ if (sz < 100)
+ sz = 100;
+
+ p = malloc((sz + 3) * sizeof(struct line));
+ for (j = 0; (h = readhash(fd));) {
+ if (j == (int)sz) {
+ sz = sz * 3 / 2;
+ p = realloc(p, (sz + 3) * sizeof(struct line));
+ }
+ p[++j].value = h;
+ }
+ len[i] = j;
+ file[i] = p;
+}
+
+static void
+prune(void)
+{
+ int i, j;
+
+ for (pref = 0; pref < len[0] && pref < len[1] &&
+ file[0][pref + 1].value == file[1][pref + 1].value;
+ pref++)
+ ;
+ for (suff = 0; suff < len[0] - pref && suff < len[1] - pref &&
+ file[0][len[0] - suff].value == file[1][len[1] - suff].value;
+ suff++)
+ ;
+ for (j = 0; j < 2; j++) {
+ sfile[j] = file[j] + pref;
+ slen[j] = 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 i, k, y, j, l;
+ int oldc, tc, oldl;
+ u_int numtries;
+
+ const u_int bound = dflag ? UINT_MAX : max(256, isqrt(n));
+
+ k = 0;
+ c[0] = newcand(0, 0, 0);
+ 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];
+ c[l] = newcand(i, y, oldc);
+ oldc = tc;
+ oldl = l;
+ numtries++;
+ } else {
+ c[l] = newcand(i, y, oldc);
+ k++;
+ break;
+ }
+ } while ((y = b[++j]) > 0 && numtries < bound);
+ }
+ return (k);
+}
+
+static int
+newcand(int x, int y, int pred)
+{
+ struct cand *q;
+
+ if (clen == clistlen) {
+ clistlen = clistlen * 11 / 10;
+ clist = realloc(clist, clistlen * sizeof(cand));
+ }
+ 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;
+ while (1) {
+ l = i + j;
+ if ((l >>= 1) <= 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 <= len[0]; i++)
+ J[i] = i <= pref ? i :
+ i > len[0] - suff ? i + len[1] - 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 <= 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 || wflag || iflag) {
+ for (;;) {
+ c = getc(f1);
+ d = getc(f2);
+ /*
+ * GNU diff ignores a missing newline
+ * in one file if bflag || wflag.
+ */
+ if ((bflag || wflag) &&
+ ((c == EOF && d == '\n') ||
+ (c == '\n' && d == EOF))) {
+ break;
+ }
+ ctold++;
+ ctnew++;
+ if (bflag && 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) {
+ 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 <= len[1]; j++)
+ ixnew[j] = ctnew += skipline(f2);
+ /*
+ * if (jackpot)
+ * fprintf(stderr, "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 = malloc((l + 1) * sizeof(int));
+ for (i = 1; i <= l; i++)
+ a[f[i].serial] = f[i].value;
+ for (i = 1; i <= l; i++)
+ b[i] = a[i];
+ free(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(const char *file1, FILE *f1, const char *file2, FILE *f2)
+{
+ int m, i0, i1, j0, j1;
+
+ rewind(f1);
+ rewind(f2);
+ m = len[0];
+ J[0] = 0;
+ J[m + 1] = 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(file1, f1, file2, f2, i0, i1, j0, j1);
+ }
+ if (m == 0)
+ change(file1, f1, file2, f2, 1, 0, 1, len[1]);
+ if (format == D_IFDEF) {
+ for (;;) {
+#define c i0
+ if ((c = getc(f1)) == EOF)
+ return;
+ putchar(c);
+ }
+#undef c
+ }
+ if (anychange != 0) {
+ if (format == D_CONTEXT)
+ dump_context_vec(f1, f2);
+ else if (format == D_UNIFIED)
+ dump_unified_vec(f1, f2);
+ }
+}
+
+static __inline void
+range(int a, int b, char *separator)
+{
+ printf("%d", a > b ? b : a);
+ if (a < b)
+ printf("%s%d", separator, b);
+}
+
+static __inline void
+uni_range(int a, int b)
+{
+ if (a < b)
+ printf("%d,%d", a, b - a + 1);
+ else if (a == b)
+ printf("%d", b);
+ else
+ printf("%d,0", b);
+}
+
+static char *
+preadline(int fd, size_t len, off_t off)
+{
+ char *line;
+ ssize_t nr;
+
+ line = malloc(len + 1);
+ if (line == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate line");
+ return (NULL);
+ }
+ if ((nr = pread(fd, line, len, off)) < 0) {
+ cvs_log(LP_ERRNO, "preadline failed");
+ return (NULL);
+ }
+ line[nr] = '\0';
+ return (line);
+}
+
+static int
+ignoreline(char *line)
+{
+ int ret;
+
+ ret = regexec(&ignore_re, line, 0, NULL, 0);
+ free(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(const char *file1, FILE *f1, const char *file2, FILE *f2,
+ int a, int b, int c, int d)
+{
+ static size_t max_context = 64;
+ int i;
+
+ if (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 (format == D_CONTEXT || format == D_UNIFIED) {
+ /*
+ * Allocate change records as needed.
+ */
+ if (context_vec_ptr == context_vec_end - 1) {
+ ptrdiff_t offset = context_vec_ptr - context_vec_start;
+ max_context <<= 1;
+ context_vec_start = realloc(context_vec_start,
+ max_context * sizeof(struct context_vec));
+ 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.
+ */
+ printf("%s %s %s",
+ format == D_CONTEXT ? "***" : "---", diff_file,
+ ctime(&stb1.st_mtime));
+ printf("%s %s %s",
+ format == D_CONTEXT ? "---" : "+++", diff_file,
+ ctime(&stb2.st_mtime));
+ 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 (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 (format) {
+ case D_BRIEF:
+ return;
+ case D_NORMAL:
+ range(a, b, ",");
+ putchar(a > b ? 'a' : c > d ? 'd' : 'c');
+ if (format == D_NORMAL)
+ range(c, d, ",");
+ putchar('\n');
+ break;
+ }
+ if (format == D_NORMAL || format == D_IFDEF) {
+ fetch(ixold, a, b, f1, '<', 1);
+ if (a <= b && c <= d && format == D_NORMAL)
+ puts("---");
+ }
+ i = fetch(ixnew, c, d, f2, format == D_NORMAL ? '>' : '\0', 0);
+ if (inifdef) {
+ printf("#endif /* %s */\n", ifdefname);
+ inifdef = 0;
+ }
+}
+
+static int
+fetch(long *f, int a, int b, FILE *lb, int ch, int oldfile)
+{
+ int i, j, c, lastc, col, nc;
+
+ /*
+ * When doing #ifdef's, copy down to current line
+ * if this is the first file, so that stuff makes it to output.
+ */
+ if (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++)
+ putchar(getc(lb));
+ }
+ if (a > b)
+ return (0);
+ if (format == D_IFDEF) {
+ if (inifdef) {
+ printf("#else /* %s%s */\n",
+ oldfile == 1 ? "!" : "", ifdefname);
+ } else {
+ if (oldfile)
+ printf("#ifndef %s\n", ifdefname);
+ else
+ printf("#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 (format != D_IFDEF && ch != '\0') {
+ putchar(ch);
+ if (Tflag && (format == D_NORMAL || format == D_CONTEXT
+ || format == D_UNIFIED))
+ putchar('\t');
+ else if (format != D_UNIFIED)
+ putchar(' ');
+ }
+ col = 0;
+ for (j = 0, lastc = '\0'; j < nc; j++, lastc = c) {
+ if ((c = getc(lb)) == EOF) {
+ puts("\n\\ No newline at end of file");
+ return (0);
+ }
+ if (c == '\t' && tflag) {
+ do {
+ putchar(' ');
+ } while (++col & 7);
+ } else {
+ putchar(c);
+ col++;
+ }
+ }
+ }
+ return (0);
+}
+
+/*
+ * 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 && !wflag) {
+ if (iflag)
+ 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 && !wflag) {
+ 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];
+ int i, cnt;
+
+ if (aflag || f == NULL)
+ return (1);
+
+ rewind(f);
+ cnt = fread(buf, 1, sizeof(buf), f);
+ for (i = 0; i < cnt; i++)
+ if (!isprint(buf[i]) && !isspace(buf[i]))
+ return (0);
+ return (1);
+}
+
+static __inline int min(int a, int b)
+{
+ return (a < b ? a : b);
+}
+
+static __inline int max(int a, int b)
+{
+ return (a > b ? a : b);
+}
+
+static char *
+match_function(const long *f, int pos, FILE *file)
+{
+ char buf[FUNCTION_CONTEXT_SIZE];
+ size_t nc;
+ int last = lastline;
+ char *p;
+
+ lastline = pos;
+ while (pos > last) {
+ fseek(file, f[pos - 1], SEEK_SET);
+ nc = f[pos] - f[pos - 1];
+ if (nc >= sizeof(buf))
+ nc = sizeof(buf) - 1;
+ nc = fread(buf, 1, nc, file);
+ if (nc > 0) {
+ buf[nc] = '\0';
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ if (isalpha(buf[0]) || buf[0] == '_' || buf[0] == '$') {
+ strlcpy(lastbuf, 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(len[0], context_vec_ptr->b + context);
+ lowc = max(1, cvp->c - context);
+ upd = min(len[1], context_vec_ptr->d + context);
+
+ printf("***************");
+ printf("\n*** ");
+ range(lowa, upb, ",");
+ printf(" ****\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) {
+ 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 */
+ printf("--- ");
+ range(lowc, upd, ",");
+ printf(" ----\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) {
+ 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(len[0], context_vec_ptr->b + context);
+ lowc = max(1, cvp->c - context);
+ upd = min(len[1], context_vec_ptr->d + context);
+
+ fputs("@@ -", stdout);
+ uni_range(lowa, upb);
+ fputs(" +", stdout);
+ uni_range(lowc, upd);
+ fputs(" @@", stdout);
+ putchar('\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;
+}
diff --git a/usr.bin/cvs/rcs.c b/usr.bin/cvs/rcs.c
new file mode 100644
index 00000000000..ba7df7c5522
--- /dev/null
+++ b/usr.bin/cvs/rcs.c
@@ -0,0 +1,1557 @@
+/* $OpenBSD: rcs.c,v 1.1.1.1 2004/07/13 22:02:40 jfb 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 <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "rcs.h"
+#include "log.h"
+
+#define RCS_BUFSIZE 8192
+
+
+/* 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_line;
+
+ char *rp_buf;
+ size_t rp_blen;
+
+ /* pushback token buffer */
+ char rp_ptok[128];
+ int rp_pttype; /* token type, RCS_TOK_ERR if no token */
+
+ FILE *rp_file;
+};
+
+
+struct rcs_line {
+ char *rl_line;
+ int rl_lineno;
+ TAILQ_ENTRY(rcs_line) rl_list;
+};
+
+
+struct rcs_foo {
+ int rl_nblines;
+ char *rl_data;
+ TAILQ_HEAD(rcs_tqh, rcs_line) rl_lines;
+};
+
+
+
+static int rcs_parse_admin (RCSFILE *);
+static int rcs_parse_delta (RCSFILE *);
+static int rcs_parse_deltatext (RCSFILE *);
+
+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 struct rcs_delta* rcs_findrev (RCSFILE *, RCSNUM *);
+static struct rcs_foo* rcs_splitlines (const char *);
+
+#define RCS_TOKSTR(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_buf
+#define RCS_TOKLEN(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_blen
+
+
+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_STRING, 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_STRING, RCS_VOPT },
+ { "strict", RCS_TOK_STRICT, 0, 0, },
+ { "symbols", RCS_TOK_SYMBOLS, 0, 0 },
+ { "text", RCS_TOK_TEXT, RCS_TOK_STRING, RCS_NOSCOL },
+};
+
+
+
+/*
+ * rcs_open()
+ *
+ * Open a file containing RCS-formatted information. The file's path is
+ * given in <path>, and the opening mode is given in <mode>, which is either
+ * RCS_MODE_READ, RCS_MODE_WRITE, or RCS_MODE_RDWR. If the mode requests write
+ * access and the file does not exist, it will be created.
+ * The file isn't actually parsed by rcs_open(); parsing is delayed until the
+ * first operation that requires information from the file.
+ * Returns a handle to the opened file on success, or NULL on failure.
+ */
+
+RCSFILE*
+rcs_open(const char *path, u_int mode)
+{
+ RCSFILE *rfp;
+ struct stat st;
+
+ if ((stat(path, &st) == -1) && (errno == ENOENT) &&
+ !(mode & RCS_MODE_WRITE)) {
+ cvs_log(LP_ERRNO, "cannot open RCS file `%s'", path);
+ return (NULL);
+ }
+
+ rfp = (RCSFILE *)malloc(sizeof(*rfp));
+ if (rfp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS file structure");
+ return (NULL);
+ }
+ memset(rfp, 0, sizeof(*rfp));
+
+ rfp->rf_head = rcsnum_alloc();
+ if (rfp->rf_head == NULL) {
+ free(rfp);
+ return (NULL);
+ }
+
+ rfp->rf_path = strdup(path);
+ if (rfp->rf_path == NULL) {
+ cvs_log(LP_ERRNO, "failed to duplicate RCS file path");
+ rcs_close(rfp);
+ return (NULL);
+ }
+
+ rcsnum_aton(RCS_HEAD_INIT, NULL, rfp->rf_head);
+
+ rfp->rf_ref = 1;
+ rfp->rf_flags |= RCS_RF_SLOCK;
+ rfp->rf_mode = mode;
+
+ TAILQ_INIT(&(rfp->rf_delta));
+ TAILQ_INIT(&(rfp->rf_symbols));
+ TAILQ_INIT(&(rfp->rf_locks));
+
+ if (rcs_parse(rfp) < 0) {
+ rcs_close(rfp);
+ return (NULL);
+ }
+
+ return (rfp);
+}
+
+
+/*
+ * rcs_close()
+ *
+ * Close an RCS file handle.
+ */
+
+void
+rcs_close(RCSFILE *rfp)
+{
+ struct rcs_delta *rdp;
+
+ if (rfp->rf_ref > 1) {
+ rfp->rf_ref--;
+ return;
+ }
+
+ while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
+ rdp = TAILQ_FIRST(&(rfp->rf_delta));
+ TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
+ rcs_freedelta(rdp);
+ }
+
+ if (rfp->rf_head != NULL)
+ rcsnum_free(rfp->rf_head);
+
+ if (rfp->rf_path != NULL)
+ free(rfp->rf_path);
+ if (rfp->rf_comment != NULL)
+ free(rfp->rf_comment);
+ if (rfp->rf_expand != NULL)
+ free(rfp->rf_expand);
+ if (rfp->rf_desc != NULL)
+ free(rfp->rf_desc);
+ free(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[128], numbuf[64];
+ struct rcs_sym *symp;
+ struct rcs_lock *lkp;
+ struct rcs_delta *rdp;
+
+ if (rfp->rf_flags & RCS_RF_SYNCED)
+ return (0);
+
+ fp = fopen(rfp->rf_path, "w");
+ if (fp == NULL) {
+ cvs_log(LP_ERRNO, "failed to open RCS output file `%s'",
+ rfp->rf_path);
+ return (-1);
+ }
+
+ rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
+ fprintf(fp, "head\t%s;\n", numbuf);
+ fprintf(fp, "access;\n");
+
+ fprintf(fp, "symbols\n");
+ TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
+ rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
+ snprintf(buf, sizeof(buf), "%s:%s", symp->rs_name, numbuf);
+ fprintf(fp, "\t%s", buf);
+ if (symp != TAILQ_LAST(&(rfp->rf_symbols), rcs_slist))
+ fputc('\n', fp);
+ }
+ fprintf(fp, ";\n");
+
+ fprintf(fp, "locks;");
+
+ if (rfp->rf_flags & RCS_RF_SLOCK)
+ fprintf(fp, " strict;");
+ fputc('\n', fp);
+
+ if (rfp->rf_comment != NULL)
+ fprintf(fp, "comment\t@%s@;\n", rfp->rf_comment);
+
+ if (rfp->rf_expand != NULL)
+ fprintf(fp, "expand @ %s @;\n", rfp->rf_expand);
+
+ fprintf(fp, "\n\n");
+
+ 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, 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);
+ fprintf(fp, "branches;\n");
+ fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
+ numbuf, sizeof(numbuf)));
+ }
+
+ fprintf(fp, "\ndesc\n@%s@\n\n", rfp->rf_desc);
+
+ /* deltatexts */
+ TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
+ fprintf(fp, "\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
+ sizeof(numbuf)));
+ fprintf(fp, "log\n@%s@\n", rdp->rd_log);
+ fprintf(fp, "text\n@%s@\n\n", rdp->rd_text);
+ }
+ fclose(fp);
+
+ rfp->rf_flags |= RCS_RF_SYNCED;
+
+ return (0);
+}
+
+
+/*
+ * rcs_addsym()
+ *
+ * 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_addsym(RCSFILE *rfp, const char *sym, RCSNUM *snum)
+{
+ struct rcs_sym *symp;
+
+ /* first look for duplication */
+ TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
+ if (strcmp(symp->rs_name, sym) == 0) {
+ return (-1);
+ }
+ }
+
+ symp = (struct rcs_sym *)malloc(sizeof(*symp));
+ if (symp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS symbol");
+ return (-1);
+ }
+
+ symp->rs_name = strdup(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_RF_SYNCED;
+
+ return (0);
+}
+
+
+/*
+ * rcs_patch()
+ *
+ * Apply an RCS-format patch pointed to by <patch> to the file contents
+ * found in <data>.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+BUF*
+rcs_patch(const char *data, const char *patch)
+{
+ char op, *ep;
+ size_t len;
+ int i, lineno, nbln;
+ struct rcs_foo *dlines, *plines;
+ struct rcs_line *lp, *dlp, *ndlp;
+ BUF *res;
+ FILE *fp;
+
+ len = strlen(data);
+ res = cvs_buf_alloc(len, BUF_AUTOEXT);
+ if (res == NULL)
+ return (NULL);
+
+ dlines = rcs_splitlines(data);
+ if (dlines == NULL)
+ return (NULL);
+ plines = rcs_splitlines(patch);
+ if (plines == NULL)
+ return (NULL);
+
+ dlp = TAILQ_FIRST(&(dlines->rl_lines));
+ lp = TAILQ_FIRST(&(plines->rl_lines));
+
+ /* skip first bogus line */
+ for (lp = TAILQ_NEXT(lp, rl_list); lp != NULL;
+ lp = TAILQ_NEXT(lp, rl_list)) {
+ op = *(lp->rl_line);
+ lineno = (int)strtol((lp->rl_line + 1), &ep, 10);
+ if ((lineno > dlines->rl_nblines) || (lineno <= 0) ||
+ (*ep != ' ')) {
+ cvs_log(LP_ERR,
+ "invalid line specification in RCS patch");
+ return (NULL);
+ }
+ ep++;
+ nbln = (int)strtol(ep, &ep, 10);
+ if ((nbln <= 0) || (*ep != '\0')) {
+ cvs_log(LP_ERR,
+ "invalid line number specification in RCS patch");
+ return (NULL);
+ }
+
+ /* find the appropriate line */
+ for (;;) {
+ if (dlp == NULL)
+ break;
+ if (dlp->rl_lineno == lineno)
+ break;
+ if (dlp->rl_lineno > lineno) {
+ dlp = TAILQ_PREV(dlp, rcs_tqh, rl_list);
+ }
+ else if (dlp->rl_lineno < lineno) {
+ ndlp = TAILQ_NEXT(dlp, rl_list);
+ if (ndlp->rl_lineno > lineno)
+ break;
+ dlp = ndlp;
+ }
+ }
+ if (dlp == NULL) {
+ cvs_log(LP_ERR,
+ "can't find referenced line in RCS patch");
+ return (NULL);
+ }
+
+ if (op == 'd') {
+ for (i = 0; (i < nbln) && (dlp != NULL); i++) {
+ ndlp = TAILQ_NEXT(dlp, rl_list);
+ TAILQ_REMOVE(&(dlines->rl_lines), dlp, rl_list);
+ dlp = ndlp;
+ }
+ }
+ else if (op == 'a') {
+ for (i = 0; i < nbln; i++) {
+ ndlp = lp;
+ lp = TAILQ_NEXT(lp, rl_list);
+ if (lp == NULL) {
+ cvs_log(LP_ERR, "truncated RCS patch");
+ return (NULL);
+ }
+ TAILQ_REMOVE(&(plines->rl_lines), lp, rl_list);
+ TAILQ_INSERT_AFTER(&(dlines->rl_lines), dlp,
+ lp, rl_list);
+ dlp = lp;
+
+ /* we don't want lookup to block on those */
+ lp->rl_lineno = lineno;
+
+ lp = ndlp;
+ }
+ }
+ else {
+ cvs_log(LP_ERR, "unknown RCS patch operation `%c'", op);
+ return (NULL);
+ }
+
+ /* last line of the patch, done */
+ if (lp->rl_lineno == plines->rl_nblines)
+ break;
+ }
+
+ /* once we're done patching, rebuild the line numbers */
+ lineno = 1;
+ TAILQ_FOREACH(lp, &(dlines->rl_lines), rl_list) {
+ if (lineno == 1) {
+ lineno++;
+ continue;
+ }
+ cvs_buf_fappend(res, "%s\n", lp->rl_line);
+ lp->rl_lineno = lineno++;
+ }
+
+ dlines->rl_nblines = lineno - 1;
+
+ return (res);
+}
+
+
+/*
+ * rcs_getrev()
+ *
+ * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The
+ * returned buffer is dynamically allocated and should be released using free()
+ * once the caller is done using it.
+ */
+
+BUF*
+rcs_getrev(RCSFILE *rfp, RCSNUM *rev)
+{
+ int res;
+ size_t len;
+ void *bp;
+ RCSNUM *crev;
+ BUF *rbuf;
+ struct rcs_delta *rdp = NULL;
+
+ res = rcsnum_cmp(rfp->rf_head, rev, 0);
+ if (res == 1) {
+ cvs_log(LP_ERR, "sorry, can't travel in the future yet");
+ return (NULL);
+ }
+ else {
+ rdp = rcs_findrev(rfp, rfp->rf_head);
+ if (rdp == NULL) {
+ cvs_log(LP_ERR, "failed to get RCS HEAD revision");
+ return (NULL);
+ }
+
+ len = strlen(rdp->rd_text);
+ rbuf = cvs_buf_alloc(len, BUF_AUTOEXT);
+ if (rbuf == NULL)
+ return (NULL);
+ cvs_buf_append(rbuf, rdp->rd_text, len);
+
+ if (res != 0) {
+ /* Apply patches backwards to get the right version.
+ * This will need some rework to support sub branches.
+ */
+ crev = rcsnum_alloc();
+
+ rcsnum_cpy(rfp->rf_head, crev, 0);
+ do {
+ crev->rn_id[crev->rn_len - 1]--;
+ rdp = rcs_findrev(rfp, crev);
+ if (rdp == NULL)
+ return (NULL);
+
+ cvs_buf_putc(rbuf, '\0');
+ bp = cvs_buf_release(rbuf);
+ rbuf = rcs_patch((char *)bp, rdp->rd_text);
+ if (rbuf == NULL)
+ break;
+ } while (rcsnum_cmp(crev, rev, 0) != 0);
+
+ rcsnum_free(crev);
+ }
+ }
+
+
+ return (rbuf);
+}
+
+
+/*
+ * rcs_getrevbydate()
+ *
+ * Get an RCS revision by a specific date.
+ */
+
+RCSNUM*
+rcs_getrevbydate(RCSFILE *rfp, struct tm *date)
+{
+ return (NULL);
+}
+
+
+/*
+ * rcs_findrev()
+ *
+ * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
+ * The revision number is given in <rev>.
+ * Returns a pointer to the delta on success, or NULL on failure.
+ */
+
+static struct rcs_delta*
+rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
+{
+ u_int cmplen;
+ struct rcs_delta *rdp;
+ struct rcs_dlist *hp;
+
+ cmplen = 2;
+ hp = &(rfp->rf_delta);
+
+ TAILQ_FOREACH(rdp, hp, rd_list) {
+ if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0) {
+ if (cmplen == rev->rn_len)
+ return (rdp);
+
+ hp = &(rdp->rd_snodes);
+ cmplen += 2;
+ }
+ }
+
+ return (NULL);
+}
+
+
+/*
+ * rcs_parse()
+ *
+ * Parse the contents of file <path>, which are in the RCS format.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+rcs_parse(RCSFILE *rfp)
+{
+ int ret;
+ struct rcs_pdata *pdp;
+
+ if (rfp->rf_flags & RCS_RF_PARSED)
+ return (0);
+
+ pdp = (struct rcs_pdata *)malloc(sizeof(*pdp));
+ if (pdp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS parser data");
+ return (-1);
+ }
+ memset(pdp, 0, sizeof(*pdp));
+
+ pdp->rp_line = 1;
+ pdp->rp_pttype = RCS_TOK_ERR;
+
+ pdp->rp_file = fopen(rfp->rf_path, "r");
+ if (pdp->rp_file == NULL) {
+ cvs_log(LP_ERRNO, "failed to open RCS file `%s'", rfp->rf_path);
+ rcs_freepdata(pdp);
+ return (-1);
+ }
+
+ pdp->rp_buf = (char *)malloc(RCS_BUFSIZE);
+ if (pdp->rp_buf == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS parser buffer");
+ rcs_freepdata(pdp);
+ return (-1);
+ }
+ pdp->rp_blen = RCS_BUFSIZE;
+
+ /* ditch the strict lock */
+ rfp->rf_flags &= ~RCS_RF_SLOCK;
+ rfp->rf_pdata = pdp;
+
+ if (rcs_parse_admin(rfp) < 0) {
+ rcs_freepdata(pdp);
+ return (-1);
+ }
+
+ for (;;) {
+ ret = rcs_parse_delta(rfp);
+ if (ret == 0)
+ break;
+ else if (ret == -1) {
+ rcs_freepdata(pdp);
+ return (-1);
+ }
+ }
+
+ ret = rcs_gettok(rfp);
+ if (ret != RCS_TOK_DESC) {
+ cvs_log(LP_ERR, "token `%s' found where RCS desc expected",
+ RCS_TOKSTR(rfp));
+ rcs_freepdata(pdp);
+ return (-1);
+ }
+
+ ret = rcs_gettok(rfp);
+ if (ret != RCS_TOK_STRING) {
+ cvs_log(LP_ERR, "token `%s' found where RCS desc expected",
+ RCS_TOKSTR(rfp));
+ rcs_freepdata(pdp);
+ return (-1);
+ }
+
+ rfp->rf_desc = strdup(RCS_TOKSTR(rfp));
+
+ for (;;) {
+ ret = rcs_parse_deltatext(rfp);
+ if (ret == 0)
+ break;
+ else if (ret == -1) {
+ rcs_freepdata(pdp);
+ return (-1);
+ }
+ }
+
+ cvs_log(LP_DEBUG, "RCS file `%s' parsed OK (%u lines)", rfp->rf_path,
+ pdp->rp_line);
+
+ rcs_freepdata(pdp);
+
+ rfp->rf_pdata = NULL;
+ rfp->rf_flags |= RCS_RF_PARSED|RCS_RF_SYNCED;
+
+ return (0);
+}
+
+
+/*
+ * rcs_parse_admin()
+ *
+ * Parse the administrative portion of an RCS file.
+ * Returns 0 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) {
+ cvs_log(LP_ERR, "parse error in RCS admin section");
+ return (-1);
+ }
+ else if (tok == RCS_TOK_NUM) {
+ /* assume this is the start of the first delta */
+ rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
+ return (0);
+ }
+
+ rk = NULL;
+ for (i = 0; i < sizeof(rcs_keys)/sizeof(rcs_keys[0]); i++)
+ if (rcs_keys[i].rk_id == tok)
+ rk = &(rcs_keys[i]);
+
+ if (hmask & (1 << tok)) {
+ cvs_log(LP_ERR, "duplicate RCS key");
+ return (-1);
+ }
+ 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) {
+ cvs_log(LP_ERR,
+ "invalid value type for RCS key `%s'",
+ rk->rk_str);
+ }
+
+ if (tok == RCS_TOK_HEAD) {
+ rcsnum_aton(RCS_TOKSTR(rfp), NULL,
+ rfp->rf_head);
+ }
+ else if (tok == RCS_TOK_BRANCH) {
+ rcsnum_aton(RCS_TOKSTR(rfp), NULL,
+ rfp->rf_branch);
+ }
+ else if (tok == RCS_TOK_COMMENT) {
+ rfp->rf_comment = strdup(RCS_TOKSTR(rfp));
+ }
+ else if (tok == RCS_TOK_EXPAND) {
+ rfp->rf_expand = strdup(RCS_TOKSTR(rfp));
+ }
+
+ /* now get the expected semi-colon */
+ ntok = rcs_gettok(rfp);
+ if (ntok != RCS_TOK_SCOLON) {
+ cvs_log(LP_ERR,
+ "missing semi-colon after RCS `%s' key",
+ rk->rk_str);
+ return (-1);
+ }
+ break;
+ case RCS_TOK_ACCESS:
+ rcs_parse_access(rfp);
+ break;
+ case RCS_TOK_SYMBOLS:
+ rcs_parse_symbols(rfp);
+ break;
+ case RCS_TOK_LOCKS:
+ rcs_parse_locks(rfp);
+ break;
+ default:
+ cvs_log(LP_ERR,
+ "unexpected token `%s' in RCS admin section",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * 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 = (struct rcs_delta *)malloc(sizeof(*rdp));
+ if (rdp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS delta structure");
+ return (-1);
+ }
+ memset(rdp, 0, 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_NUM) {
+ cvs_log(LP_ERR, "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) {
+ cvs_log(LP_ERR, "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 < sizeof(rcs_keys)/sizeof(rcs_keys[0]); i++)
+ if (rcs_keys[i].rk_id == tok)
+ rk = &(rcs_keys[i]);
+
+ if (hmask & (1 << tok)) {
+ cvs_log(LP_ERR, "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 {
+ cvs_log(LP_ERR, "missing mandatory "
+ "value to RCS key `%s'",
+ rk->rk_str);
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+ }
+
+ if (ntok != rk->rk_val) {
+ cvs_log(LP_ERR,
+ "invalid value type for RCS key `%s'",
+ rk->rk_str);
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+
+ if (tokstr != NULL)
+ free(tokstr);
+ tokstr = strdup(RCS_TOKSTR(rfp));
+
+
+ /* now get the expected semi-colon */
+ ntok = rcs_gettok(rfp);
+ if (ntok != RCS_TOK_SCOLON) {
+ cvs_log(LP_ERR,
+ "missing semi-colon after RCS `%s' key",
+ rk->rk_str);
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+
+ if (tok == RCS_TOK_DATE) {
+ rcsnum_aton(tokstr, NULL, &datenum);
+ if (datenum.rn_len != 6) {
+ cvs_log(LP_ERR,
+ "RCS date specification has %s "
+ "fields",
+ (datenum.rn_len > 6) ? "too many" :
+ "missing");
+ rcs_freedelta(rdp);
+ }
+ rdp->rd_date.tm_year = datenum.rn_id[0];
+ 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];
+ }
+ 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:
+ rcs_parse_branches(rfp, rdp);
+ break;
+ default:
+ cvs_log(LP_ERR,
+ "unexpected token `%s' in RCS delta",
+ RCS_TOKSTR(rfp));
+ rcs_freedelta(rdp);
+ return (-1);
+ }
+ }
+
+ TAILQ_INSERT_TAIL(&(rfp->rf_delta), rdp, rd_list);
+
+ 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;
+
+ tnum = rcsnum_alloc();
+ if (tnum == NULL)
+ return (-1);
+
+ tok = rcs_gettok(rfp);
+ if (tok == RCS_TOK_EOF)
+ return (0);
+
+ if (tok != RCS_TOK_NUM) {
+ cvs_log(LP_ERR,
+ "unexpected token `%s' at start of RCS delta text",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+ 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;
+ }
+ if (rdp == NULL) {
+ cvs_log(LP_ERR, "RCS delta text `%s' has no matching delta",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ tok = rcs_gettok(rfp);
+ if (tok != RCS_TOK_LOG) {
+ cvs_log(LP_ERR, "unexpected token `%s' where RCS log expected",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ tok = rcs_gettok(rfp);
+ if (tok != RCS_TOK_STRING) {
+ cvs_log(LP_ERR, "unexpected token `%s' where RCS log expected",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+ rdp->rd_log = strdup(RCS_TOKSTR(rfp));
+ if (rdp->rd_log == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy RCS deltatext log");
+ return (-1);
+ }
+
+ tok = rcs_gettok(rfp);
+ if (tok != RCS_TOK_TEXT) {
+ cvs_log(LP_ERR, "unexpected token `%s' where RCS text expected",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ tok = rcs_gettok(rfp);
+ if (tok != RCS_TOK_STRING) {
+ cvs_log(LP_ERR, "unexpected token `%s' where RCS text expected",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ rdp->rd_text = strdup(RCS_TOKSTR(rfp));
+ if (rdp->rd_text == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy RCS delta text");
+ return (-1);
+ }
+
+ 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) {
+ cvs_log(LP_ERR, "unexpected token `%s' in access list",
+ RCS_TOKSTR(rfp));
+ 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_STRING) {
+ cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ symp = (struct rcs_sym *)malloc(sizeof(*symp));
+ if (symp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS symbol");
+ return (-1);
+ }
+ symp->rs_name = strdup(RCS_TOKSTR(rfp));
+ symp->rs_num = rcsnum_alloc();
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_COLON) {
+ cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ free(symp->rs_name);
+ free(symp);
+ return (-1);
+ }
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_NUM) {
+ cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ free(symp->rs_name);
+ free(symp);
+ return (-1);
+ }
+
+ if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, symp->rs_num) < 0) {
+ cvs_log(LP_ERR, "failed to parse RCS NUM `%s'",
+ RCS_TOKSTR(rfp));
+ free(symp->rs_name);
+ free(symp);
+ return (-1);
+ }
+
+ TAILQ_INSERT_HEAD(&(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) {
+ cvs_log(LP_ERR, "unexpected token `%s' in lock list",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ lkp = (struct rcs_lock *)malloc(sizeof(*lkp));
+ if (lkp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS lock");
+ return (-1);
+ }
+ lkp->rl_num = rcsnum_alloc();
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_COLON) {
+ cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ free(lkp);
+ return (-1);
+ }
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_NUM) {
+ cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
+ RCS_TOKSTR(rfp));
+ free(lkp);
+ return (-1);
+ }
+
+ if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, lkp->rl_num) < 0) {
+ cvs_log(LP_ERR, "failed to parse RCS NUM `%s'",
+ RCS_TOKSTR(rfp));
+ free(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_RF_SLOCK;
+
+ type = rcs_gettok(rfp);
+ if (type != RCS_TOK_SCOLON) {
+ cvs_log(LP_ERR,
+ "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) {
+ cvs_log(LP_ERR,
+ "unexpected token `%s' in list of branches",
+ RCS_TOKSTR(rfp));
+ return (-1);
+ }
+
+ brp = (struct rcs_branch *)malloc(sizeof(*brp));
+ if (brp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS branch");
+ return (-1);
+ }
+ brp->rb_num = rcsnum_alloc();
+ rcsnum_aton(RCS_TOKSTR(rfp), NULL, brp->rb_num);
+
+ TAILQ_INSERT_TAIL(&(rdp->rd_branches), brp, rb_list);
+ }
+
+ return (0);
+}
+
+
+/*
+ * rcs_freedelta()
+ *
+ * Free the contents of a delta structure.
+ */
+
+void
+rcs_freedelta(struct rcs_delta *rdp)
+{
+ struct rcs_delta *crdp;
+
+ if (rdp->rd_author != NULL)
+ free(rdp->rd_author);
+ if (rdp->rd_state != NULL)
+ free(rdp->rd_state);
+ if (rdp->rd_log != NULL)
+ free(rdp->rd_log);
+ if (rdp->rd_text != NULL)
+ free(rdp->rd_text);
+
+ while ((crdp = TAILQ_FIRST(&(rdp->rd_snodes))) != NULL) {
+ TAILQ_REMOVE(&(rdp->rd_snodes), crdp, rd_list);
+ rcs_freedelta(crdp);
+ }
+
+ free(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)
+ free(pd->rp_buf);
+ free(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;
+ char *bp, *bep;
+ struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
+
+ type = RCS_TOK_ERR;
+ bp = pdp->rp_buf;
+ bep = pdp->rp_buf + pdp->rp_blen - 1;
+ *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_line++;
+ } 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)) {
+ *(bp++) = ch;
+ while (bp <= bep - 1) {
+ ch = getc(pdp->rp_file);
+ if (!isalnum(ch)) {
+ ungetc(ch, pdp->rp_file);
+ break;
+ }
+ *(bp++) = ch;
+ }
+ *bp = '\0';
+
+ for (i = 0; i < sizeof(rcs_keys)/sizeof(rcs_keys[0]); i++) {
+ if (strcmp(rcs_keys[i].rk_str, pdp->rp_buf) == 0) {
+ type = rcs_keys[i].rk_id;
+ break;
+ }
+ }
+
+ /* not a keyword, assume it's just a string */
+ if (type == RCS_TOK_ERR)
+ type = RCS_TOK_STRING;
+ }
+ else if (ch == '@') {
+ /* we have a 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_line++;
+
+ *(bp++) = ch;
+ if (bp == bep)
+ break;
+ }
+
+ *bp = '\0';
+ type = RCS_TOK_STRING;
+ }
+ else if (isdigit(ch)) {
+ *(bp++) = ch;
+ last = ch;
+ type = RCS_TOK_NUM;
+
+ for (;;) {
+ ch = getc(pdp->rp_file);
+ if (bp == bep)
+ break;
+ if (!isdigit(ch) && ch != '.') {
+ ungetc(ch, pdp->rp_file);
+ break;
+ }
+
+ if (last == '.' && ch == '.') {
+ type = RCS_TOK_ERR;
+ break;
+ }
+ last = ch;
+ *(bp++) = ch;
+ }
+ *(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_stresc()
+ *
+ * Performs either escaping or unescaping of the string stored in <str>.
+ * The operation is to escape special RCS characters if the <esc> argument
+ * is 1, or unescape otherwise. The result is stored in the <buf> destination
+ * buffer, and <blen> must originally point to the size of <buf>.
+ * Returns the number of bytes which have been read from the source <str> and
+ * operated on. The <blen> parameter will contain the number of bytes
+ * actually copied in <buf>.
+ */
+
+size_t
+rcs_stresc(int esc, const char *str, char *buf, size_t *blen)
+{
+ size_t rlen;
+ const char *sp;
+ char *bp, *bep;
+
+ if (!esc)
+ printf("unescaping `%s'\n", str);
+
+ rlen = 0;
+ bp = buf;
+ bep = buf + *blen - 1;
+
+ for (sp = str; (*sp != '\0') && (bp <= (bep - 1)); sp++) {
+ if (*sp == '@') {
+ if (esc) {
+ if (bp > (bep - 2))
+ break;
+ *(bp++) = '@';
+ }
+ else {
+ sp++;
+ if (*sp != '@') {
+ cvs_log(LP_WARN,
+ "unknown escape character `%c' in "
+ "RCS file", *sp);
+ if (*sp == '\0')
+ break;
+ }
+ }
+ }
+
+ *(bp++) = *sp;
+ }
+
+ *bp = '\0';
+ *blen = (bp - buf);
+ return (sp - str);
+}
+
+
+/*
+ * rcs_splitlines()
+ *
+ * Split the contents of a file into a list of lines.
+ */
+
+static struct rcs_foo*
+rcs_splitlines(const char *fcont)
+{
+ char *dcp;
+ struct rcs_foo *foo;
+ struct rcs_line *lp;
+
+ foo = (struct rcs_foo *)malloc(sizeof(*foo));
+ if (foo == NULL) {
+ cvs_log(LP_ERR, "failed to allocate line structure");
+ return (NULL);
+ }
+ TAILQ_INIT(&(foo->rl_lines));
+ foo->rl_nblines = 0;
+ foo->rl_data = strdup(fcont);
+ if (foo->rl_data == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy file contents");
+ free(foo);
+ return (NULL);
+ }
+
+ /*
+ * Add a first bogus line with line number 0. This is used so we
+ * can position the line pointer before 1 when changing the first line
+ * in rcs_patch().
+ */
+ lp = (struct rcs_line *)malloc(sizeof(*lp));
+ if (lp == NULL) {
+ return (NULL);
+ }
+ lp->rl_line = NULL;
+ lp->rl_lineno = 0;
+ TAILQ_INSERT_TAIL(&(foo->rl_lines), lp, rl_list);
+
+
+ for (dcp = foo->rl_data; *dcp != '\0';) {
+ lp = (struct rcs_line *)malloc(sizeof(*lp));
+ if (lp == NULL) {
+ cvs_log(LP_ERR, "failed to allocate line entry");
+ return (NULL);
+ }
+
+ lp->rl_line = dcp;
+ lp->rl_lineno = ++(foo->rl_nblines);
+ TAILQ_INSERT_TAIL(&(foo->rl_lines), lp, rl_list);
+
+ dcp = strchr(dcp, '\n');
+ if (dcp == NULL) {
+ break;
+ }
+ *(dcp++) = '\0';
+ }
+
+ return (foo);
+}
diff --git a/usr.bin/cvs/server.c b/usr.bin/cvs/server.c
new file mode 100644
index 00000000000..baf8234eaed
--- /dev/null
+++ b/usr.bin/cvs/server.c
@@ -0,0 +1,95 @@
+/* $OpenBSD: server.c,v 1.1.1.1 2004/07/13 22:02:40 jfb 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 <sys/types.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <paths.h>
+#include <sysexits.h>
+
+#include "cvs.h"
+#include "log.h"
+#include "sock.h"
+
+
+extern struct cvsroot *cvs_root;
+
+
+
+/* argument vector built by the `Argument' and `Argumentx' requests */
+char **cvs_args;
+u_int cvs_nbarg = 0;
+
+u_int cvs_utf8ok = 0;
+u_int cvs_case = 0;
+
+
+
+/*
+ * cvs_server()
+ *
+ * Implement the `cvs server' command. As opposed to the general method of
+ * CVS client/server implementation, the cvs program merely acts as a
+ * redirector to the cvs daemon for most of the tasks.
+ *
+ * The `cvs server' command is only used on the server side of a remote
+ * cvs command. With this command, the cvs program starts listening on
+ * standard input for CVS protocol requests.
+ */
+
+int
+cvs_server(int argc, char **argv)
+{
+ ssize_t ret;
+ char reqbuf[128];
+
+ if (argc != 1) {
+ return (EX_USAGE);
+ }
+
+ for (;;) {
+ ret = read(STDIN_FILENO, reqbuf, sizeof(reqbuf));
+ if (ret == 0) {
+ break;
+ }
+
+
+ }
+
+
+ if (cvs_sock_connect(cvs_root->cr_dir) < 0) {
+ cvs_log(LP_ERR, "failed to connect to CVS server socket");
+ return (-1);
+ }
+
+ cvs_sock_disconnect();
+
+ return (0);
+}