summaryrefslogtreecommitdiff
path: root/usr.bin/cvs
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/cvs')
-rw-r--r--usr.bin/cvs/Makefile7
-rw-r--r--usr.bin/cvs/README4
-rw-r--r--usr.bin/cvs/aclparse.y486
-rw-r--r--usr.bin/cvs/add.c98
-rw-r--r--usr.bin/cvs/buf.c457
-rw-r--r--usr.bin/cvs/buf.h67
-rw-r--r--usr.bin/cvs/cache.c206
-rw-r--r--usr.bin/cvs/cache.h41
-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.1148
-rw-r--r--usr.bin/cvs/cvs.c497
-rw-r--r--usr.bin/cvs/cvs.h374
-rw-r--r--usr.bin/cvs/cvs/Makefile16
-rw-r--r--usr.bin/cvs/cvsacl.5107
-rw-r--r--usr.bin/cvs/cvsd.8116
-rw-r--r--usr.bin/cvs/cvsd.c649
-rw-r--r--usr.bin/cvs/cvsd.h106
-rw-r--r--usr.bin/cvs/cvsd/Makefile11
-rw-r--r--usr.bin/cvs/cvsrc.565
-rw-r--r--usr.bin/cvs/diff.c1678
-rw-r--r--usr.bin/cvs/entries.c263
-rw-r--r--usr.bin/cvs/event.h69
-rw-r--r--usr.bin/cvs/getlog.c148
-rw-r--r--usr.bin/cvs/hist.c300
-rw-r--r--usr.bin/cvs/history.c205
-rw-r--r--usr.bin/cvs/init.c127
-rw-r--r--usr.bin/cvs/lock.c65
-rw-r--r--usr.bin/cvs/log.c254
-rw-r--r--usr.bin/cvs/log.h71
-rw-r--r--usr.bin/cvs/msg.c121
-rw-r--r--usr.bin/cvs/proto.c821
-rw-r--r--usr.bin/cvs/rcs.c1557
-rw-r--r--usr.bin/cvs/rcs.h151
-rw-r--r--usr.bin/cvs/rcsnum.c231
-rw-r--r--usr.bin/cvs/root.c261
-rw-r--r--usr.bin/cvs/server.c95
-rw-r--r--usr.bin/cvs/sock.c237
-rw-r--r--usr.bin/cvs/sock.h65
-rw-r--r--usr.bin/cvs/update.c85
-rw-r--r--usr.bin/cvs/util.c386
-rw-r--r--usr.bin/cvs/version.c69
43 files changed, 11534 insertions, 0 deletions
diff --git a/usr.bin/cvs/Makefile b/usr.bin/cvs/Makefile
new file mode 100644
index 00000000000..3aeff968388
--- /dev/null
+++ b/usr.bin/cvs/Makefile
@@ -0,0 +1,7 @@
+# $OpenBSD: Makefile,v 1.1 2004/07/13 22:02:40 jfb Exp $
+
+.include <bsd.own.mk>
+
+SUBDIR= cvs cvsd
+
+.include <bsd.subdir.mk>
diff --git a/usr.bin/cvs/README b/usr.bin/cvs/README
new file mode 100644
index 00000000000..2b92661146b
--- /dev/null
+++ b/usr.bin/cvs/README
@@ -0,0 +1,4 @@
+README for cvs
+
+This is a BSD-licensed equivalent of the popular versioning system
+CVS. Not much more to say for now
diff --git a/usr.bin/cvs/aclparse.y b/usr.bin/cvs/aclparse.y
new file mode 100644
index 00000000000..8fe67693731
--- /dev/null
+++ b/usr.bin/cvs/aclparse.y
@@ -0,0 +1,486 @@
+/* $OpenBSD: aclparse.y,v 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/queue.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "cvsd.h"
+#include "cvs.h"
+#include "log.h"
+#include "event.h"
+
+
+#define CVS_ACL_DENY 0
+#define CVS_ACL_ALLOW 1
+
+#define CVS_ACL_LOGOPT 0x01
+#define CVS_ACL_QUICKOPT 0x02
+
+
+struct acl_user {
+ uid_t au_uid;
+ SLIST_ENTRY(acl_user) au_list;
+};
+
+
+struct acl_rule {
+ u_int8_t ar_act;
+ u_int8_t ar_opts;
+ u_int8_t ar_op;
+ char *ar_path;
+ char *ar_tag;
+
+ SLIST_HEAD(, acl_user) ar_users;
+};
+
+
+
+
+typedef struct {
+ union {
+ u_int32_t num;
+ char *string;
+ struct acl_user *user_list;
+ } v;
+
+ int lineno;
+} YYSTYPE;
+
+
+
+
+int lgetc (FILE *);
+
+
+int yyerror (const char *, ...);
+int yylex (void);
+int lookup (const char *);
+int kw_cmp (const void *, const void *);
+
+u_int cvs_acl_matchuid (struct acl_rule *, uid_t);
+u_int cvs_acl_matchtag (const char *, const char *);
+u_int cvs_acl_matchpath (const char *, const char *);
+
+
+static char *acl_file;
+static FILE *acl_fin;
+static int acl_lineno = 1;
+static int acl_errors = 0;
+static struct acl_rule *acl_rules;
+static u_int acl_nrules = 0;
+static u_int acl_defact = CVS_ACL_DENY;
+
+
+static u_int acl_opts = 0;
+
+
+/*
+ * cvs_acl_eval()
+ *
+ * Evaluate a thingamajimmie against the currently loaded ACL ruleset.
+ * Returns CVS_ACL_ALLOW if the operation is permitted, CVS_ACL_DENY otherwise.
+ */
+
+u_int
+cvs_acl_eval(struct cvs_op *op)
+{
+ u_int i, res;
+ struct acl_rule *arp;
+
+ /* deny by default */
+ res = acl_defact;
+
+ for (i = 0; i < acl_nrules; i++) {
+ arp = &(acl_rules[i]);
+
+ if ((op->co_op != arp->ar_op) ||
+ !cvs_acl_matchuid(arp, op->co_uid) ||
+ !cvs_acl_matchtag(op->co_tag, arp->ar_tag) ||
+ !cvs_acl_matchpath(op->co_path, arp->ar_path))
+ continue;
+
+ res = arp->ar_act;
+
+ if (arp->ar_opts & CVS_ACL_LOGOPT)
+ cvs_log(LP_WARN, "act=%u, path=%s, tag=%s, uid=%u",
+ op->co_op, op->co_path, op->co_tag, op->co_uid);
+ if (arp->ar_opts & CVS_ACL_QUICKOPT)
+ break;
+ }
+
+ return (res);
+}
+
+
+/*
+ * cvs_acl_matchuid()
+ *
+ * Check if an ACL rule has a UID matching <uid>. If no user is specified
+ * for a given rule, any UID will match.
+ * Returns 1 if this is the case, 0 otherwise.
+ */
+
+u_int
+cvs_acl_matchuid(struct acl_rule *rule, uid_t uid)
+{
+ struct acl_user *aup;
+
+ if (SLIST_EMPTY(&(rule->ar_users)))
+ return (1);
+
+ SLIST_FOREACH(aup, &(rule->ar_users), au_list)
+ if (aup->au_uid == uid)
+ return (1);
+ return (0);
+}
+
+
+/*
+ * cvs_acl_matchtag()
+ *
+ * Returns 1 if this is the case, 0 otherwise.
+ */
+
+u_int
+cvs_acl_matchtag(const char *tag1, const char *tag2)
+{
+ if ((tag1 == NULL) && (tag2 == NULL)) /* HEAD */
+ return (1);
+
+ if ((tag1 != NULL) && (tag2 != NULL) &&
+ (strcmp(tag1, tag2) == 0))
+ return (1);
+
+ return (0);
+}
+
+
+/*
+ * cvs_acl_matchpath()
+ *
+ * Check if the path <op_path> is a subpath of <acl_path>.
+ * Returns 1 if this is the case, 0 otherwise.
+ */
+
+u_int
+cvs_acl_matchpath(const char *op_path, const char *acl_path)
+{
+ size_t len;
+ char rop_path[MAXPATHLEN];
+
+ /* if the ACL path is NULL, apply on all paths */
+ if (acl_path == NULL)
+ return (1);
+
+ if (realpath(op_path, rop_path) == NULL) {
+ cvs_log(LP_ERRNO, "failed to convert `%s' to a real path",
+ op_path);
+ return (0);
+ }
+
+ printf("comparing `%s' to `%s'\n", rop_path, acl_path);
+ len = strlen(rop_path);
+
+ if (strncmp(rop_path, acl_path, len) == 0)
+ return (1);
+
+ return (0);
+}
+%}
+
+%token ALLOW DENY LOG QUICK ON TAG FROM
+%token ADD CHECKOUT COMMIT DIFF HISTORY UPDATE
+%token <v.string> STRING
+%type <v.num> action options operation
+%type <v.userlist>
+%type <v.string> pathspec tagspec
+%%
+
+ruleset : /* empty */
+ | ruleset '\n'
+ | ruleset rule '\n'
+ | ruleset error '\n' { acl_errors++; }
+ ;
+
+rule : action options operation pathspec tagspec userspec
+ {
+ void *tmp;
+ struct acl_rule *arp;
+
+ tmp = realloc(acl_rules,
+ (acl_nrules + 1) * sizeof(struct acl_rule));
+ if (tmp == NULL) {
+ cvs_log(LP_ERRNO, "failed to grow ACL ruleset");
+ free($4);
+ free($5);
+ YYERROR;
+ }
+ acl_rules = (struct acl_rule *)tmp;
+ arp = &(acl_rules[acl_nrules++]);
+
+ arp->ar_act = $1;
+ arp->ar_opts = $2;
+ arp->ar_op = $3;
+ SLIST_INIT(&arp->ar_users);
+ arp->ar_path = $4;
+ arp->ar_tag = $5;
+
+ }
+ ;
+
+action : ALLOW { $$ = CVS_ACL_ALLOW; }
+ | DENY { $$ = CVS_ACL_DENY; }
+ ;
+
+options : /* empty */ { $$ = 0; }
+ | LOG { $$ = CVS_ACL_LOGOPT; }
+ | QUICK { $$ = CVS_ACL_QUICKOPT; }
+ | LOG QUICK { $$ = CVS_ACL_LOGOPT | CVS_ACL_QUICKOPT; }
+ ;
+
+operation : ADD { $$ = CVS_OP_ADD; }
+ | COMMIT { $$ = CVS_OP_COMMIT; }
+ | TAG { $$ = CVS_OP_TAG; }
+ ;
+
+pathspec : /* empty */ { $$ = NULL; }
+ | ON STRING { $$ = $2; }
+ ;
+
+tagspec : /* empty */ { $$ = NULL; }
+ | TAG STRING { $$ = $2; }
+ ;
+
+userspec : /* empty */
+ | FROM userlist
+ ;
+
+userlist : user
+ | userlist ',' user
+ ;
+
+user : STRING
+ {
+ uid_t uid;
+ char *ep;
+ struct passwd *pw;
+ struct acl_user *aup;
+
+ uid = (uid_t)strtol($1, &ep, 10);
+ if (*ep != '\0')
+ pw = getpwnam($1);
+ else
+ pw = getpwuid(uid);
+ if (pw == NULL) {
+ yyerror("invalid username or ID `%s'", $1);
+ YYERROR;
+ }
+
+ aup = (struct acl_user *)malloc(sizeof(*aup));
+ if (aup == NULL) {
+ yyerror("failed to allocate ACL user data");
+ YYERROR;
+ }
+ aup->au_uid = pw->pw_uid;
+ }
+ ;
+%%
+
+
+struct acl_kw {
+ char *kw_str;
+ u_int kw_id;
+};
+
+
+
+static const struct acl_kw keywords[] = {
+ { "add", ADD },
+ { "allow", ALLOW },
+ { "commit", COMMIT },
+ { "deny", DENY },
+ { "from", FROM },
+ { "log", LOG },
+ { "on", ON },
+ { "quick", QUICK },
+ { "tag", TAG },
+};
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct acl_kw *)e)->kw_str));
+}
+
+
+int
+lookup(const char *tstr)
+{
+ int type;
+ const struct acl_kw *kwp;
+
+ kwp = bsearch(tstr, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+ if (kwp != NULL)
+ type = kwp->kw_id;
+ else
+ type = STRING;
+ return (type);
+}
+
+
+
+int
+lgetc(FILE *f)
+{
+ int c;
+
+ c = getc(f);
+ if ((c == '\t') || (c == ' ')) {
+ do {
+ c = getc(f);
+ } while ((c == ' ') || (c == '\t'));
+ ungetc(c, f);
+ c = ' ';
+ }
+ else if (c == '\\')
+ c = getc(f);
+
+ return (c);
+}
+
+
+int
+yylex(void)
+{
+ int tok, c;
+ char buf[1024], *bp, *ep;
+
+ bp = buf;
+ ep = buf + sizeof(buf) - 1;
+
+ yylval.lineno = acl_lineno;
+
+ /* skip whitespace */
+ while ((c = lgetc(acl_fin)) == ' ')
+ ;
+
+ if (c == '#') {
+ do {
+ c = lgetc(acl_fin);
+ } while ((c != '\n') && (c != EOF));
+ }
+ else if (c == EOF)
+ c = 0;
+ else if (c == '\n')
+ acl_lineno++;
+ else if (c != ',') {
+ do {
+ *bp++ = c;
+ if (bp == ep) {
+ yyerror("string too long");
+ return (-1);
+ }
+
+ c = lgetc(acl_fin);
+ if (c == EOF)
+ break;
+ } while ((c != EOF) && (c != ' ') && (c != '\n'));
+ ungetc(c, acl_fin);
+ *bp = '\0';
+ c = lookup(buf);
+ if (c == STRING) {
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL) {
+ cvs_log(LP_ERRNO,
+ "failed to copy token string");
+ return (-1);
+ }
+ }
+ }
+
+ return (c);
+}
+
+
+
+int
+yyerror(const char *fmt, ...)
+{
+ char *nfmt;
+ va_list vap;
+
+ va_start(vap, fmt);
+
+ if (asprintf(&nfmt, "%s:%d: %s", acl_file, yylval.lineno, fmt) == -1) {
+ cvs_log(LP_ERRNO, "failed to allocate message buffer");
+ return (-1);
+ }
+ cvs_vlog(LP_ERR, nfmt, vap);
+
+ free(nfmt);
+ va_end(vap);
+ return (0);
+
+}
+
+
+/*
+ * cvs_acl_parse()
+ *
+ * Parse the contents of the ACL file <file>.
+ */
+
+int
+cvs_acl_parse(const char *file)
+{
+ acl_file = strdup(file);
+ if (acl_file == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy ACL file path");
+ return (-1);
+ }
+
+ acl_fin = fopen(file, "r");
+ if (acl_fin == NULL) {
+ cvs_log(LP_ERRNO, "failed to open ACL file `%s'", file);
+ return (-1);
+ }
+
+ if (yyparse() != 0)
+ acl_lineno = -1;
+
+ (void)fclose(acl_fin);
+
+ return (acl_lineno);
+}
diff --git a/usr.bin/cvs/add.c b/usr.bin/cvs/add.c
new file mode 100644
index 00000000000..fde7a4f205a
--- /dev/null
+++ b/usr.bin/cvs/add.c
@@ -0,0 +1,98 @@
+/* $OpenBSD: add.c,v 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_add()
+ *
+ * Handler for the `cvs add' command.
+ * Returns 0 on success, or one of the known system exit codes on failure.
+ */
+
+int
+cvs_add(int argc, char **argv)
+{
+ int ch, i, ret;
+ char *kflag, *msg;
+
+ kflag = NULL;
+
+ while ((ch = getopt(argc, argv, "k:m:")) != -1) {
+ switch (ch) {
+ case 'k':
+ kflag = optarg;
+ break;
+ case 'm':
+ msg = optarg;
+ break;
+ default:
+ return (EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ return (EX_USAGE);
+ }
+
+ for (i = 0; i < argc; i++) {
+ /*
+ * XXX figure out if we should send `Modified' or `Is-Modified'
+ * The cvs documentation specifies that Modified should be used
+ * in this case, but GNU CVS sends the latter.
+ */
+ ret = cvs_client_sendreq(CVS_REQ_ISMODIFIED, argv[i], 0);
+ if (ret < 0)
+ return (EX_DATAERR);
+ }
+
+ for (i = 0; i < argc; i++) {
+ ret = cvs_client_sendreq(CVS_REQ_ARGUMENT, argv[i], 0);
+ }
+
+ ret = cvs_client_sendreq(CVS_REQ_ADD, NULL, 0);
+
+ return (0);
+}
diff --git a/usr.bin/cvs/buf.c b/usr.bin/cvs/buf.c
new file mode 100644
index 00000000000..9aa65b9cd57
--- /dev/null
+++ b/usr.bin/cvs/buf.c
@@ -0,0 +1,457 @@
+/* $OpenBSD: buf.c,v 1.1 2004/07/13 22:02:40 jfb Exp $ */
+/*
+ * Copyright (c) 2003 Jean-Francois Brousseau <jfb@fugusec.net>
+ * 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/stat.h>
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "buf.h"
+#include "log.h"
+
+
+#define BUF_INCR 128
+
+
+struct cvs_buf {
+ u_int cb_flags;
+
+ /* buffer handle and size */
+ void *cb_buf;
+ size_t cb_size;
+
+ /* start and length of valid data in buffer */
+ u_char *cb_cur;
+ size_t cb_len;
+};
+
+
+
+#define SIZE_LEFT(b) ((size_t)((u_char *)b->cb_buf - b->cb_cur) + b->cb_size)
+
+
+static ssize_t cvs_buf_grow (BUF *, size_t);
+
+
+
+/*
+ * cvs_buf_alloc()
+ *
+ * Create a new buffer structure and return a pointer to it. This structure
+ * uses dynamically-allocated memory and must be freed with cvs_buf_free(),
+ * once the buffer is no longer needed.
+ */
+
+BUF*
+cvs_buf_alloc(size_t len, u_int flags)
+{
+ BUF *b;
+
+ b = (BUF *)malloc(sizeof(*b));
+ if (b == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate buffer");
+ return (NULL);
+ }
+
+ b->cb_buf = malloc(len);
+ if (b->cb_buf == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate buffer");
+ free(b);
+ return (NULL);
+ }
+ memset(b->cb_buf, 0, sizeof(b->cb_buf));
+
+ b->cb_flags = flags;
+ b->cb_size = len;
+ b->cb_cur = (u_char *)b->cb_buf;
+ b->cb_len = 0;
+
+ return (b);
+}
+
+
+/*
+ * cvs_buf_load()
+ *
+ * Open the file specified by <path> and load all of its contents into a
+ * buffer.
+ * Returns the loaded buffer on success, or NULL on failure.
+ */
+
+BUF*
+cvs_buf_load(const char *path, u_int flags)
+{
+ int fd;
+ ssize_t ret;
+ size_t len;
+ void *bp;
+ struct stat st;
+ BUF *buf;
+
+ fd = open(path, O_RDONLY, 0600);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to open buffer source");
+ return (NULL);
+ }
+
+ if (fstat(fd, &st) == -1) {
+ cvs_log(LP_ERRNO, "failed to stat buffer source");
+ (void)close(fd);
+ return (NULL);
+ }
+
+ buf = cvs_buf_alloc((size_t)st.st_size, flags);
+ if (buf == NULL) {
+ (void)close(fd);
+ return (NULL);
+ }
+
+ for (bp = buf->cb_cur; ; bp += (size_t)ret) {
+ len = MIN(SIZE_LEFT(buf), 4096);
+ ret = read(fd, bp, len);
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "read failed from buffer source");
+ (void)close(fd);
+ cvs_buf_free(bp);
+ return (NULL);
+ }
+ else if (ret == 0)
+ break;
+
+ buf->cb_len += (size_t)ret;
+ }
+
+ (void)close(fd);
+
+ return (buf);
+}
+
+
+/*
+ * cvs_buf_free()
+ *
+ * Free the buffer <b> and all associated data.
+ */
+
+void
+cvs_buf_free(BUF *b)
+{
+ free(b->cb_buf);
+ free(b);
+}
+
+
+/*
+ * cvs_buf_release()
+ *
+ * Free the buffer <b>'s structural information but do not free the contents
+ * of the buffer. Instead, they are returned and should be freed later using
+ * free().
+ */
+
+void*
+cvs_buf_release(BUF *b)
+{
+ void *tmp;
+ tmp = b->cb_buf;
+ free(b);
+ return (tmp);
+}
+
+
+/*
+ * cvs_buf_empty()
+ *
+ * Empty the contents of the buffer <b> and reset pointers.
+ */
+
+void
+cvs_buf_empty(BUF *b)
+{
+ b->cb_cur = (u_char *)b->cb_buf;
+ b->cb_len = 0;
+}
+
+
+/*
+ * cvs_buf_copy()
+ *
+ * Copy the first <len> bytes of data in the buffer <b> starting at offset
+ * <off> in the destination buffer <dst>, which can accept up to <len> bytes.
+ * Returns the number of bytes successfully copied, or -1 on failure.
+ */
+
+ssize_t
+cvs_buf_copy(BUF *b, size_t off, void *dst, size_t len)
+{
+ size_t rc;
+
+ if (off > b->cb_len)
+ return (-1);
+
+ rc = MIN(len, (b->cb_len - off));
+ memcpy(dst, b->cb_buf, rc);
+
+ return (ssize_t)rc;
+}
+
+
+/*
+ * cvs_buf_set()
+ *
+ * Set the contents of the buffer <b> to the first <len> bytes of data found
+ * at <src>. If the buffer was not created with BUF_AUTOEXT, as many bytes
+ * as possible will be copied in the buffer.
+ */
+
+int
+cvs_buf_set(BUF *b, const void *src, size_t len, size_t off)
+{
+ size_t rlen;
+
+ if (b->cb_size < (len + off)) {
+ if ((b->cb_flags & BUF_AUTOEXT) && (cvs_buf_grow(b,
+ len + off - b->cb_size) < 0))
+ return (-1);
+ else
+ rlen = b->cb_size - off;
+
+ }
+ else
+ rlen = len;
+
+ memcpy((b->cb_buf + off), src, rlen);
+
+ if (b->cb_len == 0) {
+ b->cb_cur = b->cb_buf + off;
+ b->cb_len = rlen;
+ }
+
+ return (int)rlen;
+}
+
+
+/*
+ * cvs_buf_putc()
+ *
+ * Append a single character <c> to the end of the buffer <b>.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_buf_putc(BUF *b, int c)
+{
+ u_char *bp;
+
+ bp = b->cb_cur + b->cb_len;
+ if (bp == (b->cb_buf + b->cb_size)) {
+ /* extend */
+ if (!(b->cb_flags & BUF_AUTOEXT) ||
+ (cvs_buf_grow(b, BUF_INCR) < 0))
+ return (-1);
+
+ /* the buffer might have been moved */
+ bp = b->cb_cur + b->cb_len;
+ }
+ *bp = (u_char)c;
+ b->cb_len++;
+
+ return (0);
+}
+
+
+/*
+ * cvs_buf_append()
+ *
+ * Append <len> bytes of data pointed to by <data> to the buffer <b>. If the
+ * buffer is too small to accept all data, it will attempt to append as much
+ * data as possible, or if the BUF_AUTOEXT flag is set for the buffer, it
+ * will get resized to an appropriate size to accept all data.
+ * Returns the number of bytes successfully appended to the buffer, or -1
+ * on failure.
+ */
+
+ssize_t
+cvs_buf_append(BUF *b, const void *data, size_t len)
+{
+ size_t left, rlen;
+ void *bp, *bep;
+
+ bp = b->cb_cur + b->cb_len;
+ bep = b->cb_buf + b->cb_size;
+ left = bep - bp;
+ rlen = len;
+
+ if (left < len) {
+ if (b->cb_flags & BUF_AUTOEXT) {
+ if (cvs_buf_grow(b, len - left) < 0)
+ return (-1);
+ bp = b->cb_cur + b->cb_len;
+ }
+ else
+ rlen = bep - bp;
+ }
+
+ memcpy(bp, data, rlen);
+ b->cb_len += rlen;
+
+ return (rlen);
+}
+
+
+/*
+ * cvs_buf_fappend()
+ *
+ */
+
+int
+cvs_buf_fappend(BUF *b, const char *fmt, ...)
+{
+ int ret;
+ char *str;
+ va_list vap;
+
+ va_start(vap, fmt);
+
+ ret = vasprintf(&str, fmt, vap);
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "failed to format data");
+ return (-1);
+ }
+ va_end(vap);
+
+ ret = cvs_buf_append(b, str, ret);
+ free(str);
+ return (ret);
+}
+
+
+/*
+ * cvs_buf_size()
+ *
+ * Returns the size of the buffer that is being used.
+ */
+
+size_t
+cvs_buf_size(BUF *b)
+{
+ return (b->cb_len);
+}
+
+
+/*
+ * cvs_buf_peek()
+ *
+ * Peek at the contents of the buffer <b> at offset <off>.
+ */
+
+const void*
+cvs_buf_peek(BUF *b, size_t off)
+{
+ if (off >= b->cb_len)
+ return (NULL);
+
+ return (b->cb_buf + off);
+}
+
+
+/*
+ * cvs_buf_write()
+ *
+ * Write the contents of the buffer <b> to the file whose path is given in
+ * <path>. If the file does not exist, it is created with mode <mode>.
+ */
+
+int
+cvs_buf_write(BUF *b, const char *path, mode_t mode)
+{
+ int fd;
+ u_char *bp;
+ size_t len;
+ ssize_t ret;
+
+ fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to open file `%s'", path);
+ return (-1);
+ }
+
+ len = b->cb_len;
+ bp = b->cb_cur;
+
+ do {
+ ret = write(fd, bp, MIN(len, 8192));
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "failed to write to file `%s'", path);
+ (void)close(fd);
+ (void)unlink(path);
+ return (-1);
+ }
+
+ len -= (size_t)ret;
+ bp += (size_t)ret;
+ } while (len > 0);
+
+ (void)close(fd);
+
+ return (0);
+}
+
+
+/*
+ * cvs_buf_grow()
+ *
+ * Grow the buffer <b> by <len> bytes. The contents are unchanged by this
+ * operation regardless of the result.
+ * Returns the new size on success, or -1 on failure.
+ */
+
+static ssize_t
+cvs_buf_grow(BUF *b, size_t len)
+{
+ void *tmp;
+ size_t diff;
+
+ diff = b->cb_cur - (u_char *)b->cb_buf;
+ tmp = realloc(b->cb_buf, b->cb_size + len);
+ if (tmp == NULL) {
+ cvs_log(LP_ERRNO, "failed to grow buffer");
+ return (-1);
+ }
+ b->cb_buf = (u_char *)tmp;
+ b->cb_size += len;
+
+ /* readjust pointers in case the buffer moved in memory */
+ b->cb_cur = b->cb_buf + diff;
+
+ return (ssize_t)b->cb_size;
+}
diff --git a/usr.bin/cvs/buf.h b/usr.bin/cvs/buf.h
new file mode 100644
index 00000000000..fdd569664a1
--- /dev/null
+++ b/usr.bin/cvs/buf.h
@@ -0,0 +1,67 @@
+/* $OpenBSD: buf.h,v 1.1 2004/07/13 22:02:40 jfb Exp $ */
+/*
+ * Copyright (c) 2003 Jean-Francois Brousseau <jfb@fugusec.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Buffer management
+ * -----------------
+ *
+ * This code provides an API to generic memory buffer management. All
+ * operations are performed on a cvs_buf structure, which is kept opaque to the
+ * API user in order to avoid corruption of the fields and make sure that only
+ * the internals can modify the fields.
+ *
+ * The first step is to allocate a new buffer using the cvs_buf_create()
+ * function, which returns a pointer to a new buffer.
+ */
+
+#ifndef BUF_H
+#define BUF_H
+
+#include <sys/types.h>
+
+
+/* flags */
+#define BUF_AUTOEXT 1 /* autoextend on append */
+
+
+typedef struct cvs_buf BUF;
+
+
+BUF* cvs_buf_alloc (size_t, u_int);
+BUF* cvs_buf_load (const char *, u_int);
+void cvs_buf_free (BUF *);
+void* cvs_buf_release (BUF *);
+void cvs_buf_empty (BUF *);
+ssize_t cvs_buf_copy (BUF *, size_t, void *, size_t);
+int cvs_buf_set (BUF *, const void *, size_t, size_t);
+int cvs_buf_append (BUF *, const void *, size_t);
+int cvs_buf_fappend (BUF *, const char *, ...);
+int cvs_buf_putc (BUF *, int);
+size_t cvs_buf_size (BUF *);
+const void* cvs_buf_peek (BUF *, size_t);
+int cvs_buf_write (BUF *, const char *, mode_t);
+
+#define cvs_buf_get(b) cvs_buf_peek(b, 0)
+
+#endif /* BUF_H */
diff --git a/usr.bin/cvs/cache.c b/usr.bin/cvs/cache.c
new file mode 100644
index 00000000000..cdd0922c21e
--- /dev/null
+++ b/usr.bin/cvs/cache.c
@@ -0,0 +1,206 @@
+/* $OpenBSD: cache.c,v 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/time.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "log.h"
+#include "rcs.h"
+#include "cache.h"
+
+#define RCS_CACHE_BUCKETS 256
+
+
+struct rcs_cachent {
+ u_int rc_hits;
+ u_int8_t rc_hash;
+ RCSFILE *rc_rfp;
+ struct timeval rc_atime;
+
+ TAILQ_ENTRY(rcs_cachent) rc_list;
+ TAILQ_ENTRY(rcs_cachent) rc_lru;
+};
+
+
+static u_int8_t rcs_cache_hash (const char *);
+
+
+TAILQ_HEAD(rcs_cachebkt, rcs_cachent) rcs_cache[RCS_CACHE_BUCKETS];
+
+TAILQ_HEAD(rcs_lruhead, rcs_cachent) rcs_cache_lru;
+
+u_int rcs_cache_maxent;
+u_int rcs_cache_nbent;
+
+
+
+/*
+ * rcs_cache_init()
+ *
+ * Initialize the RCS file data cache.
+ * Returns 0 on success, -1 on failure.
+ */
+
+int
+rcs_cache_init(u_int maxent)
+{
+ u_int i;
+
+ for (i = 0; i < RCS_CACHE_BUCKETS; i++)
+ TAILQ_INIT(&rcs_cache[i]);
+
+ TAILQ_INIT(&rcs_cache_lru);
+
+ rcs_cache_maxent = maxent;
+ rcs_cache_nbent = 0;
+
+ return (0);
+}
+
+
+/*
+ * rcs_cache_destroy()
+ */
+
+void
+rcs_cache_destroy(void)
+{
+}
+
+
+/*
+ * rcs_cache_fetch()
+ *
+ */
+
+RCSFILE*
+rcs_cache_fetch(const char *path)
+{
+ u_int8_t hash;
+ struct rcs_cachent *rcp;
+ RCSFILE *rfp;
+
+ rfp = NULL;
+ hash = rcs_cache_hash(path);
+
+ TAILQ_FOREACH(rcp, &(rcs_cache[hash]), rc_list) {
+ if (strcmp(path, rcp->rc_rfp->rf_path) == 0) {
+ rfp = rcp->rc_rfp;
+ break;
+ }
+ }
+
+ if (rcp != NULL) {
+ (void)gettimeofday(&(rcp->rc_atime), NULL);
+ rcp->rc_hits++;
+
+ /* move this item back at the end of the LRU */
+ TAILQ_REMOVE(&rcs_cache_lru, rcp, rc_lru);
+ TAILQ_INSERT_TAIL(&rcs_cache_lru, rcp, rc_lru);
+
+ /* increment reference count for caller */
+ rfp->rf_ref++;
+ }
+
+ return (rfp);
+}
+
+
+/*
+ * rcs_cache_store()
+ *
+ * Store the RCSFILE <rfp> in the RCS file cache. By storing the file, its
+ * reference count gets incremented for the cache reference, so the caller
+ * should still rcs_close() the file once they are done with it.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+rcs_cache_store(RCSFILE *rfp)
+{
+ struct rcs_cachent *rcp, *old_rcp;
+
+ /* don't store the same element twice */
+ if (rcs_cache_fetch(rfp->rf_path) != NULL)
+ return (0);
+
+ rcp = (struct rcs_cachent *)malloc(sizeof(*rcp));
+ if (rcp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate RCS cache entry");
+ return (-1);
+ }
+
+ rcp->rc_hits = 0;
+
+ rcp->rc_rfp = rfp;
+ rfp->rf_ref++;
+
+ rcp->rc_hash = rcs_cache_hash(rfp->rf_path);
+
+ rcs_cache_nbent++;
+ if (rcs_cache_nbent == rcs_cache_maxent) {
+ /* ditch the oldest entry in the LRU */
+ old_rcp = TAILQ_FIRST(&rcs_cache_lru);
+ TAILQ_REMOVE(&rcs_cache_lru, old_rcp, rc_lru);
+ TAILQ_REMOVE(&(rcs_cache[old_rcp->rc_hash]), old_rcp, rc_list);
+
+ /* free our reference */
+ rcs_close(old_rcp->rc_rfp);
+ free(old_rcp);
+ }
+
+ TAILQ_INSERT_TAIL(&(rcs_cache[rcp->rc_hash]), rcp, rc_list);
+ TAILQ_INSERT_TAIL(&rcs_cache_lru, rcp, rc_lru);
+
+ return (0);
+}
+
+
+/*
+ * rcs_cache_hash()
+ *
+ * Hash the <path> string.
+ */
+
+static u_int8_t
+rcs_cache_hash(const char *path)
+{
+ const char *sp;
+ u_int8_t hash;
+
+ hash = 0;
+ for (sp = path; *sp != '\0'; sp++)
+ hash ^= (*sp << 3) ^ (*sp >> 2);
+
+ return (hash);
+}
diff --git a/usr.bin/cvs/cache.h b/usr.bin/cvs/cache.h
new file mode 100644
index 00000000000..33b2a485f5b
--- /dev/null
+++ b/usr.bin/cvs/cache.h
@@ -0,0 +1,41 @@
+/* $OpenBSD: cache.h,v 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 CACHE_H
+#define CACHE_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+
+
+int rcs_cache_init (u_int);
+RCSFILE *rcs_cache_fetch (const char *path);
+int rcs_cache_store (RCSFILE *);
+void rcs_cache_destroy (void);
+
+#endif /* CACHE_H */
diff --git a/usr.bin/cvs/checkout.c b/usr.bin/cvs/checkout.c
new file mode 100644
index 00000000000..25d53f6ae46
--- /dev/null
+++ b/usr.bin/cvs/checkout.c
@@ -0,0 +1,80 @@
+/* $OpenBSD: checkout.c,v 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..6f2fd2e63df
--- /dev/null
+++ b/usr.bin/cvs/client.c
@@ -0,0 +1,595 @@
+/* $OpenBSD: client.c,v 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..839bd29abb2
--- /dev/null
+++ b/usr.bin/cvs/commit.c
@@ -0,0 +1,145 @@
+/* $OpenBSD: commit.c,v 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.1 b/usr.bin/cvs/cvs.1
new file mode 100644
index 00000000000..53b17614497
--- /dev/null
+++ b/usr.bin/cvs/cvs.1
@@ -0,0 +1,148 @@
+.\" $OpenBSD: cvs.1,v 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.
+.\"
+.Dd May 16, 2004
+.Dt CVS 1
+.Os
+.Sh NAME
+.Nm cvs
+.Nd OpenCVS Concurrent Versioning System client/server
+.Sh SYNOPSIS
+.Nm
+.Op Fl flQqv
+.Op Fl d Ar root
+.Op Fl e Ar editor
+.Ar command Op Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+program acts as both client and server for the use of and administration of
+a CVS source repository.
+CVS is used to maintain version information on files that are kept in a
+repository.
+Although it is more commonly used to track changes in source code, there
+are no real limitations to the type of files that you can store in a
+repository.
+.Pp
+The following options are supported:
+.Bl -tag -width Ds
+.It Fl d Ar root
+Use
+.Ar root
+as the path to the root directory of the CVS repository.
+The value must specify an absolute path.
+.It Fl e Ar editor
+Use the program
+.Ar editor
+whenever editing log information.
+This option overrides the environment variables CVSEDITOR, VISUAL and EDITOR.
+.It Fl f
+Do not read the contents of the user's .cvsrc file on startup.
+.It Fl l
+Suppress logging of history information.
+.It Fl Q
+Be extra quiet.
+Only error messages will be displayed.
+.It Fl q
+Be quiet about reporting.
+.It Fl v
+Display version information and exit.
+.El
+.Sh COMMANDS
+.Bl -tag -width Ds
+.Ic add
+.Ar file Op ...
+Before a file is known to CVS, it must be added to the repository using
+this command.
+Adding a file does not actually publish the contents of the
+file, so you must commit the first revision in order to let other users
+see the file with the
+.Ic update
+command.
+.Pp
+.Ic commit
+.Op Fl m Ar msg
+The
+.Ic commit
+command is used to send local changes back to the server and update the
+repository's information to reflect the changes.
+.Pp
+.Ic update
+.Op Fl dP
+.El
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa $HOME/.cvsrc
+File containing a list of implicit options to pass to certain commands.
+This file is read on startup unless the
+.Fl f
+option is specified.
+.It Pa $CVSROOT/CVSROOT
+Directory containing repository administrative files.
+.It Pa $CVSROOT/CVSROOT/loginfo
+File containing associations between modules and handlers for
+post-commit logging.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width CVS_CLIENT_LOG
+.It Ev CVSROOT
+When set, this variable should contain the string pointing to the root
+directory of the CVS repository.
+The contents of this variable are ignored when the
+.Fl d
+option is given or if `Root' files exist in the checked-out copy.
+.It Ev CVS_RSH
+Name of the program to use when connecting to the server through a remote
+shell.
+The default is to use the
+.Xr ssh 1
+program.
+.It Ev CVS_SERVER
+If set, gives the name of the program to invoke as a
+.Nm
+server when using remote shell.
+The default is to use `cvs'.
+.It Ev CVSEDITOR
+Name of the editor to use when editing commit messages.
+Checked before EDITOR and VISUAL.
+.It Ev EDITOR
+.It Ev VISUAL
+.It Ev CVS_CLIENT_LOG
+This variable enables logging of all communications between the client and
+server when running in non-local mode.
+If set, this environment variable must contain a base path from which two
+paths will be generated by appending ".in" to the value for the server's
+input and ".out" for the server's output.
+.El
+.Sh SEE ALSO
+.Xr rcs 1 ,
+.Xr diff 1 ,
+.Xr patch 1 ,
+.Xr cvsd 8
+.Sh HISTORY
+The OpenCVS project is a BSD-licensed rewrite of the original
+Concurrent Versioning System written by Jean-Francois Brousseau.
+The original CVS code was written in large parts by Dick Grune,
+Brian Berliner and Jeff Polk.
diff --git a/usr.bin/cvs/cvs.c b/usr.bin/cvs/cvs.c
new file mode 100644
index 00000000000..8ec94769df0
--- /dev/null
+++ b/usr.bin/cvs/cvs.c
@@ -0,0 +1,497 @@
+#define DEBUG
+/* $OpenBSD: cvs.c,v 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/wait.h>
+
+#include <err.h>
+#include <pwd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "cvs.h"
+#include "log.h"
+
+
+extern char *__progname;
+
+
+/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
+int verbosity = 2;
+
+
+
+/* compression level used with zlib, 0 meaning no compression taking place */
+int cvs_compress = 0;
+int cvs_trace = 0;
+int cvs_nolog = 0;
+int cvs_readonly = 0;
+
+/* name of the command we are running */
+char *cvs_command;
+char *cvs_rootstr;
+char *cvs_rsh = CVS_RSH_DEFAULT;
+char *cvs_editor = CVS_EDITOR_DEFAULT;
+
+struct cvsroot *cvs_root = NULL;
+
+
+/*
+ * Command dispatch table
+ * ----------------------
+ *
+ * The synopsis field should only contain the list of arguments that the
+ * command supports, without the actual command's name.
+ *
+ * Command handlers are expected to return 0 if no error occured, or one of
+ * the values known in sysexits.h in case of an error. In case the error
+ * returned is EX_USAGE, the command's usage string is printed to standard
+ * error before returning.
+ */
+
+static struct cvs_cmd {
+ char cmd_name[CVS_CMD_MAXNAMELEN];
+ char cmd_alias[CVS_CMD_MAXALIAS][CVS_CMD_MAXNAMELEN];
+ int (*cmd_hdlr)(int, char **);
+ char *cmd_synopsis;
+ char cmd_descr[CVS_CMD_MAXDESCRLEN];
+} cvs_cdt[] = {
+ {
+ "add", { "ad", "new" }, cvs_add,
+ "[-m msg] file ...",
+ "Add a new file/directory to the repository",
+ },
+ {
+ "admin", { "adm", "rcs" }, NULL,
+ "",
+ "Administration front end for rcs",
+ },
+ {
+ "annotate", { "ann" }, NULL,
+ "",
+ "Show last revision where each line was modified",
+ },
+ {
+ "checkout", { "co", "get" }, NULL,
+ "",
+ "Checkout sources for editing",
+ },
+ {
+ "commit", { "ci", "com" }, cvs_commit,
+ "",
+ "Check files into the repository",
+ },
+ {
+ "diff", { "di", "dif" }, cvs_diff,
+ "",
+ "Show differences between revisions",
+ },
+ {
+ "edit", { }, NULL,
+ "",
+ "Get ready to edit a watched file",
+ },
+ {
+ "editors", { }, NULL,
+ "",
+ "See who is editing a watched file",
+ },
+ {
+ "export", { "ex", "exp" }, NULL,
+ "",
+ "Export sources from CVS, similar to checkout",
+ },
+ {
+ "history", { "hi", "his" }, cvs_history,
+ "",
+ "Show repository access history",
+ },
+ {
+ "import", { "im", "imp" }, NULL,
+ "",
+ "Import sources into CVS, using vendor branches",
+ },
+ {
+ "init", { }, cvs_init,
+ "",
+ "Create a CVS repository if it doesn't exist",
+ },
+#if defined(HAVE_KERBEROS)
+ {
+ "kserver", {}, NULL
+ "",
+ "Start a Kerberos authentication CVS server",
+ },
+#endif
+ {
+ "log", { "lo" }, cvs_getlog,
+ "",
+ "Print out history information for files",
+ },
+ {
+ "login", {}, NULL,
+ "",
+ "Prompt for password for authenticating server",
+ },
+ {
+ "logout", {}, NULL,
+ "",
+ "Removes entry in .cvspass for remote repository",
+ },
+ {
+ "rdiff", {}, NULL,
+ "",
+ "Create 'patch' format diffs between releases",
+ },
+ {
+ "release", {}, NULL,
+ "",
+ "Indicate that a Module is no longer in use",
+ },
+ {
+ "remove", {}, NULL,
+ "",
+ "Remove an entry from the repository",
+ },
+ {
+ "rlog", {}, NULL,
+ "",
+ "Print out history information for a module",
+ },
+ {
+ "rtag", {}, NULL,
+ "",
+ "Add a symbolic tag to a module",
+ },
+ {
+ "server", {}, cvs_server,
+ "",
+ "Server mode",
+ },
+ {
+ "status", {}, NULL,
+ "",
+ "Display status information on checked out files",
+ },
+ {
+ "tag", { "ta", }, NULL,
+ "",
+ "Add a symbolic tag to checked out version of files",
+ },
+ {
+ "unedit", {}, NULL,
+ "",
+ "Undo an edit command",
+ },
+ {
+ "update", {}, cvs_update,
+ "",
+ "Bring work tree in sync with repository",
+ },
+ {
+ "version", {}, cvs_version,
+ "",
+ "Show current CVS version(s)",
+ },
+ {
+ "watch", {}, NULL,
+ "",
+ "Set watches",
+ },
+ {
+ "watchers", {}, NULL,
+ "",
+ "See who is watching a file",
+ },
+};
+
+#define CVS_NBCMD (sizeof(cvs_cdt)/sizeof(cvs_cdt[0]))
+
+
+
+void usage (void);
+void sigchld_hdlr (int);
+void cvs_readrc (void);
+struct cvs_cmd* cvs_findcmd (const char *);
+
+
+
+/*
+ * sigchld_hdlr()
+ *
+ * Handler for the SIGCHLD signal, which can be received in case we are
+ * running a remote server and it dies.
+ */
+
+void
+sigchld_hdlr(int signo)
+{
+ int status;
+ pid_t pid;
+
+ if ((pid = wait(&status)) == -1) {
+ }
+}
+
+
+/*
+ * usage()
+ *
+ * Display usage information.
+ */
+
+void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: %s [-lQqtv] [-d root] [-e editor] [-z level] "
+ "command [options] ...\n",
+ __progname);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ char *envstr, *ep;
+ int ret;
+ u_int i, readrc;
+ struct cvs_cmd *cmdp;
+
+ readrc = 1;
+
+ if (cvs_log_init(LD_STD, 0) < 0)
+ err(1, "failed to initialize logging");
+
+ /* by default, be very verbose */
+ (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
+
+#ifdef DEBUG
+ (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
+#endif
+
+ /* check environment so command-line options override it */
+ if ((envstr = getenv("CVS_RSH")) != NULL)
+ cvs_rsh = envstr;
+
+ if (((envstr = getenv("CVSEDITOR")) != NULL) ||
+ ((envstr = getenv("VISUAL")) != NULL) ||
+ ((envstr = getenv("EDITOR")) != NULL))
+ cvs_editor = envstr;
+
+ while ((ret = getopt(argc, argv, "d:e:fHlnQqrtvz:")) != -1) {
+ switch (ret) {
+ case 'd':
+ cvs_rootstr = optarg;
+ break;
+ case 'e':
+ cvs_editor = optarg;
+ break;
+ case 'f':
+ readrc = 0;
+ break;
+ case 'l':
+ cvs_nolog = 1;
+ break;
+ case 'n':
+ break;
+ case 'Q':
+ verbosity = 0;
+ break;
+ case 'q':
+ /* don't override -Q */
+ if (verbosity > 1)
+ verbosity = 1;
+ break;
+ case 'r':
+ cvs_readonly = 1;
+ break;
+ case 't':
+ cvs_trace = 1;
+ break;
+ case 'v':
+ printf("%s\n", CVS_VERSION);
+ exit(0);
+ /* NOTREACHED */
+ break;
+ case 'z':
+ cvs_compress = (int)strtol(optarg, &ep, 10);
+ if (*ep != '\0')
+ errx(1, "error parsing compression level");
+ if (cvs_compress < 0 || cvs_compress > 9)
+ errx(1, "gzip compression level must be "
+ "between 0 and 9");
+ break;
+ default:
+ usage();
+ exit(EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* reset getopt() for use by commands */
+ optind = 1;
+ optreset = 1;
+
+ if (argc == 0) {
+ usage();
+ exit(EX_USAGE);
+ }
+
+ /* setup signal handlers */
+ signal(SIGCHLD, sigchld_hdlr);
+
+ if (readrc)
+ cvs_readrc();
+
+ cvs_command = argv[0];
+ ret = -1;
+
+ cmdp = cvs_findcmd(cvs_command);
+ if (cmdp == NULL) {
+ fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
+ fprintf(stderr, "CVS commands are:\n");
+ for (i = 0; i < CVS_NBCMD; i++)
+ fprintf(stderr, "\t%-16s%s\n",
+ cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr);
+ exit(EX_USAGE);
+ }
+
+ if (cmdp->cmd_hdlr == NULL) {
+ cvs_log(LP_ERR, "command `%s' not implemented", cvs_command);
+ exit(1);
+ }
+
+ ret = (*cmdp->cmd_hdlr)(argc, argv);
+ if (ret == EX_USAGE) {
+ fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
+ cmdp->cmd_synopsis);
+ }
+
+ return (ret);
+}
+
+
+/*
+ * cvs_findcmd()
+ *
+ * Find the entry in the command dispatch table whose name or one of its
+ * aliases matches <cmd>.
+ * Returns a pointer to the command entry on success, NULL on failure.
+ */
+
+struct cvs_cmd*
+cvs_findcmd(const char *cmd)
+{
+ u_int i, j;
+ struct cvs_cmd *cmdp;
+
+ cmdp = NULL;
+
+ for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) {
+ if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0)
+ cmdp = &cvs_cdt[i];
+ else {
+ for (j = 0; j < CVS_CMD_MAXALIAS; j++) {
+ if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) {
+ cmdp = &cvs_cdt[i];
+ break;
+ }
+ }
+ }
+ }
+
+ return (cmdp);
+}
+
+
+/*
+ * cvs_readrc()
+ *
+ * Read the CVS `.cvsrc' file in the user's home directory. If the file
+ * exists, it should contain a list of arguments that should always be given
+ * implicitly to the specified commands.
+ */
+
+void
+cvs_readrc(void)
+{
+ char rcpath[MAXPATHLEN], linebuf[128], *lp;
+ struct cvs_cmd *cmdp;
+ struct passwd *pw;
+ FILE *fp;
+
+ pw = getpwuid(getuid());
+ if (pw == NULL) {
+ cvs_log(LP_NOTICE, "failed to get user's password entry");
+ return;
+ }
+
+ snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
+
+ fp = fopen(rcpath, "r");
+ if (fp == NULL) {
+ if (errno != ENOENT)
+ cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
+ strerror(errno));
+ return;
+ }
+
+ while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
+ lp = strchr(linebuf, ' ');
+
+ /* ignore lines with no arguments */
+ if (lp == NULL)
+ continue;
+
+ *(lp++) = '\0';
+ if (strcmp(linebuf, "cvs") == 0) {
+ /* global options */
+ }
+ else {
+ cmdp = cvs_findcmd(linebuf);
+ if (cmdp == NULL) {
+ cvs_log(LP_NOTICE,
+ "unknown command `%s' in cvsrc",
+ linebuf);
+ continue;
+ }
+ }
+ }
+ if (ferror(fp)) {
+ cvs_log(LP_NOTICE, "failed to read line from cvsrc");
+ }
+
+ (void)fclose(fp);
+}
diff --git a/usr.bin/cvs/cvs.h b/usr.bin/cvs/cvs.h
new file mode 100644
index 00000000000..0a5b5120619
--- /dev/null
+++ b/usr.bin/cvs/cvs.h
@@ -0,0 +1,374 @@
+/* $OpenBSD: cvs.h,v 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/cvs/Makefile b/usr.bin/cvs/cvs/Makefile
new file mode 100644
index 00000000000..1b01c901887
--- /dev/null
+++ b/usr.bin/cvs/cvs/Makefile
@@ -0,0 +1,16 @@
+# $Id: Makefile,v 1.1 2004/07/13 22:02:40 jfb Exp $
+
+.PATH: ${.CURDIR}/..
+
+PROG=ocvs
+BINDIR=/usr/bin
+BINOWN=root
+BINGRP=_cvsd
+BINMODE=2555
+MAN=cvs.1 cvsrc.5
+
+SRCS= cvs.c add.c buf.c client.c commit.c diff.c entries.c getlog.c \
+ history.c hist.c init.c lock.c log.c proto.c rcs.c rcsnum.c root.c \
+ server.c sock.c update.c util.c version.c
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/cvs/cvsacl.5 b/usr.bin/cvs/cvsacl.5
new file mode 100644
index 00000000000..f1f12cf370a
--- /dev/null
+++ b/usr.bin/cvs/cvsacl.5
@@ -0,0 +1,107 @@
+.\" $OpenBSD: cvsacl.5,v 1.1 2004/07/13 22:02:40 jfb Exp $
+.\"
+.\" Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
+.\"
+.\" 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. 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 BY THE AUTHOR ``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.
+.\"
+.Dd June 22, 2004
+.Dt CVSACL 5
+.Os
+.Sh NAME
+.Nm cvsacl
+.Nd CVS Access Control Lists format
+.Sh DESCRIPTION
+The
+.Nm
+file format is the format understood by the
+.Xr cvsd 8
+daemon for performing access control on files within the CVS repository.
+It is composed of access rules, each on a single line, which either allow
+or deny certain operations on files or entire directories.
+.Sh GRAMMAR
+The
+.Nm
+grammar is expressed in BNF (Backus-Naur Form) notation.
+Terminals are displayed as normal text.
+Nonterminals are in bold.
+Anything enclosed between angle brackets
+.Po
+.Ql [
+and
+.Ql \&]
+.Pc
+is optional.
+The pipe character
+.Pq Ql \&|
+is used to separate multiple choices.
+.Pp
+Here is the BNF syntax for
+.Nm
+rules:
+.Bl -tag -width "this is a test"
+.It Ic line
+::=
+.It Ic rule
+::=
+.Ic action
+.Bo
+.Ic optlist
+.Bc
+.Ic op
+[ branch
+.Ic branch
+]
+.Pp
+[ from
+.Ic userlist
+]
+.It Ic action
+::= allow | deny
+.It Ic option
+::= quick | log
+.It Ic optlist
+::=
+.Ic option
+|
+.Ic optlist ,
+.Ic option
+.It Ic op
+::= add | commit | tag | update
+.It Ic userlist
+::=
+.El
+.Pp
+.Sh EXAMPLES
+The following rule denies all operations:
+.Bd -literal
+ deny quick
+.Ed
+.Sh SEE ALSO
+.Xr cvs 1 ,
+.Xr cvsd 8
+.Sh HISTORY
+The
+.Nm
+file format appeared with the OpenCVS project.
+.Sh AUTHORS
+.An Jean-Francois Brousseau
diff --git a/usr.bin/cvs/cvsd.8 b/usr.bin/cvs/cvsd.8
new file mode 100644
index 00000000000..f65eba78929
--- /dev/null
+++ b/usr.bin/cvs/cvsd.8
@@ -0,0 +1,116 @@
+.\" $OpenBSD: cvsd.8,v 1.1 2004/07/13 22:02:40 jfb Exp $
+.\"
+.\" Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
+.\"
+.\" 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. 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 BY THE AUTHOR ``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.
+.\"
+.Dd May 16, 2004
+.Dt CVSD 8
+.Os
+.Sh NAME
+.Nm cvsd
+.Nd Concurrent Versions System daemon
+.Sh SYNOPSIS
+.Nm cvsd
+.Op Fl dfpv
+.Op Fl a Ar aclfile
+.Op Fl r Ar cvsroot
+.Op Fl s Ar sockpath
+.Sh DESCRIPTION
+The
+.Nm
+daemon manages access to a CVS repository.
+It provides a much more secure alternative to the traditional client-server
+model commonly used by
+.Xr cvs 1
+through various mechanisms described below.
+.Pp
+On startup,
+.Nm
+spawns a child process that chroots to the CVS repository's root directory.
+Both processes then drop privileges to user and group
+.Ic _cvsd .
+Once this is done, the child process loads the list of ACLs and opens a local
+socket on which it listens for requests.
+The parent process' only purpose is to answer requests for things outside of
+the child's jail.
+.Pp
+In order to enforce ACLs and prevent users from modifying the files in the
+repository directly, all of the files within the repository should be owned
+by the user and group
+.Ic _cvsd
+and should only be writable by the user.
+.Pp
+The options are as follows:
+.Bl -tag -width "-s sockpath"
+.It Fl a Ar aclfile
+Use
+.Ar aclfile
+as the source file for the Access Control Lists to apply on the repository.
+.It Fl d
+Start the server with debugging enabled.
+This option overrides the
+.Fl v
+option.
+.It Fl f
+Stay in foreground instead of performing the usual operations to become
+a daemon.
+This causes all log messages to be printed on standard input or standard
+error, depending on the priority of each message.
+.It Fl p
+On startup, perform a check on the whole contents of the CVS repository to
+check file permissions and ownership, and print warnings for any files or
+directories that do not match the expected permission masks.
+When running with this option,
+.Nm
+will exit with an error message if any of the files have permissions that are
+too open.
+.It Fl r Ar cvsroot
+Use
+.Ar cvsroot
+as the CVS repository's root directory.
+.It Fl s Ar sockpath
+Use the path specified by
+.Ar sockpath
+as the file to bind to for the local socket.
+.It Fl v
+Be verbose.
+.Sh FILES
+.Bl -tag -width /var/run/cvsd.sock -compact
+.It Pa /var/run/cvsd.pid
+Process ID of the currently running
+.Nm .
+.It Pa /var/run/cvsd.sock
+Default listening socket for incoming cvs requests.
+.El
+.Sh SEE ALSO
+.Xr cvs 1 ,
+.Xr rcs 1 ,
+.Xr rcsfile 5 ,
+.Xr cvsacl 5
+.Sh HISTORY
+The
+.Nm
+server appeared as part of the OpenCVS project.
+.Sh AUTHORS
+.An Jean-Francois Brousseau
diff --git a/usr.bin/cvs/cvsd.c b/usr.bin/cvs/cvsd.c
new file mode 100644
index 00000000000..748eadfe5ef
--- /dev/null
+++ b/usr.bin/cvs/cvsd.c
@@ -0,0 +1,649 @@
+/* $OpenBSD: cvsd.c,v 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/stat.h>
+#include <sys/wait.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <pwd.h>
+#include <grp.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "log.h"
+#include "sock.h"
+#include "cvs.h"
+#include "cvsd.h"
+
+
+static void cvsd_parent_loop (void);
+static void cvsd_child_loop (void);
+static int cvsd_privdrop (void);
+
+
+extern char *__progname;
+
+
+
+int foreground = 0;
+
+volatile sig_atomic_t restart = 0;
+
+
+uid_t cvsd_uid = -1;
+gid_t cvsd_gid = -1;
+
+
+char *cvsd_root = NULL;
+char *cvsd_aclfile = NULL;
+
+
+static int cvsd_privfd = -1;
+
+
+
+static TAILQ_HEAD(,cvsd_child) cvsd_children;
+static volatile sig_atomic_t cvsd_chnum = 0;
+static volatile sig_atomic_t cvsd_chmin = CVSD_CHILD_DEFMIN;
+static volatile sig_atomic_t cvsd_chmax = CVSD_CHILD_DEFMAX;
+
+
+/*
+ * sighup_handler()
+ *
+ * Handler for the SIGHUP signal.
+ */
+
+void
+sighup_handler(int signo)
+{
+ restart = 1;
+}
+
+
+/*
+ * sigint_handler()
+ *
+ * Handler for the SIGINT signal.
+ */
+
+void
+sigint_handler(int signo)
+{
+ cvs_sock_doloop = 0;
+}
+
+
+/*
+ * sigchld_handler()
+ *
+ */
+
+void
+sigchld_handler(int signo)
+{
+ int status;
+
+ wait(&status);
+
+}
+
+
+/*
+ * usage()
+ *
+ * Display program usage.
+ */
+
+void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: %s [-dfpv] [-a aclfile] [-c config] [-r root] [-s path]\n"
+ "\t-a aclfile\tUse the file <aclfile> for ACL ruleset\n"
+ "\t-d\t\tStart the server in debugging mode (very verbose)\n"
+ "\t-f\t\tStay in foreground instead of becoming a daemon\n"
+ "\t-p\t\tPerform permission and ownership check on the repository\n"
+ "\t-r root\tUse <root> as the root directory of the repository\n"
+ "\t-s path\tUse <path> as the path for the CVS server socket\n"
+ "\t-v\t\tBe verbose\n",
+ __progname);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ u_int i;
+ int ret, checkrepo;
+ uid_t uid;
+ struct passwd *pwd;
+ struct group *grp;
+
+ checkrepo = 0;
+
+ if (cvs_log_init(LD_STD|LD_SYSLOG, LF_PID) < 0)
+ err(1, "failed to initialize logging mechanism");
+
+ while ((ret = getopt(argc, argv, "a:dfpr:s:v")) != -1) {
+ switch (ret) {
+ case 'a':
+ cvsd_aclfile = optarg;
+ break;
+ case 'd':
+ cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
+ cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
+ break;
+ case 'f':
+ foreground = 1;
+ break;
+ case 'p':
+ checkrepo = 1;
+ break;
+ case 'r':
+ cvsd_root = optarg;
+ break;
+ case 's':
+ cvsd_sock_path = optarg;
+ break;
+ case 'v':
+ cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
+ break;
+ default:
+ usage();
+ exit(EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if ((cvsd_aclfile != NULL) && (cvs_acl_parse(cvsd_aclfile) < 0))
+ errx(1, "error while parsing the ACL file `%s'", cvsd_aclfile);
+
+ if (cvsd_root == NULL)
+ errx(1, "no CVS root directory specified");
+
+ if (argc > 0)
+ errx(EX_USAGE, "unrecognized trailing arguments");
+
+ TAILQ_INIT(&cvsd_children);
+
+ pwd = getpwnam(CVSD_USER);
+ if (pwd == NULL)
+ err(EX_NOUSER, "failed to get user `%s'", CVSD_USER);
+
+ grp = getgrnam(CVSD_GROUP);
+ if (grp == NULL)
+ err(EX_NOUSER, "failed to get group `%s'", CVSD_GROUP);
+
+ cvsd_uid = pwd->pw_uid;
+ cvsd_gid = grp->gr_gid;
+
+ signal(SIGHUP, sighup_handler);
+ signal(SIGINT, sigint_handler);
+ signal(SIGQUIT, sigint_handler);
+ signal(SIGTERM, sigint_handler);
+
+ if (!foreground && daemon(0, 0) == -1) {
+ cvs_log(LP_ERRNO, "failed to become a daemon");
+ exit(EX_OSERR);
+ }
+
+ if (cvsd_sock_open() < 0) {
+ exit(1);
+ }
+
+ if (setegid(cvsd_gid) == -1) {
+ cvs_log(LP_ERRNO, "failed to drop group privileges");
+ exit(EX_OSERR);
+ }
+ if (seteuid(cvsd_uid) == -1) {
+ cvs_log(LP_ERRNO, "failed to drop user privileges");
+ exit(EX_OSERR);
+ }
+
+ if (checkrepo && cvsd_checkperms("/") != 0) {
+ cvs_log(LP_ERR,
+ "exiting due to permission errors on repository");
+ exit(1);
+ }
+
+ /* spawn the initial pool of children */
+ for (i = 0; i < cvsd_chmin; i++) {
+ ret = cvsd_forkchild();
+ if (ret == -1) {
+ cvs_log(LP_ERR, "failed to spawn child");
+ exit(EX_OSERR);
+ }
+ }
+
+ cvsd_sock_loop();
+
+ cvs_log(LP_NOTICE, "shutting down");
+ cvs_log_cleanup();
+
+ cvsd_sock_close();
+
+ return (0);
+}
+
+
+/*
+ * cvsd_privdrop()
+ *
+ * Drop privileges.
+ */
+
+int
+cvsd_privdrop(void)
+{
+ cvs_log(LP_INFO, "dropping privileges to %s:%s", CVSD_USER,
+ CVSD_GROUP);
+ if (setegid(cvsd_gid) == -1) {
+ cvs_log(LP_ERRNO, "failed to drop group privileges to %s",
+ CVSD_GROUP);
+ return (-1);
+ }
+
+ if (seteuid(cvsd_uid) == -1) {
+ cvs_log(LP_ERRNO, "failed to drop user privileges to %s",
+ CVSD_USER);
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvsd_checkperms()
+ *
+ * Check permissions on the CVS repository and log warnings for any
+ * weird of loose permissions.
+ * Returns the number of warnings on success, or -1 on failure.
+ */
+
+int
+cvsd_checkperms(const char *path)
+{
+ int fd, nbwarn, ret;
+ mode_t fmode;
+ long base;
+ void *dp, *endp;
+ char buf[1024], spath[MAXPATHLEN];
+ struct stat st;
+ struct dirent *dep;
+
+ nbwarn = 0;
+
+ cvs_log(LP_DEBUG, "checking permissions on `%s'", path);
+
+ if (stat(path, &st) == -1) {
+ cvs_log(LP_ERRNO, "failed to stat `%s'", path);
+ return (-1);
+ }
+
+ if (S_ISDIR(st.st_mode))
+ fmode = CVSD_DPERM;
+ else
+ fmode = CVSD_FPERM;
+
+ if (st.st_uid != cvsd_uid) {
+ cvs_log(LP_WARN, "owner of `%s' is not %s", path, CVSD_USER);
+ nbwarn++;
+ }
+
+ if (st.st_gid != cvsd_gid) {
+ cvs_log(LP_WARN, "group of `%s' is not %s", path, CVSD_GROUP);
+ nbwarn++;
+ }
+
+ if (st.st_mode & S_IWGRP) {
+ cvs_log(LP_WARN, "file `%s' is group-writable", path,
+ fmode);
+ nbwarn++;
+ }
+
+ if (st.st_mode & S_IWOTH) {
+ cvs_log(LP_WARN, "file `%s' is world-writable", path,
+ fmode);
+ nbwarn++;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fd = open(path, O_RDONLY, 0);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to open directory `%s'",
+ path);
+ return (nbwarn);
+ }
+ /* recurse */
+ ret = getdirentries(fd, buf, sizeof(buf), &base);
+ if (ret == -1) {
+ cvs_log(LP_ERRNO,
+ "failed to get directory entries for `%s'", path);
+ (void)close(fd);
+ return (nbwarn);
+ }
+
+ dp = buf;
+ endp = buf + ret;
+
+ while (dp < endp) {
+ dep = (struct dirent *)dp;
+ dp += dep->d_reclen;
+
+ if ((dep->d_namlen == 1) && (dep->d_name[0] == '.'))
+ continue;
+ if ((dep->d_namlen == 2) && (dep->d_name[0] == '.') &&
+ (dep->d_name[1] == '.'))
+ continue;
+
+ /* skip the CVSROOT directory */
+ if (strcmp(dep->d_name, CVS_PATH_ROOT) == 0)
+ continue;
+
+ snprintf(spath, sizeof(spath), "%s/%s", path,
+ dep->d_name);
+ ret = cvsd_checkperms(spath);
+ if (ret == -1)
+ nbwarn++;
+ else
+ nbwarn += ret;
+ }
+ (void)close(fd);
+ }
+
+
+ return (nbwarn);
+}
+
+
+/*
+ * cvsd_forkchild()
+ *
+ * Fork a child process which chroots to the CVS repository's root directory.
+ * We need to temporarily regain privileges in order to chroot.
+ * On success, returns 0 if this is the child process, 1 if this is the
+ * parent, or -1 on failure.
+ */
+
+int
+cvsd_forkchild(void)
+{
+ int svec[2];
+ pid_t pid;
+ struct cvsd_child *chp;
+
+ if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, svec) == -1) {
+ cvs_log(LP_ERRNO, "failed to create socket pair");
+ return (-1);
+ }
+
+ /*
+ * We need to temporarily regain original privileges in order for the
+ * child to chroot().
+ */
+ if (seteuid(0) == -1) {
+ cvs_log(LP_ERRNO, "failed to regain privileges");
+ return (-1);
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ cvs_log(LP_ERRNO, "failed to fork child");
+ (void)close(svec[0]);
+ (void)close(svec[1]);
+ return (-1);
+ }
+
+ if (pid == 0) {
+ cvsd_privfd = svec[1];
+ (void)close(svec[0]);
+
+ cvs_log(LP_INFO, "changing root to %s", cvsd_root);
+ if (chroot(cvsd_root) == -1) {
+ cvs_log(LP_ERRNO, "failed to chroot to `%s'",
+ cvsd_root);
+ exit(EX_OSERR);
+ }
+ (void)chdir("/");
+
+ /* redrop privileges */
+ if (setgid(cvsd_gid) == -1) {
+ cvs_log(LP_ERRNO, "failed to drop privileges");
+ exit(EX_OSERR);
+ }
+ if (setuid(cvsd_uid) == -1) {
+ cvs_log(LP_ERRNO, "failed to drop privileges");
+ exit(EX_OSERR);
+ }
+
+ setproctitle("%s [child %d]", __progname, getpid());
+
+ cvsd_child_loop();
+
+ return (0);
+ }
+
+ cvs_log(LP_INFO, "spawning child %d", pid);
+
+ if (seteuid(cvsd_uid) == -1) {
+ cvs_log(LP_ERRNO, "failed to redrop privs");
+ return (-1);
+ }
+
+ chp = (struct cvsd_child *)malloc(sizeof(*chp));
+ if (chp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate child data");
+ return (-1);
+ }
+
+ chp->ch_pid = pid;
+ chp->ch_sock = svec[0];
+ (void)close(svec[1]);
+
+ return (1);
+}
+
+
+/*
+ * cvsd_parent_loop()
+ *
+ * Main loop of the parent cvsd process, which listens on its end of the
+ * socket pair for requests from the chrooted child.
+ */
+
+static void
+cvsd_parent_loop(void)
+{
+ uid_t uid;
+ int timeout, ret;
+ nfds_t nfds, i;
+ struct pollfd *pfd;
+ struct cvsd_child *chp;
+
+ nfds = 0;
+ timeout = INFTIM;
+
+ for (;;) {
+ nfds = cvsd_chnum;
+ pfd = (struct pollfd *)realloc(pfd,
+ nfds * sizeof(struct pollfd));
+ if (pfd == NULL) {
+ cvs_log(LP_ERRNO, "failed to reallocate polling data");
+ return;
+ }
+
+ i = 0;
+ TAILQ_FOREACH(chp, &cvsd_children, ch_list) {
+ pfd[i].fd = chp->ch_sock;
+ pfd[i].events = POLLIN;
+ pfd[i].revents = 0;
+ i++;
+
+ if (i == nfds)
+ break;
+ }
+
+ ret = poll(pfd, nfds, timeout);
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "poll error");
+ break;
+ }
+
+ chp = TAILQ_FIRST(&cvsd_children);
+ for (i = 0; i < nfds; i++) {
+ if (pfd[i].revents & (POLLERR|POLLNVAL)) {
+ cvs_log(LP_ERR,
+ "poll error on child socket (PID %d)",
+ chp->ch_pid);
+ }
+ else
+ cvsd_msghdlr(chp, pfd[i].fd);
+
+ chp = TAILQ_NEXT(chp, ch_list);
+ }
+
+ }
+}
+
+
+/*
+ * cvsd_child_loop()
+ *
+ */
+
+static void
+cvsd_child_loop(void)
+{
+ int ret, timeout;
+ struct pollfd pfd[1];
+
+ pfd[0].fd = cvsd_privfd;
+ pfd[0].events = POLLIN;
+ timeout = INFTIM;
+
+ for (;;) {
+ ret = poll(pfd, 1, timeout);
+ if (ret == -1) {
+ }
+ else if (ret == 0) {
+ cvs_log(LP_INFO, "parent process closed descriptor");
+ break;
+ }
+ cvs_log(LP_INFO, "polling");
+
+ }
+
+ exit(0);
+}
+
+
+/*
+ * cvsd_msghdlr()
+ */
+
+int
+cvsd_msghdlr(struct cvsd_child *child, int fd)
+{
+ uid_t uid;
+ ssize_t ret;
+ char rbuf[CVSD_MSG_MAXLEN];
+ struct group *gr;
+ struct passwd *pw;
+ struct iovec iov[2];
+ struct cvsd_msg msg;
+
+ ret = read(fd, &msg, sizeof(msg));
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "failed to read CVS message");
+ return (-1);
+ }
+ else if (ret == 0) {
+ cvs_log(LP_WARN, "child closed socket pair");
+ return (0);
+ }
+
+ if (msg.cm_len > 0) {
+ ret = read(fd, rbuf, msg.cm_len);
+ if (ret != (ssize_t)msg.cm_len) {
+ cvs_log(LP_ERR, "failed to read entire msg");
+ return (-1);
+ }
+ }
+
+ /* setup the I/O vector for the reply */
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = sizeof(msg);
+
+ msg.cm_type = CVSD_MSG_ERROR;
+ msg.cm_len = 0;
+
+ switch (msg.cm_type) {
+ case CVSD_MSG_GETUID:
+ rbuf[ret] = '\0';
+ cvs_log(LP_INFO, "getting UID for `%s'", rbuf);
+
+ pw = getpwnam(rbuf);
+ if (pw != NULL) {
+ msg.cm_type = CVSD_MSG_UID;
+ msg.cm_len = sizeof(uid_t);
+ iov[1].iov_len = msg.cm_len;
+ iov[1].iov_base = &(pw->pw_uid);
+ }
+ break;
+ case CVSD_MSG_GETUNAME:
+ memcpy(&uid, rbuf, sizeof(uid));
+ cvs_log(LP_INFO, "getting username for UID %u", uid);
+ pw = getpwuid(uid);
+ if (pw != NULL) {
+ msg.cm_type = CVSD_MSG_UNAME;
+ msg.cm_len = strlen(pw->pw_name);
+ iov[1].iov_len = msg.cm_len;
+ iov[1].iov_base = pw->pw_name;
+ }
+ break;
+ default:
+ cvs_log(LP_ERR, "unknown command type %u", msg.cm_type);
+ return (-1);
+ }
+
+ ret = writev(fd, iov, 2);
+
+ return (ret);
+}
diff --git a/usr.bin/cvs/cvsd.h b/usr.bin/cvs/cvsd.h
new file mode 100644
index 00000000000..e35dc8d9634
--- /dev/null
+++ b/usr.bin/cvs/cvsd.h
@@ -0,0 +1,106 @@
+/* $OpenBSD: cvsd.h,v 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 CVSD_H
+#define CVSD_H
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <pwd.h>
+#include <signal.h>
+
+#include "cvs.h"
+
+#define CVSD_USER "_cvsd"
+#define CVSD_GROUP "_cvsd"
+
+#define CVSD_CHILD_DEFMIN 3
+#define CVSD_CHILD_DEFMAX 5
+
+
+
+#define CVSD_FPERM (S_IRUSR | S_IWUSR)
+#define CVSD_DPERM (S_IRWXU)
+
+
+/* requests */
+#define CVSD_MSG_GETUID 1
+#define CVSD_MSG_GETUNAME 2
+#define CVSD_MSG_PASSFD 3 /* server passes client file descriptor */
+#define CVSD_MSG_SETIDLE 4 /* client has no further processing to do */
+
+/* replies */
+#define CVSD_MSG_UID 128
+#define CVSD_MSG_UNAME 129
+
+#define CVSD_MSG_SHUTDOWN 253
+#define CVSD_MSG_OK 254
+#define CVSD_MSG_ERROR 255
+
+#define CVSD_MSG_MAXLEN 256
+
+
+/* message structure to pass data between the parent and the chrooted child */
+struct cvsd_msg {
+ u_int8_t cm_type;
+ u_int8_t cm_len; /* length of message data in bytes */
+};
+
+
+struct cvsd_child {
+ pid_t ch_pid;
+ int ch_sock;
+
+ TAILQ_ENTRY(cvsd_child) ch_list;
+};
+
+
+
+extern int foreground;
+
+extern volatile sig_atomic_t running;
+extern volatile sig_atomic_t restart;
+
+
+
+
+
+
+int cvsd_checkperms (const char *);
+int cvsd_forkchild (void);
+
+
+/* from aclparse.y */
+int cvs_acl_parse (const char *);
+u_int cvs_acl_eval (struct cvs_op *);
+
+/* from msg.c */
+int cvsd_sendmsg (int, u_int, const void *, size_t);
+int cvsd_recvmsg (int, u_int *, void *, size_t *);
+
+#endif /* CVSD_H */
diff --git a/usr.bin/cvs/cvsd/Makefile b/usr.bin/cvs/cvsd/Makefile
new file mode 100644
index 00000000000..ad2e5ec891c
--- /dev/null
+++ b/usr.bin/cvs/cvsd/Makefile
@@ -0,0 +1,11 @@
+# $Id: Makefile,v 1.1 2004/07/13 22:02:40 jfb Exp $
+
+.PATH: ${.CURDIR}/..
+
+BINDIR=/usr/sbin
+PROG=cvsd
+MAN=cvsd.8
+
+SRCS= cvsd.c aclparse.y log.c msg.c sock.c
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/cvs/cvsrc.5 b/usr.bin/cvs/cvsrc.5
new file mode 100644
index 00000000000..035d4c0cbfc
--- /dev/null
+++ b/usr.bin/cvs/cvsrc.5
@@ -0,0 +1,65 @@
+.\" $OpenBSD: cvsrc.5,v 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.
+.\"
+.Dd June 14, 2004
+.Dt CVSRC 5
+.Os
+.Sh NAME
+.Nm cvsrc
+.Nd startup resource file for CVS
+.Sh DESCRIPTION
+The cvsrc file provides a way to give the
+.Xr cvs 1
+program implicit global options and command-specific options.
+The file is read on startup unless the
+.Fl f
+option is specified.
+.Pp
+The format of each line is as follows:
+.Pp
+.Dl command [arg ...]
+.Pp
+where
+.Ar command
+is either the `cvs' keyword to specify global options, one of the supported
+.Xr cvs 1
+commands or a command alias.
+Arguments following
+.Ar command
+will be added implicitly to the appropriate command's argument vector if it is
+run.
+Empty lines and lines specifying no optional arguments are ignored.
+Lines whose
+.Ar command
+argument is not a valid command will generate a warning when running without
+the
+.Fl q
+or
+.Fl Q
+flags.
+.Sh EXAMPLES
+Need to put in some examples!
+.Sh SEE ALSO
+.Xr cvs 1
diff --git a/usr.bin/cvs/diff.c b/usr.bin/cvs/diff.c
new file mode 100644
index 00000000000..61291c7031e
--- /dev/null
+++ b/usr.bin/cvs/diff.c
@@ -0,0 +1,1678 @@
+/* $OpenBSD: diff.c,v 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/entries.c b/usr.bin/cvs/entries.c
new file mode 100644
index 00000000000..033eb59518e
--- /dev/null
+++ b/usr.bin/cvs/entries.c
@@ -0,0 +1,263 @@
+/* $OpenBSD: entries.c,v 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/stat.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "log.h"
+#include "cvs.h"
+
+
+#define CVS_ENTRIES_NFIELDS 5
+#define CVS_ENTRIES_DELIM '/'
+
+
+/*
+ * cvs_ent_open()
+ *
+ * Open the CVS Entries file for the directory <dir>.
+ * Returns a pointer to the CVSENTRIES file structure on success, or NULL
+ * on failure.
+ */
+
+CVSENTRIES*
+cvs_ent_open(const char *dir)
+{
+ size_t len;
+ char entpath[MAXPATHLEN], ebuf[128];
+ FILE *fp;
+ struct cvs_ent *ent;
+ CVSENTRIES *ep;
+
+ snprintf(entpath, sizeof(entpath), "%s/" CVS_PATH_ENTRIES, dir);
+ fp = fopen(entpath, "r");
+ if (fp == NULL) {
+ cvs_log(LP_ERRNO, "cannot open CVS/Entries for reading",
+ entpath);
+ return (NULL);
+ }
+
+ ep = (CVSENTRIES *)malloc(sizeof(CVSENTRIES));
+ if (ep == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate Entries data");
+ (void)fclose(fp);
+ return (NULL);
+ }
+ ep->cef_path = strdup(dir);
+ if (ep->cef_path == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy Entries path");
+ free(ep);
+ (void)fclose(fp);
+ return (NULL);
+ }
+
+ ep->cef_nid = 0;
+ ep->cef_entries = NULL;
+ ep->cef_nbent = 0;
+
+ while (fgets(ebuf, sizeof(ebuf), fp) != NULL) {
+ len = strlen(ebuf);
+ if ((len > 0) && (ebuf[len - 1] == '\n'))
+ ebuf[--len] = '\0';
+ ent = cvs_ent_parse(ebuf);
+ if (ent == NULL)
+ continue;
+
+ if (cvs_ent_add(ep, ent) < 0) {
+ cvs_ent_close(ep);
+ ep = NULL;
+ break;
+ }
+ }
+
+ (void)fclose(fp);
+ return (ep);
+}
+
+
+/*
+ * cvs_ent_close()
+ *
+ * Close the Entries file <ep>.
+ */
+
+void
+cvs_ent_close(CVSENTRIES *ep)
+{
+ free(ep);
+}
+
+
+/*
+ * cvs_ent_add()
+ *
+ * Add the entry <ent>
+ */
+
+int
+cvs_ent_add(CVSENTRIES *ef, struct cvs_ent *ent)
+{
+ void *tmp;
+ struct cvs_ent *entp;
+
+ if (cvs_ent_get(ef, ent->ce_name) != NULL)
+ return (-1);
+
+ entp = cvs_ent_parse(ent->ce_line);
+ if (entp == NULL) {
+ return (-1);
+ }
+
+ tmp = realloc(ef->cef_entries, (ef->cef_nbent + 1) * sizeof(entp));
+ if (tmp == NULL) {
+ cvs_log(LP_ERRNO, "failed to resize entries buffer");
+ return (-1);
+ }
+
+ ef->cef_entries = (struct cvs_ent **)tmp;
+ ef->cef_entries[ef->cef_nbent++] = entp;
+
+ return (0);
+}
+
+
+/*
+ * cvs_ent_get()
+ *
+ * Get the CVS entry from the Entries file <ef> whose 'name' portion matches
+ * <file>.
+ * Returns a pointer to the cvs entry structure on success, or NULL on failure.
+ */
+
+struct cvs_ent*
+cvs_ent_get(CVSENTRIES *ef, const char *file)
+{
+ u_int i;
+
+ for (i = 0; i < ef->cef_nbent; i++) {
+ if (strcmp(ef->cef_entries[i]->ce_name, file) == 0)
+ return ef->cef_entries[i];
+ }
+
+ return (NULL);
+}
+
+
+/*
+ * cvs_ent_next()
+ *
+ * Returns a pointer to the cvs entry structure on success, or NULL on failure.
+ */
+
+struct cvs_ent*
+cvs_ent_next(CVSENTRIES *ef)
+{
+ if (ef->cef_nid >= ef->cef_nbent)
+ return (NULL);
+
+ return (ef->cef_entries[ef->cef_nid++]);
+}
+
+
+/*
+ * cvs_ent_parse()
+ *
+ * Parse a single line from a CVS/Entries file and return a cvs_entry structure
+ * containing all the parsed information.
+ */
+
+struct cvs_ent*
+cvs_ent_parse(const char *entry)
+{
+ int i;
+ char *fields[CVS_ENTRIES_NFIELDS], *sp, *dp;
+ struct cvs_ent *entp;
+
+ entp = (struct cvs_ent *)malloc(sizeof(*entp));
+ if (entp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate CVS entry");
+ return (NULL);
+ }
+
+ entp->ce_rev = rcsnum_alloc();
+ if (entp->ce_rev == NULL) {
+ free(entp);
+ return (NULL);
+ }
+
+ entp->ce_line = strdup(entry);
+ if (entp->ce_line == NULL) {
+ free(entp);
+ return (NULL);
+ }
+
+ entp->ce_buf = strdup(entry);
+ if (entp->ce_buf == NULL) {
+ free(entp->ce_line);
+ free(entp);
+ return (NULL);
+ }
+ sp = entp->ce_buf;
+
+ if (*sp == CVS_ENTRIES_DELIM)
+ entp->ce_type = CVS_ENT_FILE;
+ else if (*sp == 'D') {
+ entp->ce_type = CVS_ENT_DIR;
+ sp++;
+ }
+ else {
+ /* unknown entry, ignore for future expansion */
+ entp->ce_type = CVS_ENT_NONE;
+ sp++;
+ }
+
+ sp++;
+ i = 0;
+ do {
+ dp = strchr(sp, CVS_ENTRIES_DELIM);
+ if (dp != NULL)
+ *(dp++) = '\0';
+ fields[i++] = sp;
+ sp = dp;
+ } while ((dp != NULL) && (i < CVS_ENTRIES_NFIELDS));
+
+ entp->ce_name = fields[0];
+
+ if (entp->ce_type == CVS_ENT_FILE) {
+ rcsnum_aton(fields[1], NULL, entp->ce_rev);
+ entp->ce_timestamp = fields[2];
+ entp->ce_opts = fields[3];
+ entp->ce_tag = fields[4];
+ }
+
+ return (entp);
+}
diff --git a/usr.bin/cvs/event.h b/usr.bin/cvs/event.h
new file mode 100644
index 00000000000..f70896b8c5e
--- /dev/null
+++ b/usr.bin/cvs/event.h
@@ -0,0 +1,69 @@
+/* $OpenBSD: event.h,v 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 EVENT_H
+#define EVENT_H
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <pwd.h>
+
+
+
+#define CVS_EVENT_ADD 1
+#define CVS_EVENT_ANNOTATE 2
+#define CVS_EVENT_CHECKOUT 3
+#define CVS_EVENT_COMMIT 4
+#define CVS_EVENT_DIFF 5
+#define CVS_EVENT_HISTORY 6
+#define CVS_EVENT_IMPORT 7
+#define CVS_EVENT_REMOVE 8
+#define CVS_EVENT_UPDATE 9
+#define CVS_EVENT_TAG 10
+
+
+struct cvs_file {
+ char *cf_path; /* path relative to the module */
+ LIST_ENTRY(cvs_file) cf_list;
+};
+
+
+
+
+struct cvs_event {
+ u_int ev_type;
+ uid_t ev_user;
+ struct timeval ev_time;
+
+ char *ev_branch;
+
+ LIST_HEAD(, cvs_file) ev_files; /* files affected by this event */
+};
+
+
+#endif /* EVENT_H */
diff --git a/usr.bin/cvs/getlog.c b/usr.bin/cvs/getlog.c
new file mode 100644
index 00000000000..e36a9fbc7eb
--- /dev/null
+++ b/usr.bin/cvs/getlog.c
@@ -0,0 +1,148 @@
+/* $OpenBSD: getlog.c,v 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 <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 "rcs.h"
+#include "sock.h"
+
+
+#define CVS_GLOG_RFONLY 0x01
+#define CVS_GLOG_HDONLY 0x02
+
+
+#define CVS_GETLOG_REVSEP "----------------------------"
+#define CVS_GETLOG_REVEND \
+ "============================================================================="
+
+static void cvs_getlog_print (const char *, RCSFILE *, u_int);
+
+
+
+extern struct cvsroot *cvs_root;
+
+
+/*
+ * cvs_getlog()
+ *
+ * Implement the `cvs log' command.
+ */
+
+int
+cvs_getlog(int argc, char **argv)
+{
+ int i, rfonly, honly, recurse;
+ u_int flags;
+ char rcspath[MAXPATHLEN];
+ RCSFILE *rfp;
+
+ rfonly = 0;
+ honly = 0;
+ recurse = 1;
+
+ while ((i = getopt(argc, argv, "d:hlRr:")) != -1) {
+ switch (i) {
+ case 'd':
+ break;
+ case 'h':
+ honly = 1;
+ break;
+ case 'l':
+ recurse = 0;
+ break;
+ case 'R':
+ rfonly = 1;
+ break;
+ case 'r':
+ break;
+ default:
+ return (EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ for (i = 0; i < argc; i++) {
+ rfp = NULL;
+#if 0
+ if (cvs_root->cr_method == CVS_METHOD_LOCCAL) {
+ snprintf(rcspath, sizeof(rcspath), "%s/%s/%s%s",
+ cvs_root->cr_dir, argv[i], RCS_FILE_EXT);
+
+ rfp = rcs_open(rcspath, RCS_MODE_READ);
+ if (rfp == NULL)
+ continue;
+ }
+#endif
+
+ cvs_getlog_print(argv[i], rfp, 0);
+ }
+
+ return (0);
+}
+
+
+static void
+cvs_getlog_print(const char *file, RCSFILE *rfp, u_int flags)
+{
+ char numbuf[64], datebuf[64], *sp;
+ struct rcs_delta *rdp;
+
+ printf("RCS file: %s\nWorking file: %s\n",
+ rfp->rf_path, file);
+ printf("Working file: %s\n", NULL);
+ printf("head: %s\nbranch:\nlocks:\naccess list:\n");
+ printf("symbolic names:\nkeyword substitutions:\n");
+ printf("total revisions: %u;\tselected revisions: %u\n", 1, 1);
+
+ printf("description:\n");
+
+ for (;;) {
+ printf(CVS_GETLOG_REVSEP "\n");
+ rcsnum_tostr(&(rdp->rd_num), numbuf, sizeof(numbuf));
+ printf("revision %s\n", numbuf);
+ printf("date: %d/%02d/%d %02d:%02d:%02d; author: %s;"
+ " state: %s; lines:",
+ 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,
+ rdp->rd_author, rdp->rd_state);
+ }
+
+ printf(CVS_GETLOG_REVEND "\n");
+
+}
diff --git a/usr.bin/cvs/hist.c b/usr.bin/cvs/hist.c
new file mode 100644
index 00000000000..71598f4cd9a
--- /dev/null
+++ b/usr.bin/cvs/hist.c
@@ -0,0 +1,300 @@
+/* $OpenBSD: hist.c,v 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/stat.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "log.h"
+#include "cvs.h"
+
+#define CVS_HIST_BUFSIZE 8192
+
+
+
+static int cvs_hist_fillbuf (CVSHIST *);
+static int cvs_hist_fmt (const struct cvs_hent *, char *, size_t);
+
+
+
+
+
+/*
+ * cvs_hist_open()
+ *
+ * Open a CVS history file.
+ * Returns the number of entries in the file on success, or -1 on error.
+ */
+
+CVSHIST*
+cvs_hist_open(const char *path)
+{
+ CVSHIST *histp;
+
+ histp = (CVSHIST *)malloc(sizeof(*histp));
+ if (histp == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate CVS history");
+ return (NULL);
+ }
+ memset(histp, 0, sizeof(*histp));
+
+ histp->chf_buf = (char *)malloc(CVS_HIST_BUFSIZE);
+ if (histp->chf_buf == NULL) {
+ cvs_log(LP_ERRNO,
+ "failed to allocate CVS history parse buffer");
+ free(histp);
+ return (NULL);
+ }
+ histp->chf_blen = CVS_HIST_BUFSIZE;
+ histp->chf_off = 0;
+
+ histp->chf_sindex = 0;
+ histp->chf_cindex = 0;
+ histp->chf_nbhent = 0;
+
+ histp->chf_fd = open(path, O_RDONLY, 0);
+ if (histp->chf_fd == -1) {
+ cvs_log(LP_ERRNO,
+ "failed to open CVS history file `%s'", path);
+ free(histp->chf_buf);
+ free(histp);
+ return (NULL);
+ }
+
+ cvs_hist_fillbuf(histp);
+
+ return (histp);
+}
+
+
+/*
+ * cvs_hist_close()
+ *
+ * Close the CVS history file previously opened by a call to cvs_hist_open()
+ */
+
+void
+cvs_hist_close(CVSHIST *histp)
+{
+ if (histp->chf_fd >= 0)
+ (void)close(histp->chf_fd);
+ free(histp->chf_buf);
+ free(histp);
+}
+
+
+/*
+ * cvs_hist_getnext()
+ *
+ * Get the next entry from the history file <histp>. Whenever using this
+ * function, it should be assumed that the return value of the previous call
+ * to cvs_hist_getnext() is now invalid.
+ * Returns the next entry from the file on success, or NULL on failure or if
+ * no entries are left.
+ */
+
+struct cvs_hent*
+cvs_hist_getnext(CVSHIST *histp)
+{
+ if (histp->chf_cindex == histp->chf_nbhent) {
+ /* no more cached entries, refill buf and parse */
+ cvs_hist_fillbuf(histp);
+ cvs_hist_parse(histp);
+ }
+ return (&(histp->chf_hent[histp->chf_cindex++]));
+}
+
+
+/*
+ * cvs_hist_append()
+ *
+ * Append a history entry to the history file <histp>. The file offset is
+ * first set to the end of the file.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_hist_append(CVSHIST *histp, struct cvs_hent *hentp)
+{
+ char hbuf[128];
+
+ if (cvs_hist_fmt(hentp, hbuf, sizeof(hbuf)) < 0) {
+ cvs_log(LP_ERR, "failed to append CVS history entry");
+ return (-1);
+ }
+
+ /* position ourself at the end */
+ if (lseek(histp->chf_fd, (off_t)0, SEEK_END) == -1) {
+ cvs_log(LP_ERRNO, "failed to seek to end of CVS history file");
+ return (-1);
+ }
+
+ if (write(histp->chf_fd, hbuf, strlen(hbuf)) == -1) {
+ cvs_log(LP_ERR, "failed to write CVS history entry to file");
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+
+/*
+ * cvs_hist_fillbuf()
+ *
+ * Fill the history file's internal buffer for future parsing.
+ */
+
+static int
+cvs_hist_fillbuf(CVSHIST *histp)
+{
+ ssize_t ret;
+
+ /* reposition ourself in case we're missing the start of a record */
+ if (lseek(histp->chf_fd, histp->chf_off, SEEK_SET) == -1) {
+ cvs_log(LP_ERRNO, "failed to seek in CVS history file");
+ return (-1);
+ }
+ ret = read(histp->chf_fd, histp->chf_buf, histp->chf_blen);
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "failed to buffer CVS history file");
+ return (-1);
+ }
+ else {
+ histp->chf_bused = (size_t)ret;
+ }
+
+ return (ret);
+}
+
+
+/*
+ * cvs_hist_parse()
+ *
+ * Parse the current contents of the internal buffer of <histp> and regenerate
+ * the buffered history entries.
+ * Returns the number of entries parsed on success, or -1 on failure.
+ */
+
+int
+cvs_hist_parse(CVSHIST *histp)
+{
+ u_int i, fld;
+ char *fields[CVS_HIST_NBFLD], *sp, *bep, *ep, *errp;
+
+ sp = histp->chf_buf;
+ bep = histp->chf_buf + histp->chf_bused - 1;
+
+ for (i = 0; i < CVS_HIST_CACHE; i++) {
+ ep = memchr(sp, '\n', bep - sp);
+ if (ep == NULL) {
+ /*
+ * No record or incomplete record left to parse,
+ * so adjust the next read offset in consequence.
+ */
+ histp->chf_off += (off_t)(sp - histp->chf_buf);
+ break;
+ }
+ else if (ep == bep) {
+ histp->chf_off += (off_t)histp->chf_bused;
+ }
+ *(ep++) = '\0';
+
+ printf("hist(%s)\n", sp);
+
+ histp->chf_hent[i].ch_event = *sp++;
+
+ /* split the record in fields */
+ fields[0] = sp;
+
+ fld = 1;
+ while (sp < ep) {
+ if (*sp == '|') {
+ *sp = '\0';
+ fields[fld++] = sp + 1;
+ }
+ if (fld == CVS_HIST_NBFLD)
+ break;
+ sp++;
+ }
+#if 0
+ for (fld = 0; fld < CVS_HIST_NBFLD; fld++)
+ printf("fields[%u] = `%s'\n", fld, fields[fld]);
+#endif
+
+ histp->chf_hent[i].ch_date = (time_t)strtol(fields[0],
+ &errp, 16);
+ if (*errp != '\0') {
+ cvs_log(LP_ERR,
+ "parse error in date field of CVS history entry");
+ continue;
+ }
+
+ histp->chf_hent[i].ch_user = fields[1];
+ histp->chf_hent[i].ch_curdir = fields[2];
+ histp->chf_hent[i].ch_repo = fields[3];
+ histp->chf_hent[i].ch_rev = rcsnum_alloc();
+ rcsnum_aton(fields[4], NULL, histp->chf_hent[i].ch_rev);
+ histp->chf_hent[i].ch_arg = fields[5];
+ sp = ep;
+ }
+
+ /* update indexes */
+ histp->chf_sindex += histp->chf_nbhent;
+ histp->chf_nbhent = i;
+ histp->chf_cindex = 0;
+
+
+ return (i);
+}
+
+
+/*
+ * cvs_hist_fmt()
+ *
+ * Format the contents of the CVS history entry <ent> into the format used in
+ * the CVS `history' file, and store the resulting string in <buf>, which is
+ * of size <blen>.
+ */
+
+static int
+cvs_hist_fmt(const struct cvs_hent *ent, char *buf, size_t blen)
+{
+ char numbuf[64];
+
+ if (rcsnum_tostr(ent->ch_rev, numbuf, sizeof(numbuf)) == NULL) {
+ return (-1);
+ }
+
+ return snprintf(buf, blen, "%c%8x|%s|%s|%s|%s|%s",
+ ent->ch_event, ent->ch_date, ent->ch_user, ent->ch_curdir,
+ ent->ch_repo, numbuf, ent->ch_arg);
+}
diff --git a/usr.bin/cvs/history.c b/usr.bin/cvs/history.c
new file mode 100644
index 00000000000..b291869b7d6
--- /dev/null
+++ b/usr.bin/cvs/history.c
@@ -0,0 +1,205 @@
+/* $OpenBSD: history.c,v 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/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 "rcs.h"
+#include "log.h"
+
+#define CVS_HISTORY_MAXMOD 16
+
+/* history flags */
+#define CVS_HF_A 0x01
+#define CVS_HF_C 0x02
+#define CVS_HF_E 0x04
+#define CVS_HF_L 0x08
+#define CVS_HF_M 0x10
+#define CVS_HF_O 0x20
+#define CVS_HF_T 0x40
+#define CVS_HF_W 0x80
+
+#define CVS_HF_EXCL (CVS_HF_C|CVS_HF_E|CVS_HF_M|CVS_HF_O|CVS_HF_T|CVS_HF_X)
+
+static void cvs_history_print (struct cvs_hent *);
+
+
+extern char *__progname;
+
+extern struct cvsroot *cvs_root;
+
+
+/*
+ * cvs_history()
+ *
+ * Handle the `cvs history' command.
+ */
+
+
+int
+cvs_history(int argc, char **argv)
+{
+ int ch, flags;
+ u_int nbmod, rep;
+ char *user, *zone, *tag, *cp;
+ char *modules[CVS_HISTORY_MAXMOD], histpath[MAXPATHLEN];
+ struct cvs_hent *hent;
+ CVSHIST *hp;
+
+ tag = NULL;
+ user = NULL;
+ zone = "+0000";
+ nbmod = 0;
+ flags = 0;
+ rep = 0;
+
+ while ((ch = getopt(argc, argv, "acelm:oTt:u:wx:z:")) != -1) {
+ switch (ch) {
+ case 'a':
+ flags |= CVS_HF_A;
+ break;
+ case 'c':
+ rep++;
+ flags |= CVS_HF_C;
+ break;
+ case 'e':
+ rep++;
+ flags |= CVS_HF_E;
+ break;
+ case 'l':
+ flags |= CVS_HF_L;
+ break;
+ case 'm':
+ rep++;
+ flags |= CVS_HF_M;
+ if (nbmod == CVS_HISTORY_MAXMOD) {
+ cvs_log(LP_ERR, "too many `-m' options");
+ return (EX_USAGE);
+ }
+ modules[nbmod++] = optarg;
+ break;
+ case 'o':
+ rep++;
+ flags |= CVS_HF_O;
+ break;
+ case 'T':
+ rep++;
+ flags |= CVS_HF_T;
+ break;
+ case 't':
+ tag = optarg;
+ break;
+ case 'u':
+ user = optarg;
+ break;
+ case 'w':
+ flags |= CVS_HF_W;
+ break;
+ case 'x':
+ rep++;
+ for (cp = optarg; *cp != '\0'; cp++) {
+ }
+ break;
+ case 'z':
+ zone = optarg;
+ break;
+ default:
+ return (EX_USAGE);
+ }
+ }
+
+ if (rep > 1) {
+ cvs_log(LP_ERR,
+ "Only one report type allowed from: \"-Tcomxe\"");
+ return (EX_USAGE);
+ }
+ else if (rep == 0)
+ flags |= CVS_HF_O; /* use -o as default */
+
+ if (cvs_root->cr_method == CVS_METHOD_LOCAL) {
+ snprintf(histpath, sizeof(histpath), "%s/%s", cvs_root->cr_dir,
+ CVS_PATH_HISTORY);
+ hp = cvs_hist_open(histpath);
+ if (hp == NULL) {
+ return (EX_UNAVAILABLE);
+ }
+
+ while ((hent = cvs_hist_getnext(hp)) != NULL) {
+ cvs_history_print(hent);
+ }
+ cvs_hist_close(hp);
+ }
+ else {
+ if (flags & CVS_HF_C)
+ cvs_client_sendarg("-c", 0);
+
+ if (flags & CVS_HF_O)
+ cvs_client_sendarg("-o", 0);
+
+ if (tag != NULL) {
+ cvs_client_sendarg("-t", 0);
+ cvs_client_sendarg(tag, 0);
+ }
+ if (user != NULL) {
+ cvs_client_sendarg("-u", 0);
+ cvs_client_sendarg(user, 0);
+ }
+
+
+ cvs_client_sendarg("-z", 0);
+ cvs_client_sendarg(zone, 0);
+
+ cvs_client_sendreq(CVS_REQ_HISTORY, NULL, 1);
+ }
+
+ return (0);
+}
+
+
+static void
+cvs_history_print(struct cvs_hent *hent)
+{
+ struct tm etime;
+
+ if (localtime_r(&(hent->ch_date), &etime) == NULL) {
+ cvs_log(LP_ERROR, "failed to convert timestamp to structure");
+ return;
+ }
+
+ printf("%c %4d-%02d-%02d %02d:%02d +%04d %-16s %-16s\n",
+ hent->ch_event, etime.tm_year + 1900, etime.tm_mon + 1,
+ etime.tm_mday, etime.tm_hour, etime.tm_min,
+ 0, hent->ch_user, hent->ch_repo);
+}
diff --git a/usr.bin/cvs/init.c b/usr.bin/cvs/init.c
new file mode 100644
index 00000000000..f823ce77dbc
--- /dev/null
+++ b/usr.bin/cvs/init.c
@@ -0,0 +1,127 @@
+/* $OpenBSD: init.c,v 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/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 "rcs.h"
+#include "log.h"
+
+extern struct cvsroot *cvs_root;
+
+
+extern char cvs_loginfo_data[];
+
+
+#define CFT_FILE 1
+#define CFT_DIR 2
+
+
+struct cvsroot_file {
+ char *cf_path; /* path relative to CVS root directory */
+ u_int cf_type;
+ mode_t cf_mode;
+} cvsroot_files[] = {
+ { CVS_PATH_ROOT, CFT_DIR, (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) },
+
+ { CVS_PATH_COMMITINFO, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_CONFIG, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_CVSIGNORE, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_CVSWRAPPERS, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_EDITINFO, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_HISTORY, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_LOGINFO, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_MODULES, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_NOTIFY, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_RCSINFO, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_TAGINFO, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+ { CVS_PATH_VERIFYMSG, CFT_FILE, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) },
+};
+
+
+
+
+
+int
+cvs_init(int argc, char **argv)
+{
+ int fd;
+ u_int i;
+ char path[MAXPATHLEN];
+ RCSFILE *rfp;
+ struct stat st;
+
+ if (argc != 1)
+ return (EX_USAGE);
+
+ for (i = 0; i < sizeof(cvsroot_files)/sizeof(cvsroot_files[i]); i++) {
+ snprintf(path, sizeof(path), "%s/%s", cvs_root->cr_dir,
+ cvsroot_files[i].cf_path);
+
+ if (cvsroot_files[i].cf_type == CFT_DIR) {
+ if (mkdir(path, cvsroot_files[i].cf_mode) == -1) {
+ cvs_log(LP_ERRNO, "failed to create `%s'",
+ path);
+ return (EX_CANTCREAT);
+ }
+ }
+ else if (cvsroot_files[i].cf_type == CFT_FILE) {
+ fd = open(path, O_WRONLY|O_CREAT|O_EXCL,
+ cvsroot_files[i].cf_mode);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to create `%s'",
+ path);
+ return (EX_CANTCREAT);
+ }
+
+ (void)close(fd);
+
+ strlcat(path, RCS_FILE_EXT, sizeof(path));
+ rfp = rcs_open(path, RCS_MODE_WRITE);
+ if (rfp == NULL) {
+ return (EX_CANTCREAT);
+ }
+
+ if (rcs_write(rfp) < 0) {
+ rcs_close(rfp);
+ return (EX_CANTCREAT);
+ }
+
+ rcs_close(rfp);
+ }
+ }
+
+ return (0);
+}
diff --git a/usr.bin/cvs/lock.c b/usr.bin/cvs/lock.c
new file mode 100644
index 00000000000..3dddc4c0508
--- /dev/null
+++ b/usr.bin/cvs/lock.c
@@ -0,0 +1,65 @@
+/* $OpenBSD: lock.c,v 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 <stdlib.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+
+#include "cvs.h"
+
+
+
+
+
+int
+cvs_lock(const char *path)
+{
+ int fd;
+
+ fd = open(path, O_WRONLY|O_CREAT|O_EXCL);
+ if (fd == -1) {
+ warn("failed to open lock file `%s'", path);
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+int
+cvs_unlock(const char *path)
+{
+
+
+
+}
diff --git a/usr.bin/cvs/log.c b/usr.bin/cvs/log.c
new file mode 100644
index 00000000000..55d1be069a1
--- /dev/null
+++ b/usr.bin/cvs/log.c
@@ -0,0 +1,254 @@
+/* $OpenBSD: log.c,v 1.1 2004/07/13 22:02:40 jfb Exp $ */
+/*
+ * Copyright (c) 2004 Jean-Francois Brousseau <jfb@fugusec.net>
+ * 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 <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+#include "log.h"
+
+extern char *__progname;
+
+static char *cvs_log_levels[] = {
+ "debug",
+ "info",
+ "notice",
+ "warning",
+ "error",
+ "alert",
+ "error"
+};
+
+
+static int cvs_slpriomap[] = {
+ LOG_DEBUG,
+ LOG_INFO,
+ LOG_NOTICE,
+ LOG_WARNING,
+ LOG_ERR,
+ LOG_ALERT,
+ LOG_ERR,
+};
+
+
+
+static u_int cvs_log_dest = LD_STD;
+static u_int cvs_log_flags = 0;
+
+static u_int cvs_log_filters[LP_MAX + 1];
+#define NB_FILTERS sizeof(cvs_log_filters)/sizeof(cvs_log_filters[0])
+
+
+static struct syslog_data cvs_sl;
+
+
+/*
+ * cvs_log_init()
+ *
+ * Initialize the logging facility of the server.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_log_init(u_int dest, u_int flags)
+{
+ int slopt;
+
+ cvs_log_dest = dest;
+ cvs_log_flags = flags;
+
+ /* by default, filter only LP_DEBUG and LP_INFO levels */
+ memset(cvs_log_filters, 0, sizeof(cvs_log_filters));
+ cvs_log_filters[LP_DEBUG] = 1;
+ cvs_log_filters[LP_INFO] = 1;
+
+ if (dest & LD_SYSLOG) {
+ slopt = 0;
+
+ if (dest & LD_CONS)
+ slopt |= LOG_CONS;
+ if (flags & LF_PID)
+ slopt |= LOG_PID;
+
+ openlog_r(__progname, slopt, LOG_DAEMON, &cvs_sl);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_log_cleanup()
+ *
+ * Cleanup the logging facility.
+ */
+
+void
+cvs_log_cleanup(void)
+{
+ closelog_r(&cvs_sl);
+
+}
+
+
+/*
+ * cvs_log_filter()
+ *
+ * Apply or remove filters on the logging facility. The exact operation is
+ * specified by the <how> and <level> arguments. The <how> arguments tells
+ * how the filters will be affected, and <level> gives the log levels that
+ * will be affected by the change.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_log_filter(u_int how, u_int level)
+{
+ u_int i;
+
+ if ((level > LP_MAX) && (level != LP_ALL)) {
+ cvs_log(LP_ERR, "invalid log level for filter");
+ return (-1);
+ }
+
+ switch (how) {
+ case LP_FILTER_SET:
+ if (level == LP_ALL)
+ for (i = 0; i < NB_FILTERS; i++)
+ cvs_log_filters[i] = 1;
+ else
+ cvs_log_filters[level] = 1;
+ break;
+ case LP_FILTER_UNSET:
+ if (level == LP_ALL)
+ for (i = 0; i < NB_FILTERS; i++)
+ cvs_log_filters[i] = 0;
+ else
+ cvs_log_filters[level] = 0;
+ break;
+ case LP_FILTER_TOGGLE:
+ if (level == LP_ALL)
+ for (i = 0; i < NB_FILTERS; i++)
+ cvs_log_filters[i] =
+ (cvs_log_filters[i] == 0) ? 1 : 0;
+ else
+ cvs_log_filters[level] =
+ (cvs_log_filters[level] == 0) ? 1 : 0;
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_log()
+ *
+ * Log the format-string message
+ * The <fmt> argument should not have a terminating newline, as this is taken
+ * care of by the logging facility.
+ */
+
+int
+cvs_log(u_int level, const char *fmt, ...)
+{
+ int ret;
+ va_list vap;
+
+ va_start(vap, fmt);
+ ret = cvs_vlog(level, fmt, vap);
+ va_end(vap);
+
+ return (ret);
+}
+
+
+/*
+ * cvs_vlog()
+ *
+ * The <fmt> argument should not have a terminating newline, as this is taken
+ * care of by the logging facility.
+ */
+
+int
+cvs_vlog(u_int level, const char *fmt, va_list vap)
+{
+ int ecp;
+ pid_t pid;
+ char prefix[64], buf[1024], ebuf[32];
+ FILE *out;
+
+ ecp = 0;
+
+ if (level > LP_MAX) {
+ return (-1);
+ }
+
+ /* apply any filters */
+ if (cvs_log_filters[level] == 1)
+ return (0);
+
+ if (level == LP_ERRNO)
+ ecp = errno;
+
+ strlcpy(prefix, __progname, sizeof(prefix));
+ if (cvs_log_flags & LF_PID) {
+ snprintf(buf, sizeof(buf), "[%d]", (int)getpid());
+ strlcat(prefix, buf, sizeof(prefix));
+ }
+
+ vsnprintf(buf, sizeof(buf), fmt, vap);
+ if (level == LP_ERRNO) {
+ snprintf(ebuf, sizeof(ebuf), ": %s", strerror(errno));
+ strlcat(buf, ebuf, sizeof(buf));
+ }
+
+ if (cvs_log_dest & LD_STD) {
+ if (level <= LP_NOTICE)
+ out = stdout;
+ else
+ out = stderr;
+
+ fprintf(out, "%s: %s\n", prefix, buf);
+ }
+
+ if (cvs_log_dest & LD_SYSLOG)
+ syslog_r(cvs_slpriomap[level], &cvs_sl, "%s", buf);
+
+ /* preserve it just in case we changed it? */
+ if (level == LP_ERRNO)
+ errno = ecp;
+
+ return (0);
+}
diff --git a/usr.bin/cvs/log.h b/usr.bin/cvs/log.h
new file mode 100644
index 00000000000..a33c6d7c921
--- /dev/null
+++ b/usr.bin/cvs/log.h
@@ -0,0 +1,71 @@
+/* $OpenBSD: log.h,v 1.1 2004/07/13 22:02:40 jfb Exp $ */
+/*
+ * Copyright (c) 2004 Jean-Francois Brousseau <jfb@fugusec.net>
+ * 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 LOG_H
+#define LOG_H
+
+#include <sys/types.h>
+
+#include <stdarg.h>
+
+/* log destinations */
+#define LD_STD 0x01
+#define LD_SYSLOG 0x02
+#define LD_CONS 0x04
+
+#define LD_ALL (LD_STD|LD_SYSLOG|LD_CONS)
+
+/* log flags */
+#define LF_PID 0x01 /* include PID in messages */
+
+
+
+/* log priority levels */
+#define LP_DEBUG 0
+#define LP_INFO 1
+#define LP_NOTICE 2
+#define LP_WARNING 3
+#define LP_WARN LP_WARNING
+#define LP_ERROR 4
+#define LP_ERR LP_ERROR
+#define LP_ALERT 5
+#define LP_ERRNO 6
+
+#define LP_MAX 6
+#define LP_ALL 255
+
+/* filtering methods */
+#define LP_FILTER_SET 0 /* set a filter */
+#define LP_FILTER_UNSET 1 /* remove a filter */
+#define LP_FILTER_TOGGLE 2
+
+int cvs_log_init (u_int, u_int);
+void cvs_log_cleanup (void);
+int cvs_log_filter (u_int, u_int);
+int cvs_log (u_int, const char *, ...);
+int cvs_vlog (u_int, const char *, va_list);
+
+#endif /* LOG_H */
diff --git a/usr.bin/cvs/msg.c b/usr.bin/cvs/msg.c
new file mode 100644
index 00000000000..6e9aadd82cf
--- /dev/null
+++ b/usr.bin/cvs/msg.c
@@ -0,0 +1,121 @@
+/* $OpenBSD: msg.c,v 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/uio.h>
+
+#include <pwd.h>
+#include <grp.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "log.h"
+#include "cvsd.h"
+
+
+
+
+
+/*
+ * cvsd_sendmsg()
+ *
+ * Send a message of type <type> along with the first <len> bytes of data
+ * from <data> (which can be up to CVSD_MSG_MAXLEN bytes) on the descriptor
+ * <fd>.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvsd_sendmsg(int fd, u_int type, const void *data, size_t len)
+{
+ struct iovec iov[2];
+ struct cvsd_msg msg;
+
+ if (len > CVSD_MSG_MAXLEN) {
+ cvs_log(LP_ERR, "message too large");
+ return (-1);
+ }
+
+ msg.cm_type = type;
+ msg.cm_len = len;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = sizeof(msg);
+ iov[1].iov_base = (void *)data;
+ iov[1].iov_len = len;
+
+ if (writev(fd, iov, 2) == -1) {
+ cvs_log(LP_ERRNO, "failed to send message");
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvsd_recvmsg()
+ *
+ * Read a message from the file descriptor <fd> and store the message data
+ * in the <dst> buffer. The <len> parameter should contain the maximum
+ * length of data that can be stored in <dst>, and will contain the actual
+ * size of data stored on return. The message type is stored in <type>.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvsd_recvmsg(int fd, u_int *type, void *dst, size_t *len)
+{
+ ssize_t ret;
+ struct cvsd_msg msg;
+
+ if (read(fd, &msg, sizeof(msg)) == -1) {
+ cvs_log(LP_ERRNO, "failed to read message header");
+ return (-1);
+ }
+
+ if (*len < msg.cm_len) {
+ cvs_log(LP_ERR, "buffer size too small for message data");
+ return (-1);
+ }
+
+ ret = read(fd, dst, msg.cm_len);
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "failed to read message");
+ return (-1);
+ }
+ else if (ret == 0) {
+ }
+
+ *type = msg.cm_type;
+
+ return (0);
+}
diff --git a/usr.bin/cvs/proto.c b/usr.bin/cvs/proto.c
new file mode 100644
index 00000000000..e5e23cdc6e2
--- /dev/null
+++ b/usr.bin/cvs/proto.c
@@ -0,0 +1,821 @@
+/* $OpenBSD: proto.c,v 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.
+ */
+/*
+ * CVS client/server protocol
+ * ==========================
+ *
+ * The following code implements the CVS client/server protocol, which is
+ * documented at the following URL:
+ * http://www.loria.fr/~molli/cvs/doc/cvsclient_toc.html
+ *
+ * The protocol is split up into two parts; the first part is the client side
+ * of things and is composed of all the response handlers, which are all named
+ * with a prefix of "cvs_resp_". The second part is the set of request
+ * handlers used by the server. These handlers process the request and
+ * generate the appropriate response to send back. The prefix for request
+ * handlers is "cvs_req_".
+ *
+ */
+
+#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 "buf.h"
+#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;
+
+
+
+/*
+ * Local and remote directory used by the `Directory' request.
+ */
+char cvs_ldir[MAXPATHLEN];
+char cvs_rdir[MAXPATHLEN];
+
+
+
+char *cvs_fcksum = NULL;
+
+mode_t cvs_lastmode = 0;
+
+
+static int cvs_resp_validreq (int, char *);
+static int cvs_resp_cksum (int, char *);
+static int cvs_resp_m (int, char *);
+static int cvs_resp_ok (int, char *);
+static int cvs_resp_error (int, char *);
+static int cvs_resp_statdir (int, char *);
+static int cvs_resp_newentry (int, char *);
+static int cvs_resp_updated (int, char *);
+static int cvs_resp_removed (int, char *);
+static int cvs_resp_mode (int, char *);
+
+
+
+
+struct cvs_req {
+ int req_id;
+ char req_str[32];
+ u_int req_flags;
+ int (*req_hdlr)(int, char *);
+} cvs_requests[] = {
+ { CVS_REQ_DIRECTORY, "Directory", 0, NULL },
+ { CVS_REQ_MAXDOTDOT, "Max-dotdot", 0, NULL },
+ { CVS_REQ_STATICDIR, "Static-directory", 0, NULL },
+ { CVS_REQ_STICKY, "Sticky", 0, NULL },
+ { CVS_REQ_ENTRY, "Entry", 0, NULL },
+ { CVS_REQ_ENTRYEXTRA, "EntryExtra", 0, NULL },
+ { CVS_REQ_CHECKINTIME, "Checkin-time", 0, NULL },
+ { CVS_REQ_MODIFIED, "Modified", 0, NULL },
+ { CVS_REQ_ISMODIFIED, "Is-modified", 0, NULL },
+ { CVS_REQ_UNCHANGED, "Unchanged", 0, NULL },
+ { CVS_REQ_USEUNCHANGED, "UseUnchanged", 0, NULL },
+ { CVS_REQ_NOTIFY, "Notify", 0, NULL },
+ { CVS_REQ_NOTIFYUSER, "NotifyUser", 0, NULL },
+ { CVS_REQ_QUESTIONABLE, "Questionable", 0, NULL },
+ { CVS_REQ_CASE, "Case", 0, NULL },
+ { CVS_REQ_UTF8, "Utf8", 0, NULL },
+ { CVS_REQ_ARGUMENT, "Argument", 0, NULL },
+ { CVS_REQ_ARGUMENTX, "Argumentx", 0, NULL },
+ { CVS_REQ_GLOBALOPT, "Global_option", 0, NULL },
+ { CVS_REQ_GZIPSTREAM, "Gzip-stream", 0, NULL },
+ { CVS_REQ_READCVSRC2, "read-cvsrc2", 0, NULL },
+ { CVS_REQ_READWRAP, "read-cvswrappers", 0, NULL },
+ { CVS_REQ_READIGNORE, "read-cvsignore", 0, NULL },
+ { CVS_REQ_ERRIFREADER, "Error-If-Reader", 0, NULL },
+ { CVS_REQ_VALIDRCSOPT, "Valid-RcsOptions", 0, NULL },
+ { CVS_REQ_SET, "Set", 0, NULL },
+ { CVS_REQ_XPANDMOD, "expand-modules", 0, NULL },
+ { CVS_REQ_LOG, "log", 0, NULL },
+ { CVS_REQ_CO, "co", 0, NULL },
+ { CVS_REQ_EXPORT, "export", 0, NULL },
+ { CVS_REQ_RANNOTATE, "rannotate", 0, NULL },
+ { CVS_REQ_RDIFF, "rdiff", 0, NULL },
+ { CVS_REQ_RLOG, "rlog", 0, NULL },
+ { CVS_REQ_RTAG, "rtag", 0, NULL },
+ { CVS_REQ_INIT, "init", 0, NULL },
+ { CVS_REQ_UPDATE, "update", 0, NULL },
+ { CVS_REQ_HISTORY, "history", 0, NULL },
+ { CVS_REQ_IMPORT, "import", 0, NULL },
+ { CVS_REQ_ADD, "add", 0, NULL },
+ { CVS_REQ_REMOVE, "remove", 0, NULL },
+ { CVS_REQ_RELEASE, "release", 0, NULL },
+ { CVS_REQ_ROOT, "Root", 0, NULL },
+ { CVS_REQ_VALIDRESP, "Valid-responses", 0, NULL },
+ { CVS_REQ_VALIDREQ, "valid-requests", 0, NULL },
+ { CVS_REQ_VERSION, "version", 0, NULL },
+ { CVS_REQ_NOOP, "noop", 0, NULL },
+ { CVS_REQ_DIFF, "diff", 0, NULL },
+};
+
+
+struct cvs_resp {
+ u_int resp_id;
+ char resp_str[32];
+ int (*resp_hdlr)(int, char *);
+} cvs_responses[] = {
+ { CVS_RESP_OK, "ok", cvs_resp_ok },
+ { CVS_RESP_ERROR, "error", cvs_resp_error },
+ { CVS_RESP_VALIDREQ, "Valid-requests", cvs_resp_validreq },
+ { CVS_RESP_M, "M", cvs_resp_m },
+ { CVS_RESP_MBINARY, "Mbinary", cvs_resp_m },
+ { CVS_RESP_MT, "MT", cvs_resp_m },
+ { CVS_RESP_E, "E", cvs_resp_m },
+ { CVS_RESP_F, "F", cvs_resp_m },
+ { CVS_RESP_CREATED, "Created", cvs_resp_updated },
+ { CVS_RESP_UPDATED, "Updated", cvs_resp_updated },
+ { CVS_RESP_UPDEXIST, "Update-existing", cvs_resp_updated },
+ { CVS_RESP_REMOVED, "Removed", cvs_resp_removed },
+ { CVS_RESP_MERGED, "Merged", NULL },
+ { CVS_RESP_CKSUM, "Checksum", cvs_resp_cksum },
+ { CVS_RESP_CLRSTATDIR, "Clear-static-directory", cvs_resp_statdir },
+ { CVS_RESP_SETSTATDIR, "Set-static-directory", cvs_resp_statdir },
+ { CVS_RESP_NEWENTRY, "New-entry", cvs_resp_newentry },
+ { CVS_RESP_CHECKEDIN, "Checked-in", cvs_resp_newentry },
+ { CVS_RESP_MODE, "Mode", cvs_resp_mode },
+};
+
+
+#define CVS_NBREQ (sizeof(cvs_requests)/sizeof(cvs_requests[0]))
+#define CVS_NBRESP (sizeof(cvs_responses)/sizeof(cvs_responses[0]))
+
+/* mask of requets supported by server */
+static u_char cvs_server_validreq[CVS_REQ_MAX + 1];
+
+
+/*
+ * cvs_req_getbyid()
+ *
+ */
+
+const char*
+cvs_req_getbyid(int reqid)
+{
+ u_int i;
+
+ for (i = 0; i < CVS_NBREQ; i++)
+ if (cvs_requests[i].req_id == reqid)
+ return (cvs_requests[i].req_str);
+ return (NULL);
+}
+
+
+/*
+ * cvs_req_getbyname()
+ */
+
+int
+cvs_req_getbyname(const char *rname)
+{
+ u_int i;
+
+ for (i = 0; i < CVS_NBREQ; i++)
+ if (strcmp(cvs_requests[i].req_str, rname) == 0)
+ return (cvs_requests[i].req_id);
+
+ return (-1);
+}
+
+
+/*
+ * cvs_req_getvalid()
+ *
+ * Build a space-separated list of all the requests that this protocol
+ * implementation supports.
+ */
+
+char*
+cvs_req_getvalid(void)
+{
+ u_int i;
+ size_t len;
+ char *vrstr;
+ BUF *buf;
+
+ buf = cvs_buf_alloc(512, BUF_AUTOEXT);
+ if (buf == NULL)
+ return (NULL);
+
+ cvs_buf_set(buf, cvs_requests[0].req_str,
+ strlen(cvs_requests[0].req_str), 0);
+
+ for (i = 1; i < CVS_NBREQ; i++) {
+ if ((cvs_buf_putc(buf, ' ') < 0) ||
+ (cvs_buf_append(buf, cvs_requests[i].req_str,
+ strlen(cvs_requests[i].req_str)) < 0)) {
+ cvs_buf_free(buf);
+ return (NULL);
+ }
+ }
+
+ /* NUL-terminate */
+ if (cvs_buf_putc(buf, '\0') < 0) {
+ cvs_buf_free(buf);
+ return (NULL);
+ }
+
+ len = cvs_buf_size(buf);
+ vrstr = (char *)malloc(len);
+
+ cvs_buf_copy(buf, 0, vrstr, len);
+
+ cvs_buf_free(buf);
+
+ return (vrstr);
+}
+
+
+/*
+ * cvs_req_handle()
+ *
+ * Generic request handler dispatcher.
+ */
+
+int
+cvs_req_handle(char *line)
+{
+ return (0);
+}
+
+
+/*
+ * cvs_resp_getbyid()
+ *
+ */
+
+const char*
+cvs_resp_getbyid(int respid)
+{
+ u_int i;
+
+ for (i = 0; i < CVS_NBREQ; i++)
+ if (cvs_responses[i].resp_id == respid)
+ return (cvs_responses[i].resp_str);
+ return (NULL);
+}
+
+
+/*
+ * cvs_resp_getbyname()
+ */
+
+int
+cvs_resp_getbyname(const char *rname)
+{
+ u_int i;
+
+ for (i = 0; i < CVS_NBREQ; i++)
+ if (strcmp(cvs_responses[i].resp_str, rname) == 0)
+ return (cvs_responses[i].resp_id);
+
+ return (-1);
+}
+
+
+/*
+ * cvs_resp_getvalid()
+ *
+ * Build a space-separated list of all the responses that this protocol
+ * implementation supports.
+ */
+
+char*
+cvs_resp_getvalid(void)
+{
+ u_int i;
+ size_t len;
+ char *vrstr;
+ BUF *buf;
+
+ buf = cvs_buf_alloc(512, BUF_AUTOEXT);
+ if (buf == NULL)
+ return (NULL);
+
+ cvs_buf_set(buf, cvs_responses[0].resp_str,
+ strlen(cvs_responses[0].resp_str), 0);
+
+ for (i = 1; i < CVS_NBRESP; i++) {
+ if ((cvs_buf_putc(buf, ' ') < 0) ||
+ (cvs_buf_append(buf, cvs_responses[i].resp_str,
+ strlen(cvs_responses[i].resp_str)) < 0)) {
+ cvs_buf_free(buf);
+ return (NULL);
+ }
+ }
+
+ /* NUL-terminate */
+ if (cvs_buf_putc(buf, '\0') < 0) {
+ cvs_buf_free(buf);
+ return (NULL);
+ }
+
+ len = cvs_buf_size(buf);
+ vrstr = (char *)malloc(len);
+
+ cvs_buf_copy(buf, 0, vrstr, len);
+ cvs_buf_free(buf);
+
+ return (vrstr);
+}
+
+
+/*
+ * cvs_resp_handle()
+ *
+ * Generic response handler dispatcher. The handler expects the first line
+ * of the command as single argument.
+ * Returns the return value of the command on success, or -1 on failure.
+ */
+
+int
+cvs_resp_handle(char *line)
+{
+ u_int i;
+ size_t len;
+ char *cp, *cmd;
+
+ cmd = line;
+
+ cp = strchr(cmd, ' ');
+ if (cp != NULL)
+ *(cp++) = '\0';
+
+ for (i = 0; i < CVS_NBRESP; i++) {
+ if (strcmp(cvs_responses[i].resp_str, cmd) == 0)
+ return (*cvs_responses[i].resp_hdlr)
+ (cvs_responses[i].resp_id, cp);
+ }
+
+ /* unhandled */
+ return (-1);
+}
+
+
+/*
+ * 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)
+{
+ /* first things first, get list of valid requests from server */
+ 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_resp_validreq()
+ *
+ * Handler for the `Valid-requests' response. The list of valid requests is
+ * split on spaces and each request's entry in the valid request array is set
+ * to 1 to indicate the validity.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+static int
+cvs_resp_validreq(int type, char *line)
+{
+ int i;
+ char *sp, *ep;
+
+ /* parse the requests */
+ sp = line;
+ do {
+ ep = strchr(sp, ' ');
+ if (ep != NULL)
+ *ep = '\0';
+
+ i = cvs_req_getbyname(sp);
+ if (i != -1)
+ cvs_server_validreq[i] = 1;
+
+ if (ep != NULL)
+ sp = ep + 1;
+ } while (ep != NULL);
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_m()
+ *
+ * Handler for the `M', `F' and `E' responses.
+ */
+
+static int
+cvs_resp_m(int type, char *line)
+{
+ FILE *stream;
+
+ stream = NULL;
+
+ switch (type) {
+ case CVS_RESP_F:
+ fflush(stderr);
+ return (0);
+ case CVS_RESP_M:
+ stream = stdout;
+ break;
+ case CVS_RESP_E:
+ stream = stderr;
+ break;
+ case CVS_RESP_MT:
+ case CVS_RESP_MBINARY:
+ cvs_log(LP_WARN, "MT and Mbinary not supported in client yet");
+ break;
+ }
+
+ fputs(line, stream);
+ fputc('\n', stream);
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_ok()
+ *
+ * Handler for the `ok' response. This handler's job is to
+ */
+
+static int
+cvs_resp_ok(int type, char *line)
+{
+ return (1);
+}
+
+
+/*
+ * cvs_resp_error()
+ *
+ * Handler for the `error' response. This handler's job is to
+ */
+
+static int
+cvs_resp_error(int type, char *line)
+{
+ return (1);
+}
+
+
+/*
+ * cvs_resp_statdir()
+ *
+ * Handler for the `Clear-static-directory' and `Set-static-directory'
+ * responses.
+ */
+
+static int
+cvs_resp_statdir(int type, char *line)
+{
+ int fd;
+ char statpath[MAXPATHLEN];
+
+ snprintf(statpath, sizeof(statpath), "%s/%s", line,
+ CVS_PATH_STATICENTRIES);
+
+ if ((type == CVS_RESP_CLRSTATDIR) &&
+ (unlink(statpath) == -1)) {
+ cvs_log(LP_ERRNO, "failed to unlink %s file",
+ CVS_PATH_STATICENTRIES);
+ return (-1);
+ }
+ else if (type == CVS_RESP_SETSTATDIR) {
+ fd = open(statpath, O_CREAT|O_TRUNC|O_WRONLY, 0400);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to create %s file",
+ CVS_PATH_STATICENTRIES);
+ return (-1);
+ }
+ (void)close(fd);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_newentry()
+ *
+ * Handler for the `New-entry' response and `Checked-in' responses.
+ */
+
+static int
+cvs_resp_newentry(int type, char *line)
+{
+ char entbuf[128], path[MAXPATHLEN];
+ struct cvs_ent *entp;
+ CVSENTRIES *entfile;
+
+ if (cvs_splitpath(line, entbuf, sizeof(entbuf), NULL, 0) < 0)
+ return (-1);
+
+ snprintf(path, sizeof(path), "%s/" CVS_PATH_ENTRIES, entbuf);
+
+ /* get the remote path */
+ cvs_client_getln(entbuf, sizeof(entbuf));
+
+ /* get the new Entries line */
+ if (cvs_client_getln(entbuf, sizeof(entbuf)) < 0)
+ return (-1);
+
+ entp = cvs_ent_parse(entbuf);
+ if (entp == NULL)
+ return (-1);
+
+ entfile = cvs_ent_open(path);
+ if (entfile == NULL)
+ return (-1);
+
+ cvs_ent_add(entfile, entp);
+
+ cvs_ent_close(entfile);
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_cksum()
+ *
+ * Handler for the `Checksum' response. We store the checksum received for
+ * the next file in a dynamically-allocated buffer pointed to by <cvs_fcksum>.
+ * Upon next file reception, the handler checks to see if there is a stored
+ * checksum.
+ * The file handler must make sure that the checksums match and free the
+ * checksum buffer once it's done to indicate there is no further checksum.
+ */
+
+static int
+cvs_resp_cksum(int type, char *line)
+{
+ if (cvs_fcksum != NULL) {
+ cvs_log(LP_WARN, "unused checksum");
+ free(cvs_fcksum);
+ }
+
+ cvs_fcksum = strdup(line);
+ if (cvs_fcksum == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy checksum string");
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_updated()
+ *
+ * Handler for the `Updated' response.
+ */
+
+static int
+cvs_resp_updated(int type, char *line)
+{
+ char cksum_buf[CVS_CKSUM_LEN];
+
+ if (type == CVS_RESP_CREATED) {
+ }
+ else if (type == CVS_RESP_UPDEXIST) {
+ }
+ else if (type == CVS_RESP_UPDATED) {
+ }
+
+ if (cvs_recvfile(line) < 0) {
+ return (-1);
+ }
+
+ /* now see if there is a checksum */
+ if (cvs_cksum(line, cksum_buf, sizeof(cksum_buf)) < 0) {
+ }
+
+ if (strcmp(cksum_buf, cvs_fcksum) != 0) {
+ cvs_log(LP_ERR, "checksum error on received file");
+ (void)unlink(line);
+ }
+
+ free(cvs_fcksum);
+ cvs_fcksum = NULL;
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_removed()
+ *
+ * Handler for the `Updated' response.
+ */
+
+static int
+cvs_resp_removed(int type, char *line)
+{
+ return (0);
+}
+
+
+/*
+ * cvs_resp_mode()
+ *
+ * Handler for the `Mode' response.
+ */
+
+static int
+cvs_resp_mode(int type, char *line)
+{
+ if (cvs_strtomode(line, &cvs_lastmode) < 0) {
+ return (-1);
+ }
+ return (0);
+}
+
+
+/*
+ * cvs_sendfile()
+ *
+ * Send the mode and size of a file followed by the file's contents.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_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);
+}
+
+
+/*
+ * cvs_recvfile()
+ *
+ * Receive the mode and size of a file followed the file's contents and
+ * create or update the file whose path is <path> with the received
+ * information.
+ */
+
+int
+cvs_recvfile(const char *path)
+{
+ int fd;
+ mode_t mode;
+ size_t len;
+ ssize_t ret;
+ off_t fsz, cnt;
+ char buf[4096], *ep;
+
+ if ((cvs_client_getln(buf, sizeof(buf)) < 0) ||
+ (cvs_strtomode(buf, &mode) < 0)) {
+ return (-1);
+ }
+
+ cvs_client_getln(buf, sizeof(buf));
+
+ fsz = (off_t)strtol(buf, &ep, 10);
+ if (*ep != '\0') {
+ cvs_log(LP_ERR, "parse error in file size transmission");
+ return (-1);
+ }
+
+ fd = open(path, O_RDONLY, mode);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to open `%s'", path);
+ return (-1);
+ }
+
+ cnt = 0;
+ do {
+ len = MIN(sizeof(buf), (size_t)(fsz - cnt));
+ ret = cvs_client_recvraw(buf, len);
+ if (ret == -1) {
+ (void)close(fd);
+ (void)unlink(path);
+ return (-1);
+ }
+
+ if (write(fd, buf, (size_t)ret) == -1) {
+ cvs_log(LP_ERRNO,
+ "failed to write contents to file `%s'", path);
+ (void)close(fd);
+ (void)unlink(path);
+ return (-1);
+ }
+
+ cnt += (off_t)ret;
+ } while (cnt < fsz);
+
+ (void)close(fd);
+
+ return (0);
+}
diff --git a/usr.bin/cvs/rcs.c b/usr.bin/cvs/rcs.c
new file mode 100644
index 00000000000..20ded61cde4
--- /dev/null
+++ b/usr.bin/cvs/rcs.c
@@ -0,0 +1,1557 @@
+/* $OpenBSD: rcs.c,v 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/rcs.h b/usr.bin/cvs/rcs.h
new file mode 100644
index 00000000000..b902ce3e1ef
--- /dev/null
+++ b/usr.bin/cvs/rcs.h
@@ -0,0 +1,151 @@
+/* $OpenBSD: rcs.h,v 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 RCS_H
+#define RCS_H
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <time.h>
+
+#include "buf.h"
+
+#define RCS_DIFF_MAXARG 32
+#define RCS_DIFF_DIV \
+ "==================================================================="
+
+#define RCS_FILE_EXT ",v"
+
+#define RCS_HEAD_INIT "1.1"
+
+
+/* open modes */
+#define RCS_MODE_READ 0x01
+#define RCS_MODE_WRITE 0x02
+#define RCS_MODE_RDWR (RCS_MODE_READ|RCS_MODE_WRITE)
+
+
+/* file flags */
+#define RCS_RF_PARSED 0x01 /* file has been parsed */
+#define RCS_RF_SYNCED 0x02 /* in-memory copy is in sync with disk copy */
+#define RCS_RF_SLOCK 0x04 /* strict lock */
+
+/* delta flags */
+#define RCS_RD_DEAD 0x01 /* dead */
+
+
+typedef struct rcs_num {
+ u_int rn_len;
+ u_int16_t *rn_id;
+} RCSNUM;
+
+
+
+struct rcs_sym {
+ char *rs_name;
+ RCSNUM *rs_num;
+ TAILQ_ENTRY(rcs_sym) rs_list;
+};
+
+struct rcs_lock {
+ RCSNUM *rl_num;
+
+ TAILQ_ENTRY(rcs_lock) rl_list;
+};
+
+
+struct rcs_branch {
+ RCSNUM *rb_num;
+ TAILQ_ENTRY(rcs_branch) rb_list;
+};
+
+struct rcs_dlist {
+ struct rcs_delta *tqh_first;
+ struct rcs_delta **tqh_last;
+};
+
+struct rcs_delta {
+ RCSNUM *rd_num;
+ RCSNUM *rd_next;
+ u_int rd_flags;
+ struct tm rd_date;
+ char *rd_author;
+ char *rd_state;
+ char *rd_log;
+ char *rd_text;
+
+ struct rcs_dlist rd_snodes;
+
+ TAILQ_HEAD(, rcs_branch) rd_branches;
+ TAILQ_ENTRY(rcs_delta) rd_list;
+};
+
+
+
+typedef struct rcs_file {
+ char *rf_path;
+ u_int rf_ref;
+ u_int rf_mode;
+ u_int rf_flags;
+
+ RCSNUM *rf_head;
+ RCSNUM *rf_branch;
+ char *rf_comment;
+ char *rf_expand;
+ char *rf_desc;
+
+ struct rcs_dlist rf_delta;
+ TAILQ_HEAD(rcs_slist, rcs_sym) rf_symbols;
+ TAILQ_HEAD(rcs_llist, rcs_lock) rf_locks;
+
+ void *rf_pdata;
+} RCSFILE;
+
+
+
+
+RCSFILE* rcs_open (const char *, u_int);
+void rcs_close (RCSFILE *);
+int rcs_parse (RCSFILE *);
+int rcs_write (RCSFILE *);
+int rcs_addsym (RCSFILE *, const char *, RCSNUM *);
+int rcs_rmsym (RCSFILE *, const char *);
+BUF* rcs_getrev (RCSFILE *, RCSNUM *);
+RCSNUM* rcs_getrevbydate (RCSFILE *, struct tm *);
+
+BUF* rcs_patch (const char *, const char *);
+size_t rcs_stresc (int, const char *, char *, size_t *);
+
+RCSNUM* rcsnum_alloc (void);
+void rcsnum_free (RCSNUM *);
+int rcsnum_aton (const char *, char **, RCSNUM *);
+char* rcsnum_tostr (const RCSNUM *, char *, size_t);
+int rcsnum_cpy (const RCSNUM *, RCSNUM *, u_int);
+int rcsnum_cmp (const RCSNUM *, const RCSNUM *, u_int);
+
+
+#endif /* RCS_H */
diff --git a/usr.bin/cvs/rcsnum.c b/usr.bin/cvs/rcsnum.c
new file mode 100644
index 00000000000..b2694c62da7
--- /dev/null
+++ b/usr.bin/cvs/rcsnum.c
@@ -0,0 +1,231 @@
+/* $OpenBSD: rcsnum.c,v 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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "rcs.h"
+#include "log.h"
+
+
+
+/*
+ * rcsnum_alloc()
+ *
+ * Allocate an RCS number structure.
+ */
+
+RCSNUM*
+rcsnum_alloc(void)
+{
+ RCSNUM *rnp;
+
+ rnp = (RCSNUM *)malloc(sizeof(*rnp));
+ if (rnp == NULL) {
+ cvs_log(LP_ERR, "failed to allocate RCS number");
+ return (NULL);
+ }
+ rnp->rn_len = 0;
+ rnp->rn_id = NULL;
+
+ return (rnp);
+}
+
+
+/*
+ * rcsnum_free()
+ *
+ * Free an RCSNUM structure previously allocated with rcsnum_alloc().
+ */
+
+void
+rcsnum_free(RCSNUM *rn)
+{
+ if (rn->rn_id != NULL)
+ free(rn->rn_id);
+ free(rn);
+}
+
+
+/*
+ * rcsnum_tostr()
+ * Returns a pointer to the start of <buf> on success, or NULL on failure.
+ */
+
+char*
+rcsnum_tostr(const RCSNUM *nump, char *buf, size_t blen)
+{
+ u_int i;
+ char tmp[8];
+
+ if (nump->rn_len == 0) {
+ buf[0] = '\0';
+ return (buf);
+ }
+
+ snprintf(buf, blen, "%u", nump->rn_id[0]);
+ for (i = 1; i < nump->rn_len; i++) {
+ snprintf(tmp, sizeof(tmp), ".%u", nump->rn_id[i]);
+ strlcat(buf, tmp, blen);
+ }
+
+ return (buf);
+}
+
+
+/*
+ * rcsnum_cpy()
+ *
+ * Copy the number stored in <nsrc> in the destination <ndst> up to <depth>
+ * numbers deep.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+rcsnum_cpy(const RCSNUM *nsrc, RCSNUM *ndst, u_int depth)
+{
+ u_int len;
+ size_t sz;
+ void *tmp;
+
+ len = nsrc->rn_len;
+ if ((depth != 0) && (len > depth))
+ len = depth;
+ sz = len * sizeof(u_int16_t);
+
+ tmp = realloc(ndst->rn_id, sz);
+ if (tmp == NULL) {
+ cvs_log(LP_ERR, "failed to reallocate RCSNUM");
+ return (-1);
+ }
+
+ ndst->rn_id = (u_int16_t *)tmp;
+ ndst->rn_len = len;
+ memcpy(ndst->rn_id, nsrc->rn_id, sz);
+ return (0);
+}
+
+
+/*
+ * rcsnum_cmp()
+ *
+ * Compare the two numbers <n1> and <n2>. Returns -1 if <n1> is larger than
+ * <n2>, 0 if they are both the same, and 1 if <n2> is larger than <n1>.
+ * The <depth> argument specifies how many numbers deep should be checked for
+ * the result. A value of 0 means that the depth will be the minimum of the
+ * two numbers.
+ */
+
+int
+rcsnum_cmp(const RCSNUM *n1, const RCSNUM *n2, u_int depth)
+{
+ int res;
+ u_int i;
+ size_t slen;
+
+ slen = MIN(n1->rn_len, n2->rn_len);
+ if ((depth != 0) && (slen > depth))
+ slen = depth;
+
+ for (i = 0; i < slen; i++) {
+ res = n1->rn_id[i] - n2->rn_id[i];
+ if (res < 0)
+ return (1);
+ else if (res > 0)
+ return (-1);
+ }
+
+ if (n1->rn_len > n2->rn_len)
+ return (-1);
+ else if (n2->rn_len > n1->rn_len)
+ return (1);
+
+ return (0);
+}
+
+
+/*
+ * rcsnum_aton()
+ *
+ * Translate the string <str> containing a sequence of digits and periods into
+ * its binary representation, which is stored in <nump>. The address of the
+ * first byte not part of the number is stored in <ep> on return, if it is not
+ * NULL.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+rcsnum_aton(const char *str, char **ep, RCSNUM *nump)
+{
+ const char *sp;
+ void *tmp;
+
+ if (!isdigit(*str))
+ return (-1);
+
+ nump->rn_id = (u_int16_t *)malloc(sizeof(u_int16_t));
+ if (nump->rn_id == NULL)
+ return (-1);
+
+ nump->rn_len = 0;
+
+ for (sp = str; ; sp++) {
+ if (!isdigit(*sp) && (*sp != '.')) {
+ if (nump->rn_id[nump->rn_len] == 0) {
+ return (-1);
+ }
+ break;
+ }
+
+ if (*sp == '.') {
+ nump->rn_len++;
+ tmp = realloc(nump->rn_id,
+ (nump->rn_len + 1) * sizeof(u_int16_t));
+ if (tmp == NULL) {
+ free(nump->rn_id);
+ nump->rn_len = 0;
+ nump->rn_id = NULL;
+ return (-1);
+ }
+ nump->rn_id = (u_int16_t *)tmp;
+ continue;
+ }
+
+ nump->rn_id[nump->rn_len] *= 10;
+ nump->rn_id[nump->rn_len] += *sp - 0x30;
+ }
+
+ if (ep != NULL)
+ *ep = (char *)sp;
+
+ nump->rn_len++;
+ return (nump->rn_len);
+}
diff --git a/usr.bin/cvs/root.c b/usr.bin/cvs/root.c
new file mode 100644
index 00000000000..3b34a38fb5b
--- /dev/null
+++ b/usr.bin/cvs/root.c
@@ -0,0 +1,261 @@
+/* $OpenBSD: root.c,v 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 <err.h>
+#include <errno.h>
+#include <string.h>
+#include <paths.h>
+
+#include "cvs.h"
+#include "log.h"
+
+
+extern char *cvs_rootstr;
+
+
+/* keep these ordered with the defines */
+const char *cvs_methods[] = {
+ "",
+ "local",
+ "ssh",
+ "pserver",
+ "kserver",
+ "gserver",
+ "ext",
+ "fork",
+};
+
+#define CVS_NBMETHODS (sizeof(cvs_methods)/sizeof(cvs_methods[0]))
+
+
+
+/*
+ * cvsroot_parse()
+ *
+ * Parse a CVS root string (as found in CVS/Root files or the CVSROOT
+ * environment variable) and store the fields in a dynamically
+ * allocated cvs_root structure. The format of the string is as follows:
+ * :method:path
+ * Returns a pointer to the allocated information on success, or NULL
+ * on failure.
+ */
+
+struct cvsroot*
+cvsroot_parse(const char *str)
+{
+ u_int i;
+ char *cp, *sp, *pp;
+ struct cvsroot *root;
+
+ root = (struct cvsroot *)malloc(sizeof(*root));
+ if (root == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate CVS root data");
+ return (NULL);
+ }
+
+ root->cr_method = CVS_METHOD_NONE;
+
+ root->cr_buf = strdup(str);
+ if (root->cr_buf == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy CVS root");
+ free(root);
+ return (NULL);
+ }
+
+ sp = root->cr_buf;
+ cp = root->cr_buf;
+
+ if (*sp == ':') {
+ sp++;
+ cp = strchr(sp, ':');
+ if (cp == NULL) {
+ cvs_log(LP_ERR, "failed to parse CVSROOT: "
+ "unterminated method");
+ free(root->cr_buf);
+ free(root);
+ return (NULL);
+ }
+ *(cp++) = '\0';
+
+ for (i = 0; i < CVS_NBMETHODS; i++) {
+ if (strcmp(sp, cvs_methods[i]) == 0) {
+ root->cr_method = i;
+ break;
+ }
+ }
+ }
+
+ /* find the start of the actual path */
+ sp = strchr(cp, '/');
+ if (sp == NULL) {
+ cvs_log(LP_ERR, "no path specification in CVSROOT");
+ free(root->cr_buf);
+ free(root);
+ return (NULL);
+ }
+
+ root->cr_dir = sp;
+ if (sp == cp) {
+ if (root->cr_method == CVS_METHOD_NONE)
+ root->cr_method = CVS_METHOD_LOCAL;
+ /* stop here, it's just a path */
+ return (root);
+ }
+
+ if (*(sp - 1) != ':') {
+ cvs_log(LP_ERR, "missing host/path delimiter in CVS root");
+ free(root);
+ return (NULL);
+ }
+ *(sp - 1) = '\0';
+
+ /*
+ * looks like we have more than just a directory path, so
+ * attempt to split it into user and host parts
+ */
+ sp = strchr(cp, '@');
+ if (sp != NULL) {
+ *(sp++) = '\0';
+
+ /* password ? */
+ pp = strchr(cp, ':');
+ if (pp != NULL) {
+ *(pp++) = '\0';
+ root->cr_pass = pp;
+ }
+
+ root->cr_user = cp;
+ }
+
+ pp = strchr(sp, ':');
+ if (pp != NULL) {
+ *(pp++) = '\0';
+ root->cr_port = (u_int)strtol(pp, &cp, 10);
+ if (*cp != '\0' || root->cr_port > 65535) {
+ cvs_log(LP_ERR,
+ "invalid port specification in CVSROOT");
+ free(root);
+ return (NULL);
+ }
+
+ }
+
+ root->cr_host = sp;
+
+ if (root->cr_method == CVS_METHOD_NONE) {
+ /* no method found from start of CVSROOT, guess */
+ if (root->cr_host != NULL)
+ root->cr_method = CVS_METHOD_SERVER;
+ else
+ root->cr_method = CVS_METHOD_LOCAL;
+ }
+
+ return (root);
+}
+
+
+/*
+ * cvsroot_free()
+ *
+ * Free a CVSROOT structure previously allocated and returned by
+ * cvsroot_parse().
+ */
+
+void
+cvsroot_free(struct cvsroot *root)
+{
+ free(root->cr_buf);
+ free(root);
+}
+
+
+/*
+ * cvsroot_get()
+ *
+ * Get the CVSROOT information for a specific directory <dir>. The
+ * value is taken from one of 3 possible sources (in order of precedence):
+ *
+ * 1) the `-d' command-line option
+ * 2) the CVS/Root file found in checked-out trees
+ * 3) the CVSROOT environment variable
+ */
+
+struct cvsroot*
+cvsroot_get(const char *dir)
+{
+ size_t len;
+ char rootpath[MAXPATHLEN], *rootstr, *line;
+ FILE *fp;
+ struct cvsroot *rp;
+
+ if (cvs_rootstr != NULL)
+ return cvsroot_parse(cvs_rootstr);
+
+ snprintf(rootpath, sizeof(rootpath), "%s/" CVS_PATH_ROOTSPEC, dir);
+ fp = fopen(rootpath, "r");
+ if (fp == NULL) {
+ if (errno == ENOENT) {
+ /* try env as a last resort */
+ if ((rootstr = getenv("CVSROOT")) != NULL)
+ return cvsroot_parse(rootstr);
+ else
+ return (NULL);
+ }
+ else {
+ cvs_log(LP_ERRNO, "failed to open CVS/Root");
+ return (NULL);
+ }
+ }
+
+ line = fgetln(fp, &len);
+ if (line == NULL) {
+ cvs_log(LP_ERR, "failed to read CVSROOT line from CVS/Root");
+ (void)fclose(fp);
+ }
+
+ /* line is not NUL-terminated, but we don't need to allocate an
+ * extra byte because we don't want the trailing newline. It will
+ * get replaced by a \0.
+ */
+ rootstr = (char *)malloc(len);
+ if (rootstr == NULL) {
+ cvs_log(LP_ERRNO, "failed to allocate CVSROOT string");
+ (void)fclose(fp);
+ return (NULL);
+ }
+ strlcpy(rootstr, line, len);
+ rp = cvsroot_parse(rootstr);
+
+ (void)fclose(fp);
+ free(rootstr);
+
+ return (rp);
+}
diff --git a/usr.bin/cvs/server.c b/usr.bin/cvs/server.c
new file mode 100644
index 00000000000..6cc75bec08c
--- /dev/null
+++ b/usr.bin/cvs/server.c
@@ -0,0 +1,95 @@
+/* $OpenBSD: server.c,v 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);
+}
diff --git a/usr.bin/cvs/sock.c b/usr.bin/cvs/sock.c
new file mode 100644
index 00000000000..0dcfbdcaca2
--- /dev/null
+++ b/usr.bin/cvs/sock.c
@@ -0,0 +1,237 @@
+/* $OpenBSD: sock.c,v 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/socket.h>
+#include <sys/un.h>
+
+#include <poll.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "log.h"
+#include "sock.h"
+#include "cvsd.h"
+#include "event.h"
+
+
+volatile sig_atomic_t cvs_sock_doloop;
+
+
+char *cvsd_sock_path = CVSD_SOCK_PATH;
+
+
+
+/* daemon API */
+static int cvsd_sock = -1;
+static struct sockaddr_un cvsd_sun;
+
+/* for client API */
+static int cvs_sock = -1;
+static struct sockaddr_un cvs_sun;
+
+
+/*
+ * cvsd_sock_open()
+ *
+ * Open the daemon's local socket.
+ */
+
+int
+cvsd_sock_open(void)
+{
+ cvsd_sun.sun_family = AF_LOCAL;
+ strlcpy(cvsd_sun.sun_path, cvsd_sock_path, sizeof(cvsd_sun.sun_path));
+
+ cvsd_sock = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (cvsd_sock == -1) {
+ cvs_log(LP_ERRNO, "failed to open socket");
+ return (-1);
+ }
+
+ if (bind(cvsd_sock, (struct sockaddr *)&cvsd_sun,
+ SUN_LEN(&cvsd_sun)) == -1) {
+ cvs_log(LP_ERRNO, "failed to bind local socket to `%s'",
+ cvsd_sock_path);
+ (void)close(cvsd_sock);
+ return (-1);
+ }
+
+ listen(cvsd_sock, 10);
+
+ if (chmod(cvsd_sock_path, CVSD_SOCK_PERMS) == -1) {
+ cvs_log(LP_ERRNO, "failed to change mode of `%s'",
+ cvsd_sock_path);
+ (void)close(cvsd_sock);
+ (void)unlink(cvsd_sock_path);
+ return (-1);
+ }
+
+ cvs_log(LP_DEBUG, "opened local socket `%s'", cvsd_sock_path);
+
+ return (0);
+}
+
+
+/*
+ * cvsd_sock_close()
+ *
+ * Close the local socket.
+ */
+
+void
+cvsd_sock_close(void)
+{
+ cvs_log(LP_DEBUG, "closing local socket `%s'", CVSD_SOCK_PATH);
+ if (close(cvsd_sock) == -1) {
+ cvs_log(LP_ERRNO, "failed to close local socket");
+ }
+ if (unlink(cvsd_sock_path) == -1)
+ cvs_log(LP_ERRNO, "failed to unlink local socket `%s'",
+ CVSD_SOCK_PATH);
+}
+
+
+/*
+ * cvsd_sock_loop()
+ *
+ */
+
+void
+cvsd_sock_loop(void)
+{
+ int nfds, sock;
+ socklen_t slen;
+ struct sockaddr_un sun;
+ struct pollfd pfd[1];
+
+ cvs_sock_doloop = 1;
+
+ while (cvs_sock_doloop) {
+ pfd[0].fd = cvsd_sock;
+ pfd[0].events = POLLIN;
+
+ nfds = poll(pfd, 1, INFTIM);
+ if (nfds == -1) {
+ if (errno == EINTR)
+ continue;
+ cvs_log(LP_ERR, "failed to poll local socket");
+ }
+
+ if ((nfds == 0) || !(pfd[0].revents & POLLIN))
+ continue;
+
+ sock = accept(pfd[0].fd, (struct sockaddr *)&sun, &slen);
+ if (sock == -1) {
+ cvs_log(LP_ERRNO, "failed to accept connection");
+ }
+ cvs_log(LP_DEBUG, "accepted connection");
+
+ cvsd_sock_hdl(sock);
+ }
+
+
+}
+
+
+/*
+ * cvsd_sock_hdl()
+ *
+ * Handle the events for a single connection.
+ */
+
+int
+cvsd_sock_hdl(int fd)
+{
+ uid_t uid;
+ gid_t gid;
+ struct cvs_event ev;
+
+ /* don't trust what the other end put in */
+ if (getpeereid(fd, &uid, &gid) == -1) {
+ cvs_log(LP_ERR, "failed to get peer credentials");
+ (void)close(fd);
+ return (-1);
+ }
+
+ if (read(fd, &ev, sizeof(ev)) == -1) {
+ cvs_log(LP_ERR, "failed to read cvs event");
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_sock_connect()
+ *
+ * Open a connection to the CVS server's local socket.
+ */
+
+int
+cvs_sock_connect(const char *cvsroot)
+{
+ cvs_sun.sun_family = AF_LOCAL;
+ snprintf(cvs_sun.sun_path, sizeof(cvs_sun.sun_path), "%s/%s",
+ cvsroot, CVSD_SOCK_PATH);
+
+ cvs_log(LP_INFO, "connecting to CVS server socket `%s'",
+ cvs_sun.sun_path);
+
+ cvs_sock = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (cvs_sock == -1) {
+ cvs_log(LP_ERRNO, "failed to open local socket");
+ return (-1);
+ }
+
+ if (connect(cvs_sock, (struct sockaddr *)&cvs_sun,
+ SUN_LEN(&cvs_sun)) == -1) {
+ cvs_log(LP_ERRNO, "failed to connect to server socket `%s'",
+ cvs_sun.sun_path);
+ (void)close(cvs_sock);
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_sock_disconnect()
+ *
+ * Disconnect from the open socket to the CVS server.
+ */
+
+void
+cvs_sock_disconnect(void)
+{
+ if (close(cvs_sock) == -1)
+ cvs_log(LP_ERRNO, "failed to close local socket");
+}
diff --git a/usr.bin/cvs/sock.h b/usr.bin/cvs/sock.h
new file mode 100644
index 00000000000..6595cec951a
--- /dev/null
+++ b/usr.bin/cvs/sock.h
@@ -0,0 +1,65 @@
+/* $OpenBSD: sock.h,v 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 SOCK_H
+#define SOCK_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "cvs.h"
+
+
+#define CVSD_SOCK_PATH "/var/run/cvsd.sock"
+
+#define CVSD_SOCK_PERMS (S_IRWXU | S_IRWXG)
+
+
+extern char *cvsd_sock_path;
+
+extern volatile sig_atomic_t cvs_sock_doloop;
+
+
+/* daemon api */
+int cvsd_sock_open (void);
+void cvsd_sock_close (void);
+void cvsd_sock_loop (void);
+int cvsd_sock_hdl (int);
+
+/* client api */
+int cvs_sock_connect (const char *);
+void cvs_sock_disconnect (void);
+
+#endif /* SOCK_H */
diff --git a/usr.bin/cvs/update.c b/usr.bin/cvs/update.c
new file mode 100644
index 00000000000..4305740f7e5
--- /dev/null
+++ b/usr.bin/cvs/update.c
@@ -0,0 +1,85 @@
+/* $OpenBSD: update.c,v 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/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 "rcs.h"
+#include "log.h"
+
+extern char *__progname;
+
+extern struct cvsroot *cvs_root;
+
+
+/*
+ * cvs_update()
+ *
+ * Handle the `cvs update' command.
+ */
+
+
+int
+cvs_update(int argc, char **argv)
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "ACD:dflPpQqRr:")) != -1) {
+ switch (ch) {
+ case 'A':
+ case 'C':
+ case 'D':
+ case 'd':
+ case 'f':
+ case 'l':
+ case 'P':
+ case 'p':
+ case 'Q':
+ case 'q':
+ case 'R':
+ case 'r':
+ break;
+ default:
+ fprintf(stderr, "Usage: %s [args] update [args] ...\n",
+ __progname);
+ return (EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ return (0);
+}
diff --git a/usr.bin/cvs/util.c b/usr.bin/cvs/util.c
new file mode 100644
index 00000000000..f0c579bdebf
--- /dev/null
+++ b/usr.bin/cvs/util.c
@@ -0,0 +1,386 @@
+/* $OpenBSD: util.c,v 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 <md5.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "cvs.h"
+#include "log.h"
+
+
+/* letter -> mode type map */
+static const int cvs_modetypes[26] = {
+ -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1,
+ -1, 2, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1,
+};
+
+/* letter -> mode map */
+static const mode_t cvs_modes[3][26] = {
+ {
+ 0, 0, 0, 0, 0, 0, 0, /* a - g */
+ 0, 0, 0, 0, 0, 0, 0, /* h - m */
+ 0, 0, 0, S_IRUSR, 0, 0, 0, /* n - u */
+ 0, S_IWUSR, S_IXUSR, 0, 0 /* v - z */
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, /* a - g */
+ 0, 0, 0, 0, 0, 0, 0, /* h - m */
+ 0, 0, 0, S_IRGRP, 0, 0, 0, /* n - u */
+ 0, S_IWGRP, S_IXGRP, 0, 0 /* v - z */
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, /* a - g */
+ 0, 0, 0, 0, 0, 0, 0, /* h - m */
+ 0, 0, 0, S_IROTH, 0, 0, 0, /* n - u */
+ 0, S_IWOTH, S_IXOTH, 0, 0 /* v - z */
+ }
+};
+
+
+/* octal -> string */
+static const char *cvs_modestr[8] = {
+ "", "x", "w", "wx", "r", "rx", "rw", "rwx"
+};
+
+
+
+
+/*
+ * cvs_readrepo()
+ *
+ * Read the path stored in the `Repository' CVS file for a given directory
+ * <dir>, and store that path into the buffer pointed to by <dst>, whose size
+ * is <len>.
+ */
+
+int
+cvs_readrepo(const char *dir, char *dst, size_t len)
+{
+ size_t dlen;
+ FILE *fp;
+ char repo_path[MAXPATHLEN];
+
+ snprintf(repo_path, sizeof(repo_path), "%s/CVS/Repository", dir);
+
+ fp = fopen(repo_path, "r");
+ if (fp == NULL) {
+ cvs_log(LP_ERRNO, "failed to open `%s'", repo_path);
+ return (-1);
+ }
+
+ if (fgets(dst, (int)len, fp) == NULL) {
+ if (ferror(fp)) {
+ cvs_log(LP_ERRNO, "failed to read from `%s'",
+ repo_path);
+ }
+ (void)fclose(fp);
+ return (-1);
+ }
+ dlen = strlen(dst);
+ if ((dlen > 0) && (dst[dlen - 1] == '\n'))
+ dst[--dlen] = '\0';
+
+ (void)fclose(fp);
+
+ return (0);
+}
+
+
+/*
+ * cvs_strtomode()
+ *
+ * Read the contents of the string <str> and generate a permission mode from
+ * the contents of <str>, which is assumed to have the mode format of CVS.
+ * The CVS protocol specification states that any modes or mode types that are
+ * not recognized should be silently ignored. This function does not return
+ * an error in such cases, but will issue warnings.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_strtomode(const char *str, mode_t *mode)
+{
+ int type;
+ mode_t m;
+ char buf[32], ms[4], *sp, *ep;
+
+ m = 0;
+ strlcpy(buf, str, sizeof(buf));
+ sp = buf;
+ ep = sp;
+
+ for (sp = buf; ep != NULL; sp = ep + 1) {
+ ep = strchr(sp, ',');
+ if (ep != NULL)
+ *(ep++) = '\0';
+
+ if (sscanf(sp, "%c=%3s", (char *)&type, ms) != 2) {
+ cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
+ continue;
+ }
+
+ if ((type <= 'a') || (type >= 'z') ||
+ (cvs_modetypes[type - 'a'] == -1)) {
+ cvs_log(LP_WARN,
+ "invalid mode type `%c'"
+ " (`u', `g' or `o' expected), ignoring", type);
+ continue;
+ }
+
+ /* make type contain the actual mode index */
+ type = cvs_modetypes[type - 'a'];
+
+ for (sp = ms; *sp != '\0'; sp++) {
+ if ((*sp <= 'a') || (*sp >= 'z') ||
+ (cvs_modes[type][*sp - 'a'] == 0)) {
+ cvs_log(LP_WARN,
+ "invalid permission bit `%c'", *sp);
+ }
+ else
+ m |= cvs_modes[type][*sp - 'a'];
+ }
+ }
+
+ *mode = m;
+
+ return (0);
+}
+
+
+/*
+ * cvs_modetostr()
+ *
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_modetostr(mode_t mode, char *buf, size_t len)
+{
+ size_t l;
+ char tmp[16], *bp;
+ mode_t um, gm, om;
+
+ um = (mode & S_IRWXU) >> 6;
+ gm = (mode & S_IRWXG) >> 3;
+ om = mode & S_IRWXO;
+
+ bp = buf;
+ *bp = '\0';
+ l = 0;
+
+ if (um) {
+ snprintf(tmp, sizeof(tmp), "u=%s", cvs_modestr[um]);
+ l = strlcat(buf, tmp, len);
+ }
+ if (gm) {
+ if (um)
+ strlcat(buf, ",", len);
+ snprintf(tmp, sizeof(tmp), "g=%s", cvs_modestr[gm]);
+ strlcat(buf, tmp, len);
+ }
+ if (om) {
+ if (um || gm)
+ strlcat(buf, ",", len);
+ snprintf(tmp, sizeof(tmp), "o=%s", cvs_modestr[gm]);
+ strlcat(buf, tmp, len);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_cksum()
+ *
+ * Calculate the MD5 checksum of the file whose path is <file> and generate
+ * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
+ * given in <len> and must be at least 33.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_cksum(const char *file, char *dst, size_t len)
+{
+ if (len < CVS_CKSUM_LEN) {
+ cvs_log(LP_WARN, "buffer too small for checksum");
+ return (-1);
+ }
+ if (MD5File(file, dst) == NULL) {
+ cvs_log(LP_ERRNO, "failed to generate file checksum");
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_splitpath()
+ *
+ * Split a path <path> into the directory portion and the filename portion
+ * and copy them in <dir> and <file>, whose lengths are <dlen> and <flen>,
+ * unless they are NULL.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_splitpath(const char *path, char *dir, size_t dlen, char *file, size_t flen)
+{
+ size_t rlen;
+ const char *sp;
+ struct stat st;
+
+ sp = strrchr(path, '/');
+ if (sp == NULL) {
+ if (stat(path, &st) == -1)
+ return (-1);
+
+ if (S_ISDIR(st.st_mode)) {
+ if (dir != NULL)
+ strlcpy(dir, path, dlen);
+ if (file != NULL)
+ file[0] = '\0';
+ }
+ else {
+ if (file != NULL)
+ strlcpy(file, path, flen);
+ if (dir != NULL)
+ strlcpy(dir, ".", dlen);
+ }
+ }
+ else {
+ rlen = MIN(dlen - 1, sp - path);
+ if (dir != NULL) {
+ strncpy(dir, path, rlen);
+ dir[rlen] = '\0';
+ }
+
+ sp++;
+ if (file != NULL)
+ strlcpy(file, sp, flen);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_getargv()
+ *
+ * Parse a line contained in <line> and generate an argument vector by
+ * splitting the line on spaces and tabs. The resulting vector is stored in
+ * <argv>, which can accept up to <argvlen> entries.
+ * Returns the number of arguments in the vector, or -1 if an error occured.
+ */
+
+int
+cvs_getargv(const char *line, char **argv, int argvlen)
+{
+ u_int i;
+ int argc, err;
+ char linebuf[256], qbuf[128], *lp, *cp, *arg;
+
+ strlcpy(linebuf, line, sizeof(linebuf));
+ memset(argv, 0, sizeof(argv));
+ argc = 0;
+
+ /* build the argument vector */
+ err = 0;
+ for (lp = linebuf; lp != NULL;) {
+ if (*lp == '"') {
+ /* double-quoted string */
+ lp++;
+ i = 0;
+ memset(qbuf, 0, sizeof(qbuf));
+ while (*lp != '"') {
+ if (*lp == '\0') {
+ cvs_log(LP_ERR, "no terminating quote");
+ err++;
+ break;
+ }
+ else if (*lp == '\\')
+ lp++;
+
+ qbuf[i++] = *lp++;
+ if (i == sizeof(qbuf)) {
+ err++;
+ break;
+ }
+ }
+
+ arg = qbuf;
+ }
+ else {
+ cp = strsep(&lp, " \t");
+ if (cp == NULL)
+ break;
+ else if (*cp == '\0')
+ continue;
+
+ arg = cp;
+ }
+
+ argv[argc] = strdup(arg);
+ if (argv[argc] == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy argument");
+ err++;
+ break;
+ }
+ argc++;
+ }
+
+ if (err) {
+ /* ditch the argument vector */
+ for (i = 0; i < (u_int)argc; i++)
+ free(argv[i]);
+ argc = -1;
+ }
+
+ return (argc);
+}
+
+
+/*
+ * cvs_freeargv()
+ *
+ * Free an argument vector previously generated by cvs_getargv().
+ */
+
+void
+cvs_freeargv(char **argv, int argc)
+{
+ int i;
+
+ for (i = 0; i < argc; i++)
+ free(argv[i]);
+}
diff --git a/usr.bin/cvs/version.c b/usr.bin/cvs/version.c
new file mode 100644
index 00000000000..ab220397d1d
--- /dev/null
+++ b/usr.bin/cvs/version.c
@@ -0,0 +1,69 @@
+/* $OpenBSD: version.c,v 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 <sysexits.h>
+
+#include "cvs.h"
+
+
+
+extern struct cvsroot *cvs_root;
+
+
+
+int
+cvs_version(int argc, char **argv)
+{
+ if (argc > 1)
+ return (EX_USAGE);
+
+ cvs_root = cvsroot_get(".");
+
+ if ((cvs_root) && (cvs_root->cr_method != CVS_METHOD_LOCAL))
+ printf("Client: ");
+
+ printf("%s\n", CVS_VERSION);
+
+
+ if ((cvs_root) && (cvs_root->cr_method != CVS_METHOD_LOCAL))
+ if (cvs_client_connect() < 0)
+ return (1);
+
+ if ((cvs_root) && (cvs_root->cr_method != CVS_METHOD_LOCAL)) {
+ printf("Server: ");
+ cvs_client_sendreq(CVS_REQ_VERSION, NULL, 1);
+ cvs_client_disconnect();
+ }
+
+ return (0);
+}