diff options
author | Jean-Francois Brousseau <jfb@cvs.openbsd.org> | 2004-07-13 22:02:41 +0000 |
---|---|---|
committer | Jean-Francois Brousseau <jfb@cvs.openbsd.org> | 2004-07-13 22:02:41 +0000 |
commit | 00e6351a497dcb54dcf25371825dd7cfb67f8b93 (patch) | |
tree | 60a31f1eb4b0c0b32b1b87d455a7bbe51e7c210f | |
parent | 0f7b1f29354d9c9700943e8e98c88574f7ec3bfd (diff) |
* initial import from the cvs-tools module
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); +} |