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