1*1a0afcdeSderaadt /* $OpenBSD: rcs.c,v 1.89 2021/11/28 19:28:42 deraadt Exp $ */
22dc36bedSjoris /*
32dc36bedSjoris * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
42dc36bedSjoris * All rights reserved.
52dc36bedSjoris *
62dc36bedSjoris * Redistribution and use in source and binary forms, with or without
72dc36bedSjoris * modification, are permitted provided that the following conditions
82dc36bedSjoris * are met:
92dc36bedSjoris *
102dc36bedSjoris * 1. Redistributions of source code must retain the above copyright
112dc36bedSjoris * notice, this list of conditions and the following disclaimer.
122dc36bedSjoris * 2. The name of the author may not be used to endorse or promote products
132dc36bedSjoris * derived from this software without specific prior written permission.
142dc36bedSjoris *
152dc36bedSjoris * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
162dc36bedSjoris * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
172dc36bedSjoris * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
182dc36bedSjoris * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
192dc36bedSjoris * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
202dc36bedSjoris * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
212dc36bedSjoris * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
222dc36bedSjoris * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
232dc36bedSjoris * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
242dc36bedSjoris * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
252dc36bedSjoris */
262dc36bedSjoris
27*1a0afcdeSderaadt #include <sys/types.h>
284781e2faSxsa #include <sys/stat.h>
294781e2faSxsa
304781e2faSxsa #include <ctype.h>
314781e2faSxsa #include <err.h>
324781e2faSxsa #include <errno.h>
334781e2faSxsa #include <pwd.h>
344781e2faSxsa #include <stdarg.h>
354781e2faSxsa #include <stdio.h>
364781e2faSxsa #include <stdlib.h>
374781e2faSxsa #include <string.h>
384781e2faSxsa #include <unistd.h>
392dc36bedSjoris
402dc36bedSjoris #include "diff.h"
412dc36bedSjoris #include "rcs.h"
42fead43dfStobias #include "rcsparse.h"
432dc36bedSjoris #include "rcsprog.h"
44714c2363Sxsa #include "rcsutil.h"
452dc36bedSjoris #include "xmalloc.h"
462dc36bedSjoris
47*1a0afcdeSderaadt #define _MAXBSIZE (64 * 1024)
48*1a0afcdeSderaadt
49b9fc9a72Sderaadt #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
50b9fc9a72Sderaadt
51b7f54172Stobias /* invalid characters in RCS states */
52b7f54172Stobias static const char rcs_state_invch[] = RCS_STATE_INVALCHAR;
53b7f54172Stobias
542dc36bedSjoris /* invalid characters in RCS symbol names */
552dc36bedSjoris static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
562dc36bedSjoris
572dc36bedSjoris struct rcs_kw rcs_expkw[] = {
582dc36bedSjoris { "Author", RCS_KW_AUTHOR },
592dc36bedSjoris { "Date", RCS_KW_DATE },
60ef21cefbSnicm { "Locker", RCS_KW_LOCKER },
612dc36bedSjoris { "Header", RCS_KW_HEADER },
622dc36bedSjoris { "Id", RCS_KW_ID },
63b22d3d34Sderaadt { "OpenBSD", RCS_KW_ID },
642dc36bedSjoris { "Log", RCS_KW_LOG },
652dc36bedSjoris { "Name", RCS_KW_NAME },
662dc36bedSjoris { "RCSfile", RCS_KW_RCSFILE },
672dc36bedSjoris { "Revision", RCS_KW_REVISION },
682dc36bedSjoris { "Source", RCS_KW_SOURCE },
692dc36bedSjoris { "State", RCS_KW_STATE },
70b5a78d44Sschwarze { "Mdocdate", RCS_KW_MDOCDATE },
712dc36bedSjoris };
722dc36bedSjoris
732dc36bedSjoris int rcs_errno = RCS_ERR_NOERR;
742dc36bedSjoris char *timezone_flag = NULL;
752dc36bedSjoris
762dc36bedSjoris int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *);
7793acdb67Sxsa static int rcs_movefile(char *, char *, mode_t, u_int);
782dc36bedSjoris
792dc36bedSjoris static void rcs_freedelta(struct rcs_delta *);
802dc36bedSjoris static void rcs_strprint(const u_char *, size_t, FILE *);
812dc36bedSjoris
823de4517bSniallo static BUF *rcs_expand_keywords(char *, struct rcs_delta *, BUF *, int);
832dc36bedSjoris
842dc36bedSjoris RCSFILE *
rcs_open(const char * path,int fd,int flags,...)85bc8d37c3Sjoris rcs_open(const char *path, int fd, int flags, ...)
862dc36bedSjoris {
87bc8d37c3Sjoris int mode;
882dc36bedSjoris mode_t fmode;
892dc36bedSjoris RCSFILE *rfp;
902dc36bedSjoris va_list vap;
912dc36bedSjoris struct rcs_delta *rdp;
922dc36bedSjoris struct rcs_lock *lkr;
932dc36bedSjoris
942dc36bedSjoris fmode = S_IRUSR|S_IRGRP|S_IROTH;
952dc36bedSjoris flags &= 0xffff; /* ditch any internal flags */
962dc36bedSjoris
972dc36bedSjoris if (flags & RCS_CREATE) {
982dc36bedSjoris va_start(vap, flags);
992dc36bedSjoris mode = va_arg(vap, int);
1002dc36bedSjoris va_end(vap);
1012dc36bedSjoris fmode = (mode_t)mode;
1022dc36bedSjoris }
1032dc36bedSjoris
1042dc36bedSjoris rfp = xcalloc(1, sizeof(*rfp));
1052dc36bedSjoris
1062dc36bedSjoris rfp->rf_path = xstrdup(path);
1072dc36bedSjoris rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
1082dc36bedSjoris rfp->rf_mode = fmode;
109394437e6Stobias if (fd == -1)
110394437e6Stobias rfp->rf_file = NULL;
111394437e6Stobias else if ((rfp->rf_file = fdopen(fd, "r")) == NULL)
112394437e6Stobias err(1, "rcs_open: fdopen: `%s'", path);
1132dc36bedSjoris
1142dc36bedSjoris TAILQ_INIT(&(rfp->rf_delta));
1152dc36bedSjoris TAILQ_INIT(&(rfp->rf_access));
1162dc36bedSjoris TAILQ_INIT(&(rfp->rf_symbols));
1172dc36bedSjoris TAILQ_INIT(&(rfp->rf_locks));
1182dc36bedSjoris
11938f951f3Sray if (!(rfp->rf_flags & RCS_CREATE)) {
120fead43dfStobias if (rcsparse_init(rfp))
121fead43dfStobias errx(1, "could not parse admin data");
1222dc36bedSjoris
1232dc36bedSjoris /* fill in rd_locker */
1242dc36bedSjoris TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
1252dc36bedSjoris if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
1262dc36bedSjoris rcs_close(rfp);
1272dc36bedSjoris return (NULL);
1282dc36bedSjoris }
1292dc36bedSjoris
1302dc36bedSjoris rdp->rd_locker = xstrdup(lkr->rl_name);
1312dc36bedSjoris }
13238f951f3Sray }
1332dc36bedSjoris
1342dc36bedSjoris return (rfp);
1352dc36bedSjoris }
1362dc36bedSjoris
1372dc36bedSjoris /*
1382dc36bedSjoris * rcs_close()
1392dc36bedSjoris *
1402dc36bedSjoris * Close an RCS file handle.
1412dc36bedSjoris */
1422dc36bedSjoris void
rcs_close(RCSFILE * rfp)1432dc36bedSjoris rcs_close(RCSFILE *rfp)
1442dc36bedSjoris {
1452dc36bedSjoris struct rcs_delta *rdp;
1462dc36bedSjoris struct rcs_access *rap;
1472dc36bedSjoris struct rcs_lock *rlp;
1482dc36bedSjoris struct rcs_sym *rsp;
1492dc36bedSjoris
1502dc36bedSjoris if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
1512dc36bedSjoris rcs_write(rfp);
1522dc36bedSjoris
1532dc36bedSjoris while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
1542dc36bedSjoris rdp = TAILQ_FIRST(&(rfp->rf_delta));
1552dc36bedSjoris TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
1562dc36bedSjoris rcs_freedelta(rdp);
1572dc36bedSjoris }
1582dc36bedSjoris
1592dc36bedSjoris while (!TAILQ_EMPTY(&(rfp->rf_access))) {
1602dc36bedSjoris rap = TAILQ_FIRST(&(rfp->rf_access));
1612dc36bedSjoris TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
1628ac837e5Snicm free(rap->ra_name);
1638ac837e5Snicm free(rap);
1642dc36bedSjoris }
1652dc36bedSjoris
1662dc36bedSjoris while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
1672dc36bedSjoris rsp = TAILQ_FIRST(&(rfp->rf_symbols));
1682dc36bedSjoris TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
1692dc36bedSjoris rcsnum_free(rsp->rs_num);
1708ac837e5Snicm free(rsp->rs_name);
1718ac837e5Snicm free(rsp);
1722dc36bedSjoris }
1732dc36bedSjoris
1742dc36bedSjoris while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
1752dc36bedSjoris rlp = TAILQ_FIRST(&(rfp->rf_locks));
1762dc36bedSjoris TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
1772dc36bedSjoris rcsnum_free(rlp->rl_num);
1788ac837e5Snicm free(rlp->rl_name);
1798ac837e5Snicm free(rlp);
1802dc36bedSjoris }
1812dc36bedSjoris
1822dc36bedSjoris rcsnum_free(rfp->rf_head);
1832dc36bedSjoris rcsnum_free(rfp->rf_branch);
1842dc36bedSjoris
185394437e6Stobias if (rfp->rf_file != NULL)
186394437e6Stobias fclose(rfp->rf_file);
1878ac837e5Snicm
1888ac837e5Snicm free(rfp->rf_path);
1898ac837e5Snicm free(rfp->rf_comment);
1908ac837e5Snicm free(rfp->rf_expand);
1918ac837e5Snicm free(rfp->rf_desc);
1922dc36bedSjoris if (rfp->rf_pdata != NULL)
193fead43dfStobias rcsparse_free(rfp);
1948ac837e5Snicm
1958ac837e5Snicm free(rfp);
1962dc36bedSjoris }
1972dc36bedSjoris
1982dc36bedSjoris /*
1992dc36bedSjoris * rcs_write()
2002dc36bedSjoris *
2012dc36bedSjoris * Write the contents of the RCS file handle <rfp> to disk in the file whose
2022dc36bedSjoris * path is in <rf_path>.
2032dc36bedSjoris */
20493acdb67Sxsa void
rcs_write(RCSFILE * rfp)2052dc36bedSjoris rcs_write(RCSFILE *rfp)
2062dc36bedSjoris {
2072dc36bedSjoris FILE *fp;
208950e09dcSxsa char numbuf[RCS_REV_BUFSZ], *fn;
2092dc36bedSjoris struct rcs_access *ap;
2102dc36bedSjoris struct rcs_sym *symp;
2112dc36bedSjoris struct rcs_branch *brp;
2122dc36bedSjoris struct rcs_delta *rdp;
2132dc36bedSjoris struct rcs_lock *lkp;
2142dc36bedSjoris size_t len;
215a2a1496fSray int fd;
2162dc36bedSjoris
217f4a30fb9Sniallo fn = NULL;
2182dc36bedSjoris
2192dc36bedSjoris if (rfp->rf_flags & RCS_SYNCED)
22093acdb67Sxsa return;
2212dc36bedSjoris
2222dc36bedSjoris /* Write operations need the whole file parsed */
223fead43dfStobias if (rcsparse_deltatexts(rfp, NULL))
224fead43dfStobias errx(1, "problem parsing deltatexts");
2252dc36bedSjoris
226cdc530d2Sxsa (void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", rcs_tmpdir);
227cdc530d2Sxsa
2282dc36bedSjoris if ((fd = mkstemp(fn)) == -1)
229d4625ebbSxsa err(1, "%s", fn);
2302dc36bedSjoris
2312dc36bedSjoris if ((fp = fdopen(fd, "w+")) == NULL) {
2326b99bb86Sray int saved_errno;
2336b99bb86Sray
2346b99bb86Sray saved_errno = errno;
235d4625ebbSxsa (void)unlink(fn);
2366b99bb86Sray errno = saved_errno;
237d4625ebbSxsa err(1, "%s", fn);
2382dc36bedSjoris }
2392dc36bedSjoris
2407a9e6d11Sray worklist_add(fn, &temp_files);
2413627c1f6Sjoris
2422dc36bedSjoris if (rfp->rf_head != NULL)
2432dc36bedSjoris rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
2442dc36bedSjoris else
2452dc36bedSjoris numbuf[0] = '\0';
2462dc36bedSjoris
2472dc36bedSjoris fprintf(fp, "head\t%s;\n", numbuf);
2482dc36bedSjoris
2492dc36bedSjoris if (rfp->rf_branch != NULL) {
2502dc36bedSjoris rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
2512dc36bedSjoris fprintf(fp, "branch\t%s;\n", numbuf);
2522dc36bedSjoris }
2532dc36bedSjoris
2542dc36bedSjoris fputs("access", fp);
2552dc36bedSjoris TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
2562dc36bedSjoris fprintf(fp, "\n\t%s", ap->ra_name);
2572dc36bedSjoris }
2582dc36bedSjoris fputs(";\n", fp);
2592dc36bedSjoris
2602dc36bedSjoris fprintf(fp, "symbols");
2612dc36bedSjoris TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
262f66d1793Stobias if (RCSNUM_ISBRANCH(symp->rs_num))
263f66d1793Stobias rcsnum_addmagic(symp->rs_num);
2642dc36bedSjoris rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
265dffa8ce1Sray fprintf(fp, "\n\t%s:%s", symp->rs_name, numbuf);
2662dc36bedSjoris }
2672dc36bedSjoris fprintf(fp, ";\n");
2682dc36bedSjoris
2692dc36bedSjoris fprintf(fp, "locks");
2702dc36bedSjoris TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
2712dc36bedSjoris rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
2722dc36bedSjoris fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
2732dc36bedSjoris }
2742dc36bedSjoris
2752dc36bedSjoris fprintf(fp, ";");
2762dc36bedSjoris
2772dc36bedSjoris if (rfp->rf_flags & RCS_SLOCK)
2782dc36bedSjoris fprintf(fp, " strict;");
2792dc36bedSjoris fputc('\n', fp);
2802dc36bedSjoris
2812dc36bedSjoris fputs("comment\t@", fp);
2822dc36bedSjoris if (rfp->rf_comment != NULL) {
2832dc36bedSjoris rcs_strprint((const u_char *)rfp->rf_comment,
2842dc36bedSjoris strlen(rfp->rf_comment), fp);
2852dc36bedSjoris fputs("@;\n", fp);
2862dc36bedSjoris } else
2872dc36bedSjoris fputs("# @;\n", fp);
2882dc36bedSjoris
2892dc36bedSjoris if (rfp->rf_expand != NULL) {
2902dc36bedSjoris fputs("expand @", fp);
2912dc36bedSjoris rcs_strprint((const u_char *)rfp->rf_expand,
2922dc36bedSjoris strlen(rfp->rf_expand), fp);
2932dc36bedSjoris fputs("@;\n", fp);
2942dc36bedSjoris }
2952dc36bedSjoris
2962dc36bedSjoris fputs("\n\n", fp);
2972dc36bedSjoris
2982dc36bedSjoris TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
2992dc36bedSjoris fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
3002dc36bedSjoris sizeof(numbuf)));
3012dc36bedSjoris fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
3022dc36bedSjoris rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
3032dc36bedSjoris rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
3042dc36bedSjoris rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
3052dc36bedSjoris fprintf(fp, "\tauthor %s;\tstate %s;\n",
3062dc36bedSjoris rdp->rd_author, rdp->rd_state);
3072dc36bedSjoris fputs("branches", fp);
3082dc36bedSjoris TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
3093081741dStobias fprintf(fp, "\n\t%s", rcsnum_tostr(brp->rb_num, numbuf,
3102dc36bedSjoris sizeof(numbuf)));
3112dc36bedSjoris }
3122dc36bedSjoris fputs(";\n", fp);
3132dc36bedSjoris fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
3142dc36bedSjoris numbuf, sizeof(numbuf)));
3152dc36bedSjoris }
3162dc36bedSjoris
3172dc36bedSjoris fputs("\ndesc\n@", fp);
3182dc36bedSjoris if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
3192dc36bedSjoris rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
3202dc36bedSjoris if (rfp->rf_desc[len-1] != '\n')
3212dc36bedSjoris fputc('\n', fp);
3222dc36bedSjoris }
3232dc36bedSjoris fputs("@\n", fp);
3242dc36bedSjoris
3252dc36bedSjoris /* deltatexts */
3262dc36bedSjoris TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
3272dc36bedSjoris fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
3282dc36bedSjoris sizeof(numbuf)));
3292dc36bedSjoris fputs("log\n@", fp);
3302dc36bedSjoris if (rdp->rd_log != NULL) {
3312dc36bedSjoris len = strlen(rdp->rd_log);
3322dc36bedSjoris rcs_strprint((const u_char *)rdp->rd_log, len, fp);
333a4b26cbaSnicm if (len == 0 || rdp->rd_log[len-1] != '\n')
3342dc36bedSjoris fputc('\n', fp);
3352dc36bedSjoris }
3362dc36bedSjoris fputs("@\ntext\n@", fp);
33714b185ffSniallo if (rdp->rd_text != NULL)
3382dc36bedSjoris rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
3392dc36bedSjoris fputs("@\n", fp);
3402dc36bedSjoris }
34193acdb67Sxsa (void)fclose(fp);
3422dc36bedSjoris
34393acdb67Sxsa if (rcs_movefile(fn, rfp->rf_path, rfp->rf_mode, rfp->rf_flags) == -1) {
34493acdb67Sxsa (void)unlink(fn);
34593acdb67Sxsa errx(1, "rcs_movefile failed");
3462dc36bedSjoris }
3472dc36bedSjoris
3482dc36bedSjoris rfp->rf_flags |= RCS_SYNCED;
3492dc36bedSjoris
3508ac837e5Snicm free(fn);
35193acdb67Sxsa }
35293acdb67Sxsa
35393acdb67Sxsa /*
35493acdb67Sxsa * rcs_movefile()
35593acdb67Sxsa *
35693acdb67Sxsa * Move a file using rename(2) if possible and copying if not.
35793acdb67Sxsa * Returns 0 on success, -1 on failure.
35893acdb67Sxsa */
35993acdb67Sxsa static int
rcs_movefile(char * from,char * to,mode_t perm,u_int to_flags)36093acdb67Sxsa rcs_movefile(char *from, char *to, mode_t perm, u_int to_flags)
36193acdb67Sxsa {
36293acdb67Sxsa FILE *src, *dst;
36393acdb67Sxsa size_t nread, nwritten;
36493acdb67Sxsa char *buf;
36593acdb67Sxsa
36693acdb67Sxsa if (rename(from, to) == 0) {
36793acdb67Sxsa if (chmod(to, perm) == -1) {
36893acdb67Sxsa warn("%s", to);
36993acdb67Sxsa return (-1);
37093acdb67Sxsa }
37193acdb67Sxsa return (0);
37293acdb67Sxsa } else if (errno != EXDEV) {
37393acdb67Sxsa warn("failed to access temp RCS output file");
37493acdb67Sxsa return (-1);
37593acdb67Sxsa }
37693acdb67Sxsa
37793acdb67Sxsa if ((chmod(to, S_IWUSR) == -1) && !(to_flags & RCS_CREATE)) {
37893acdb67Sxsa warnx("chmod(%s, 0%o) failed", to, S_IWUSR);
37993acdb67Sxsa return (-1);
38093acdb67Sxsa }
38193acdb67Sxsa
38293acdb67Sxsa /* different filesystem, have to copy the file */
38393acdb67Sxsa if ((src = fopen(from, "r")) == NULL) {
38493acdb67Sxsa warn("%s", from);
38593acdb67Sxsa return (-1);
38693acdb67Sxsa }
38793acdb67Sxsa if ((dst = fopen(to, "w")) == NULL) {
38893acdb67Sxsa warn("%s", to);
3890c4f1d94Sjasper (void)fclose(src);
39093acdb67Sxsa return (-1);
39193acdb67Sxsa }
39293acdb67Sxsa if (fchmod(fileno(dst), perm)) {
39393acdb67Sxsa warn("%s", to);
39493acdb67Sxsa (void)unlink(to);
3950c4f1d94Sjasper (void)fclose(src);
3960c4f1d94Sjasper (void)fclose(dst);
39793acdb67Sxsa return (-1);
39893acdb67Sxsa }
39993acdb67Sxsa
400*1a0afcdeSderaadt buf = xmalloc(_MAXBSIZE);
401*1a0afcdeSderaadt while ((nread = fread(buf, sizeof(char), _MAXBSIZE, src)) != 0) {
40293acdb67Sxsa if (ferror(src)) {
40393acdb67Sxsa warnx("failed to read `%s'", from);
40493acdb67Sxsa (void)unlink(to);
40593acdb67Sxsa goto out;
40693acdb67Sxsa }
40793acdb67Sxsa nwritten = fwrite(buf, sizeof(char), nread, dst);
40893acdb67Sxsa if (nwritten != nread) {
40993acdb67Sxsa warnx("failed to write `%s'", to);
41093acdb67Sxsa (void)unlink(to);
41193acdb67Sxsa goto out;
41293acdb67Sxsa }
41393acdb67Sxsa }
41493acdb67Sxsa
41593acdb67Sxsa (void)unlink(from);
41693acdb67Sxsa
41793acdb67Sxsa out:
4180c4f1d94Sjasper (void)fclose(src);
4190c4f1d94Sjasper (void)fclose(dst);
4208ac837e5Snicm free(buf);
421cdc530d2Sxsa
42205f23e4eSnicm return (0);
4232dc36bedSjoris }
4242dc36bedSjoris
4252dc36bedSjoris /*
4262dc36bedSjoris * rcs_head_set()
4272dc36bedSjoris *
4282dc36bedSjoris * Set the revision number of the head revision for the RCS file <file> to
4292dc36bedSjoris * <rev>, which must reference a valid revision within the file.
4302dc36bedSjoris */
4312dc36bedSjoris int
rcs_head_set(RCSFILE * file,RCSNUM * rev)4322dc36bedSjoris rcs_head_set(RCSFILE *file, RCSNUM *rev)
4332dc36bedSjoris {
4342dc36bedSjoris if (rcs_findrev(file, rev) == NULL)
4352dc36bedSjoris return (-1);
4362dc36bedSjoris
4372dc36bedSjoris if (file->rf_head == NULL)
4382dc36bedSjoris file->rf_head = rcsnum_alloc();
4392dc36bedSjoris
4402dc36bedSjoris rcsnum_cpy(rev, file->rf_head, 0);
4412dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
4422dc36bedSjoris return (0);
4432dc36bedSjoris }
4442dc36bedSjoris
4452dc36bedSjoris
4462dc36bedSjoris /*
4472dc36bedSjoris * rcs_branch_get()
4482dc36bedSjoris *
4492dc36bedSjoris * Retrieve the default branch number for the RCS file <file>.
4502dc36bedSjoris * Returns the number on success. If NULL is returned, then there is no
4512dc36bedSjoris * default branch for this file.
4522dc36bedSjoris */
4532dc36bedSjoris const RCSNUM *
rcs_branch_get(RCSFILE * file)4542dc36bedSjoris rcs_branch_get(RCSFILE *file)
4552dc36bedSjoris {
4562dc36bedSjoris return (file->rf_branch);
4572dc36bedSjoris }
4582dc36bedSjoris
4592dc36bedSjoris /*
4602dc36bedSjoris * rcs_access_add()
4612dc36bedSjoris *
4622dc36bedSjoris * Add the login name <login> to the access list for the RCS file <file>.
4632dc36bedSjoris * Returns 0 on success, or -1 on failure.
4642dc36bedSjoris */
4652dc36bedSjoris int
rcs_access_add(RCSFILE * file,const char * login)4662dc36bedSjoris rcs_access_add(RCSFILE *file, const char *login)
4672dc36bedSjoris {
4682dc36bedSjoris struct rcs_access *ap;
4692dc36bedSjoris
4702dc36bedSjoris /* first look for duplication */
4712dc36bedSjoris TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
4722dc36bedSjoris if (strcmp(ap->ra_name, login) == 0) {
4732dc36bedSjoris rcs_errno = RCS_ERR_DUPENT;
4742dc36bedSjoris return (-1);
4752dc36bedSjoris }
4762dc36bedSjoris }
4772dc36bedSjoris
4782dc36bedSjoris ap = xmalloc(sizeof(*ap));
4792dc36bedSjoris ap->ra_name = xstrdup(login);
4802dc36bedSjoris TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
4812dc36bedSjoris
4822dc36bedSjoris /* not synced anymore */
4832dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
4842dc36bedSjoris return (0);
4852dc36bedSjoris }
4862dc36bedSjoris
4872dc36bedSjoris /*
4882dc36bedSjoris * rcs_access_remove()
4892dc36bedSjoris *
4902dc36bedSjoris * Remove an entry with login name <login> from the access list of the RCS
4912dc36bedSjoris * file <file>.
4922dc36bedSjoris * Returns 0 on success, or -1 on failure.
4932dc36bedSjoris */
4942dc36bedSjoris int
rcs_access_remove(RCSFILE * file,const char * login)4952dc36bedSjoris rcs_access_remove(RCSFILE *file, const char *login)
4962dc36bedSjoris {
4972dc36bedSjoris struct rcs_access *ap;
4982dc36bedSjoris
4992dc36bedSjoris TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
5002dc36bedSjoris if (strcmp(ap->ra_name, login) == 0)
5012dc36bedSjoris break;
5022dc36bedSjoris
5032dc36bedSjoris if (ap == NULL) {
5042dc36bedSjoris rcs_errno = RCS_ERR_NOENT;
5052dc36bedSjoris return (-1);
5062dc36bedSjoris }
5072dc36bedSjoris
5082dc36bedSjoris TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
5098ac837e5Snicm free(ap->ra_name);
5108ac837e5Snicm free(ap);
5112dc36bedSjoris
5122dc36bedSjoris /* not synced anymore */
5132dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
5142dc36bedSjoris return (0);
5152dc36bedSjoris }
5162dc36bedSjoris
5172dc36bedSjoris /*
5182dc36bedSjoris * rcs_sym_add()
5192dc36bedSjoris *
5202dc36bedSjoris * Add a symbol to the list of symbols for the RCS file <rfp>. The new symbol
5212dc36bedSjoris * is named <sym> and is bound to the RCS revision <snum>.
5222dc36bedSjoris * Returns 0 on success, or -1 on failure.
5232dc36bedSjoris */
5242dc36bedSjoris int
rcs_sym_add(RCSFILE * rfp,const char * sym,RCSNUM * snum)5252dc36bedSjoris rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
5262dc36bedSjoris {
5272dc36bedSjoris struct rcs_sym *symp;
5282dc36bedSjoris
5292dc36bedSjoris if (!rcs_sym_check(sym)) {
5302dc36bedSjoris rcs_errno = RCS_ERR_BADSYM;
5312dc36bedSjoris return (-1);
5322dc36bedSjoris }
5332dc36bedSjoris
5342dc36bedSjoris /* first look for duplication */
5352dc36bedSjoris TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
5362dc36bedSjoris if (strcmp(symp->rs_name, sym) == 0) {
5372dc36bedSjoris rcs_errno = RCS_ERR_DUPENT;
5382dc36bedSjoris return (-1);
5392dc36bedSjoris }
5402dc36bedSjoris }
5412dc36bedSjoris
5422dc36bedSjoris symp = xmalloc(sizeof(*symp));
5432dc36bedSjoris symp->rs_name = xstrdup(sym);
5442dc36bedSjoris symp->rs_num = rcsnum_alloc();
5452dc36bedSjoris rcsnum_cpy(snum, symp->rs_num, 0);
5462dc36bedSjoris
5472dc36bedSjoris TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
5482dc36bedSjoris
5492dc36bedSjoris /* not synced anymore */
5502dc36bedSjoris rfp->rf_flags &= ~RCS_SYNCED;
5512dc36bedSjoris return (0);
5522dc36bedSjoris }
5532dc36bedSjoris
5542dc36bedSjoris /*
5552dc36bedSjoris * rcs_sym_remove()
5562dc36bedSjoris *
5572dc36bedSjoris * Remove the symbol with name <sym> from the symbol list for the RCS file
5582dc36bedSjoris * <file>. If no such symbol is found, the call fails and returns with an
5592dc36bedSjoris * error.
5602dc36bedSjoris * Returns 0 on success, or -1 on failure.
5612dc36bedSjoris */
5622dc36bedSjoris int
rcs_sym_remove(RCSFILE * file,const char * sym)5632dc36bedSjoris rcs_sym_remove(RCSFILE *file, const char *sym)
5642dc36bedSjoris {
5652dc36bedSjoris struct rcs_sym *symp;
5662dc36bedSjoris
5672dc36bedSjoris if (!rcs_sym_check(sym)) {
5682dc36bedSjoris rcs_errno = RCS_ERR_BADSYM;
5692dc36bedSjoris return (-1);
5702dc36bedSjoris }
5712dc36bedSjoris
5722dc36bedSjoris TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
5732dc36bedSjoris if (strcmp(symp->rs_name, sym) == 0)
5742dc36bedSjoris break;
5752dc36bedSjoris
5762dc36bedSjoris if (symp == NULL) {
5772dc36bedSjoris rcs_errno = RCS_ERR_NOENT;
5782dc36bedSjoris return (-1);
5792dc36bedSjoris }
5802dc36bedSjoris
5812dc36bedSjoris TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
5828ac837e5Snicm free(symp->rs_name);
5832dc36bedSjoris rcsnum_free(symp->rs_num);
5848ac837e5Snicm free(symp);
5852dc36bedSjoris
5862dc36bedSjoris /* not synced anymore */
5872dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
5882dc36bedSjoris return (0);
5892dc36bedSjoris }
5902dc36bedSjoris
5912dc36bedSjoris /*
5922dc36bedSjoris * rcs_sym_getrev()
5932dc36bedSjoris *
5942dc36bedSjoris * Retrieve the RCS revision number associated with the symbol <sym> for the
5952dc36bedSjoris * RCS file <file>. The returned value is a dynamically-allocated copy and
5962dc36bedSjoris * should be freed by the caller once they are done with it.
5972dc36bedSjoris * Returns the RCSNUM on success, or NULL on failure.
5982dc36bedSjoris */
5992dc36bedSjoris RCSNUM *
rcs_sym_getrev(RCSFILE * file,const char * sym)6002dc36bedSjoris rcs_sym_getrev(RCSFILE *file, const char *sym)
6012dc36bedSjoris {
6022dc36bedSjoris RCSNUM *num;
6032dc36bedSjoris struct rcs_sym *symp;
6042dc36bedSjoris
6052dc36bedSjoris if (!rcs_sym_check(sym)) {
6062dc36bedSjoris rcs_errno = RCS_ERR_BADSYM;
6072dc36bedSjoris return (NULL);
6082dc36bedSjoris }
6092dc36bedSjoris
6102dc36bedSjoris num = NULL;
6112dc36bedSjoris TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
6122dc36bedSjoris if (strcmp(symp->rs_name, sym) == 0)
6132dc36bedSjoris break;
6142dc36bedSjoris
6152dc36bedSjoris if (symp == NULL) {
6162dc36bedSjoris rcs_errno = RCS_ERR_NOENT;
6172dc36bedSjoris } else {
6182dc36bedSjoris num = rcsnum_alloc();
6192dc36bedSjoris rcsnum_cpy(symp->rs_num, num, 0);
6202dc36bedSjoris }
6212dc36bedSjoris
6222dc36bedSjoris return (num);
6232dc36bedSjoris }
6242dc36bedSjoris
6252dc36bedSjoris /*
6262dc36bedSjoris * rcs_sym_check()
6272dc36bedSjoris *
6282dc36bedSjoris * Check the RCS symbol name <sym> for any unsupported characters.
6292dc36bedSjoris * Returns 1 if the tag is correct, 0 if it isn't valid.
6302dc36bedSjoris */
6312dc36bedSjoris int
rcs_sym_check(const char * sym)6322dc36bedSjoris rcs_sym_check(const char *sym)
6332dc36bedSjoris {
6342dc36bedSjoris int ret;
63503829ff5Sderaadt const unsigned char *cp;
6362dc36bedSjoris
6372dc36bedSjoris ret = 1;
6382dc36bedSjoris cp = sym;
6392dc36bedSjoris if (!isalpha(*cp++))
6402dc36bedSjoris return (0);
6412dc36bedSjoris
6422dc36bedSjoris for (; *cp != '\0'; cp++)
6432dc36bedSjoris if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
6442dc36bedSjoris ret = 0;
6452dc36bedSjoris break;
6462dc36bedSjoris }
6472dc36bedSjoris
6482dc36bedSjoris return (ret);
6492dc36bedSjoris }
6502dc36bedSjoris
6512dc36bedSjoris /*
6522dc36bedSjoris * rcs_lock_getmode()
6532dc36bedSjoris *
6542dc36bedSjoris * Retrieve the locking mode of the RCS file <file>.
6552dc36bedSjoris */
6562dc36bedSjoris int
rcs_lock_getmode(RCSFILE * file)6572dc36bedSjoris rcs_lock_getmode(RCSFILE *file)
6582dc36bedSjoris {
6592dc36bedSjoris return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
6602dc36bedSjoris }
6612dc36bedSjoris
6622dc36bedSjoris /*
6632dc36bedSjoris * rcs_lock_setmode()
6642dc36bedSjoris *
6652dc36bedSjoris * Set the locking mode of the RCS file <file> to <mode>, which must either
6662dc36bedSjoris * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
6672dc36bedSjoris * Returns the previous mode on success, or -1 on failure.
6682dc36bedSjoris */
6692dc36bedSjoris int
rcs_lock_setmode(RCSFILE * file,int mode)6702dc36bedSjoris rcs_lock_setmode(RCSFILE *file, int mode)
6712dc36bedSjoris {
6722dc36bedSjoris int pmode;
6732dc36bedSjoris pmode = rcs_lock_getmode(file);
6742dc36bedSjoris
6752dc36bedSjoris if (mode == RCS_LOCK_STRICT)
6762dc36bedSjoris file->rf_flags |= RCS_SLOCK;
6772dc36bedSjoris else if (mode == RCS_LOCK_LOOSE)
6782dc36bedSjoris file->rf_flags &= ~RCS_SLOCK;
6792dc36bedSjoris else
6802dc36bedSjoris errx(1, "rcs_lock_setmode: invalid mode `%d'", mode);
6812dc36bedSjoris
6822dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
6832dc36bedSjoris return (pmode);
6842dc36bedSjoris }
6852dc36bedSjoris
6862dc36bedSjoris /*
6872dc36bedSjoris * rcs_lock_add()
6882dc36bedSjoris *
6892dc36bedSjoris * Add an RCS lock for the user <user> on revision <rev>.
6902dc36bedSjoris * Returns 0 on success, or -1 on failure.
6912dc36bedSjoris */
6922dc36bedSjoris int
rcs_lock_add(RCSFILE * file,const char * user,RCSNUM * rev)6932dc36bedSjoris rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
6942dc36bedSjoris {
6952dc36bedSjoris struct rcs_lock *lkp;
696b519e364Sjoris struct rcs_delta *rdp;
697b519e364Sjoris
698b519e364Sjoris if ((rdp = rcs_findrev(file, rev)) == NULL) {
699b519e364Sjoris rcs_errno = RCS_ERR_NOENT;
700b519e364Sjoris return (-1);
701b519e364Sjoris }
7022dc36bedSjoris
7032dc36bedSjoris /* first look for duplication */
7042dc36bedSjoris TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
7052dc36bedSjoris if (strcmp(lkp->rl_name, user) == 0 &&
7062dc36bedSjoris rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
7072dc36bedSjoris rcs_errno = RCS_ERR_DUPENT;
7082dc36bedSjoris return (-1);
7092dc36bedSjoris }
7102dc36bedSjoris }
7112dc36bedSjoris
7122dc36bedSjoris lkp = xmalloc(sizeof(*lkp));
7132dc36bedSjoris lkp->rl_name = xstrdup(user);
7142dc36bedSjoris lkp->rl_num = rcsnum_alloc();
7152dc36bedSjoris rcsnum_cpy(rev, lkp->rl_num, 0);
7162dc36bedSjoris
717b519e364Sjoris free(rdp->rd_locker);
718b519e364Sjoris rdp->rd_locker = xstrdup(user);
719b519e364Sjoris
7202dc36bedSjoris TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
7212dc36bedSjoris
7222dc36bedSjoris /* not synced anymore */
7232dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
7242dc36bedSjoris return (0);
7252dc36bedSjoris }
7262dc36bedSjoris
7272dc36bedSjoris
7282dc36bedSjoris /*
7292dc36bedSjoris * rcs_lock_remove()
7302dc36bedSjoris *
7312dc36bedSjoris * Remove the RCS lock on revision <rev>.
7322dc36bedSjoris * Returns 0 on success, or -1 on failure.
7332dc36bedSjoris */
7342dc36bedSjoris int
rcs_lock_remove(RCSFILE * file,const char * user,RCSNUM * rev)7352dc36bedSjoris rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
7362dc36bedSjoris {
7372dc36bedSjoris struct rcs_lock *lkp;
738b519e364Sjoris struct rcs_delta *rdp;
739b519e364Sjoris
740b519e364Sjoris if ((rdp = rcs_findrev(file, rev)) == NULL) {
741b519e364Sjoris rcs_errno = RCS_ERR_NOENT;
742b519e364Sjoris return (-1);
743b519e364Sjoris }
7442dc36bedSjoris
7452dc36bedSjoris TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
7462dc36bedSjoris if (strcmp(lkp->rl_name, user) == 0 &&
7472dc36bedSjoris rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
7482dc36bedSjoris break;
7492dc36bedSjoris }
7502dc36bedSjoris
7512dc36bedSjoris if (lkp == NULL) {
7522dc36bedSjoris rcs_errno = RCS_ERR_NOENT;
7532dc36bedSjoris return (-1);
7542dc36bedSjoris }
7552dc36bedSjoris
7562dc36bedSjoris TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
7572dc36bedSjoris rcsnum_free(lkp->rl_num);
7588ac837e5Snicm free(lkp->rl_name);
7598ac837e5Snicm free(lkp);
7602dc36bedSjoris
761b519e364Sjoris free(rdp->rd_locker);
762b519e364Sjoris rdp->rd_locker = NULL;
763b519e364Sjoris
7642dc36bedSjoris /* not synced anymore */
7652dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
7662dc36bedSjoris return (0);
7672dc36bedSjoris }
7682dc36bedSjoris
7692dc36bedSjoris /*
7702dc36bedSjoris * rcs_desc_set()
7712dc36bedSjoris *
7722dc36bedSjoris * Set the description for the RCS file <file>.
7732dc36bedSjoris */
7742dc36bedSjoris void
rcs_desc_set(RCSFILE * file,const char * desc)7752dc36bedSjoris rcs_desc_set(RCSFILE *file, const char *desc)
7762dc36bedSjoris {
7772dc36bedSjoris char *tmp;
7782dc36bedSjoris
7792dc36bedSjoris tmp = xstrdup(desc);
7808ac837e5Snicm free(file->rf_desc);
7812dc36bedSjoris file->rf_desc = tmp;
7822dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
7832dc36bedSjoris }
7842dc36bedSjoris
7852dc36bedSjoris /*
7862dc36bedSjoris * rcs_comment_set()
7872dc36bedSjoris *
7882dc36bedSjoris * Set the comment leader for the RCS file <file>.
7892dc36bedSjoris */
7902dc36bedSjoris void
rcs_comment_set(RCSFILE * file,const char * comment)7912dc36bedSjoris rcs_comment_set(RCSFILE *file, const char *comment)
7922dc36bedSjoris {
7932dc36bedSjoris char *tmp;
7942dc36bedSjoris
7952dc36bedSjoris tmp = xstrdup(comment);
7968ac837e5Snicm free(file->rf_comment);
7972dc36bedSjoris file->rf_comment = tmp;
7982dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
7992dc36bedSjoris }
8002dc36bedSjoris
8012dc36bedSjoris int
rcs_patch_lines(struct rcs_lines * dlines,struct rcs_lines * plines)8022dc36bedSjoris rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
8032dc36bedSjoris {
8042dc36bedSjoris char op, *ep;
8052dc36bedSjoris struct rcs_line *lp, *dlp, *ndlp;
8062dc36bedSjoris int i, lineno, nbln;
80714b185ffSniallo u_char tmp;
8082dc36bedSjoris
8092dc36bedSjoris dlp = TAILQ_FIRST(&(dlines->l_lines));
8102dc36bedSjoris lp = TAILQ_FIRST(&(plines->l_lines));
8112dc36bedSjoris
8122dc36bedSjoris /* skip first bogus line */
8132dc36bedSjoris for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
8142dc36bedSjoris lp = TAILQ_NEXT(lp, l_list)) {
81514b185ffSniallo if (lp->l_len < 2)
81614b185ffSniallo errx(1, "line too short, RCS patch seems broken");
8172dc36bedSjoris op = *(lp->l_line);
81814b185ffSniallo /* NUL-terminate line buffer for strtol() safety. */
81914b185ffSniallo tmp = lp->l_line[lp->l_len - 1];
82014b185ffSniallo lp->l_line[lp->l_len - 1] = '\0';
8212dc36bedSjoris lineno = (int)strtol((lp->l_line + 1), &ep, 10);
8222dc36bedSjoris if (lineno > dlines->l_nblines || lineno < 0 ||
8232dc36bedSjoris *ep != ' ')
8242dc36bedSjoris errx(1, "invalid line specification in RCS patch");
8252dc36bedSjoris ep++;
8262dc36bedSjoris nbln = (int)strtol(ep, &ep, 10);
82714b185ffSniallo /* Restore the last byte of the buffer */
82814b185ffSniallo lp->l_line[lp->l_len - 1] = tmp;
82914b185ffSniallo if (nbln < 0)
8302dc36bedSjoris errx(1,
8312dc36bedSjoris "invalid line number specification in RCS patch");
8322dc36bedSjoris
8332dc36bedSjoris /* find the appropriate line */
8342dc36bedSjoris for (;;) {
8352dc36bedSjoris if (dlp == NULL)
8362dc36bedSjoris break;
8372dc36bedSjoris if (dlp->l_lineno == lineno)
8382dc36bedSjoris break;
8392dc36bedSjoris if (dlp->l_lineno > lineno) {
8407a9e6d11Sray dlp = TAILQ_PREV(dlp, tqh, l_list);
8412dc36bedSjoris } else if (dlp->l_lineno < lineno) {
8422dc36bedSjoris if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
8432dc36bedSjoris ndlp->l_lineno > lineno)
8442dc36bedSjoris break;
8452dc36bedSjoris dlp = ndlp;
8462dc36bedSjoris }
8472dc36bedSjoris }
8482dc36bedSjoris if (dlp == NULL)
8492dc36bedSjoris errx(1, "can't find referenced line in RCS patch");
8502dc36bedSjoris
8512dc36bedSjoris if (op == 'd') {
8522dc36bedSjoris for (i = 0; (i < nbln) && (dlp != NULL); i++) {
8532dc36bedSjoris ndlp = TAILQ_NEXT(dlp, l_list);
8542dc36bedSjoris TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
8558ac837e5Snicm free(dlp);
8562dc36bedSjoris dlp = ndlp;
8572dc36bedSjoris /* last line is gone - reset dlp */
8582dc36bedSjoris if (dlp == NULL) {
8592dc36bedSjoris ndlp = TAILQ_LAST(&(dlines->l_lines),
8607a9e6d11Sray tqh);
8612dc36bedSjoris dlp = ndlp;
8622dc36bedSjoris }
8632dc36bedSjoris }
8642dc36bedSjoris } else if (op == 'a') {
8652dc36bedSjoris for (i = 0; i < nbln; i++) {
8662dc36bedSjoris ndlp = lp;
8672dc36bedSjoris lp = TAILQ_NEXT(lp, l_list);
8682dc36bedSjoris if (lp == NULL)
8692dc36bedSjoris errx(1, "truncated RCS patch");
8702dc36bedSjoris TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
8712dc36bedSjoris TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
8722dc36bedSjoris lp, l_list);
8732dc36bedSjoris dlp = lp;
8742dc36bedSjoris
8752dc36bedSjoris /* we don't want lookup to block on those */
8762dc36bedSjoris lp->l_lineno = lineno;
8772dc36bedSjoris
8782dc36bedSjoris lp = ndlp;
8792dc36bedSjoris }
8802dc36bedSjoris } else
8812dc36bedSjoris errx(1, "unknown RCS patch operation `%c'", op);
8822dc36bedSjoris
8832dc36bedSjoris /* last line of the patch, done */
8842dc36bedSjoris if (lp->l_lineno == plines->l_nblines)
8852dc36bedSjoris break;
8862dc36bedSjoris }
8872dc36bedSjoris
8882dc36bedSjoris /* once we're done patching, rebuild the line numbers */
8892dc36bedSjoris lineno = 0;
8902dc36bedSjoris TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
8912dc36bedSjoris lp->l_lineno = lineno++;
8922dc36bedSjoris dlines->l_nblines = lineno - 1;
8932dc36bedSjoris
8942dc36bedSjoris return (0);
8952dc36bedSjoris }
8962dc36bedSjoris
8972dc36bedSjoris /*
8982dc36bedSjoris * rcs_getrev()
8992dc36bedSjoris *
9002dc36bedSjoris * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The
9012dc36bedSjoris * returned buffer is dynamically allocated and should be released using
9027bb3ddb0Sray * buf_free() once the caller is done using it.
9032dc36bedSjoris */
9042dc36bedSjoris BUF *
rcs_getrev(RCSFILE * rfp,RCSNUM * frev)9052dc36bedSjoris rcs_getrev(RCSFILE *rfp, RCSNUM *frev)
9062dc36bedSjoris {
9072dc36bedSjoris u_int i, numlen;
908f0e090bfSniallo int isbranch, lookonbranch, found;
90914b185ffSniallo size_t dlen, plen, len;
9102dc36bedSjoris RCSNUM *crev, *rev, *brev;
91114b185ffSniallo BUF *rbuf;
9122dc36bedSjoris struct rcs_delta *rdp = NULL;
9132dc36bedSjoris struct rcs_branch *rb;
91414b185ffSniallo u_char *data, *patch;
9152dc36bedSjoris
9162dc36bedSjoris if (rfp->rf_head == NULL)
9172dc36bedSjoris return (NULL);
9182dc36bedSjoris
9192dc36bedSjoris if (frev == RCS_HEAD_REV)
9202dc36bedSjoris rev = rfp->rf_head;
9212dc36bedSjoris else
9222dc36bedSjoris rev = frev;
9232dc36bedSjoris
9242dc36bedSjoris /* XXX rcsnum_cmp() */
9252dc36bedSjoris for (i = 0; i < rfp->rf_head->rn_len; i++) {
9262dc36bedSjoris if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) {
9272dc36bedSjoris rcs_errno = RCS_ERR_NOENT;
9282dc36bedSjoris return (NULL);
9292dc36bedSjoris }
9302dc36bedSjoris }
9312dc36bedSjoris
9329f1e7c38Sjj /* No matter what, we'll need everything parsed up until the description
9339f1e7c38Sjj so go for it. */
934fead43dfStobias if (rcsparse_deltas(rfp, NULL))
935fead43dfStobias return (NULL);
9362dc36bedSjoris
9372dc36bedSjoris rdp = rcs_findrev(rfp, rfp->rf_head);
9382dc36bedSjoris if (rdp == NULL) {
9392dc36bedSjoris warnx("failed to get RCS HEAD revision");
9402dc36bedSjoris return (NULL);
9412dc36bedSjoris }
9422dc36bedSjoris
9432dc36bedSjoris if (rdp->rd_tlen == 0)
944fead43dfStobias if (rcsparse_deltatexts(rfp, rfp->rf_head))
945fead43dfStobias return (NULL);
9462dc36bedSjoris
9472dc36bedSjoris len = rdp->rd_tlen;
9482dc36bedSjoris if (len == 0) {
9490ee14128Sray rbuf = buf_alloc(1);
9507bb3ddb0Sray buf_empty(rbuf);
9512dc36bedSjoris return (rbuf);
9522dc36bedSjoris }
9532dc36bedSjoris
9540ee14128Sray rbuf = buf_alloc(len);
9557bb3ddb0Sray buf_append(rbuf, rdp->rd_text, len);
9562dc36bedSjoris
9572dc36bedSjoris isbranch = 0;
9582dc36bedSjoris brev = NULL;
9592dc36bedSjoris
9602dc36bedSjoris /*
9612dc36bedSjoris * If a branch was passed, get the latest revision on it.
9622dc36bedSjoris */
9632dc36bedSjoris if (RCSNUM_ISBRANCH(rev)) {
9642dc36bedSjoris brev = rev;
9652dc36bedSjoris rdp = rcs_findrev(rfp, rev);
96620a75c54Snicm if (rdp == NULL) {
96720a75c54Snicm buf_free(rbuf);
9682dc36bedSjoris return (NULL);
96920a75c54Snicm }
9702dc36bedSjoris
9712dc36bedSjoris rev = rdp->rd_num;
9722dc36bedSjoris } else {
9732dc36bedSjoris if (RCSNUM_ISBRANCHREV(rev)) {
9742dc36bedSjoris brev = rcsnum_revtobr(rev);
9752dc36bedSjoris isbranch = 1;
9762dc36bedSjoris }
9772dc36bedSjoris }
9782dc36bedSjoris
9792dc36bedSjoris lookonbranch = 0;
9802dc36bedSjoris crev = NULL;
9812dc36bedSjoris
9822dc36bedSjoris /* Apply patches backwards to get the right version.
9832dc36bedSjoris */
9842dc36bedSjoris do {
985f0e090bfSniallo found = 0;
986f0e090bfSniallo
9872dc36bedSjoris if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0)
9882dc36bedSjoris break;
9892dc36bedSjoris
9902dc36bedSjoris if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len &&
9912dc36bedSjoris !TAILQ_EMPTY(&(rdp->rd_branches)))
9922dc36bedSjoris lookonbranch = 1;
9932dc36bedSjoris
9942dc36bedSjoris if (isbranch && lookonbranch == 1) {
9952dc36bedSjoris lookonbranch = 0;
9962dc36bedSjoris TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
9972dc36bedSjoris /* XXX rcsnum_cmp() is totally broken for
9982dc36bedSjoris * this purpose.
9992dc36bedSjoris */
1000b9fc9a72Sderaadt numlen = MINIMUM(brev->rn_len,
1001b88e6b7bStobias rb->rb_num->rn_len - 1);
10022dc36bedSjoris for (i = 0; i < numlen; i++) {
10032dc36bedSjoris if (rb->rb_num->rn_id[i] !=
10042dc36bedSjoris brev->rn_id[i])
10052dc36bedSjoris break;
10062dc36bedSjoris }
10072dc36bedSjoris
10082dc36bedSjoris if (i == numlen) {
10092dc36bedSjoris crev = rb->rb_num;
1010f0e090bfSniallo found = 1;
10112dc36bedSjoris break;
10122dc36bedSjoris }
10132dc36bedSjoris }
1014f0e090bfSniallo if (found == 0)
1015f0e090bfSniallo crev = rdp->rd_next;
10162dc36bedSjoris } else {
10172dc36bedSjoris crev = rdp->rd_next;
10182dc36bedSjoris }
10192dc36bedSjoris
10202dc36bedSjoris rdp = rcs_findrev(rfp, crev);
10212dc36bedSjoris if (rdp == NULL) {
10227bb3ddb0Sray buf_free(rbuf);
10232dc36bedSjoris return (NULL);
10242dc36bedSjoris }
10252dc36bedSjoris
102614b185ffSniallo plen = rdp->rd_tlen;
10277bb3ddb0Sray dlen = buf_len(rbuf);
102814b185ffSniallo patch = rdp->rd_text;
10297bb3ddb0Sray data = buf_release(rbuf);
10302dc36bedSjoris /* check if we have parsed this rev's deltatext */
10312dc36bedSjoris if (rdp->rd_tlen == 0)
1032fead43dfStobias if (rcsparse_deltatexts(rfp, rdp->rd_num))
1033fead43dfStobias return (NULL);
10342dc36bedSjoris
103514b185ffSniallo rbuf = rcs_patchfile(data, dlen, patch, plen, rcs_patch_lines);
10368ac837e5Snicm free(data);
10372dc36bedSjoris
10382dc36bedSjoris if (rbuf == NULL)
10392dc36bedSjoris break;
10402dc36bedSjoris } while (rcsnum_cmp(crev, rev, 0) != 0);
10412dc36bedSjoris
10422dc36bedSjoris return (rbuf);
10432dc36bedSjoris }
10442dc36bedSjoris
1045be0b58deSxsa void
rcs_delta_stats(struct rcs_delta * rdp,int * ladded,int * lremoved)1046be0b58deSxsa rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved)
1047be0b58deSxsa {
1048be0b58deSxsa struct rcs_lines *plines;
1049be0b58deSxsa struct rcs_line *lp;
1050cba671c4Sjasper int added, i, nbln, removed;
1051be0b58deSxsa char op, *ep;
1052be0b58deSxsa u_char tmp;
1053be0b58deSxsa
1054be0b58deSxsa added = removed = 0;
1055be0b58deSxsa
1056be0b58deSxsa plines = rcs_splitlines(rdp->rd_text, rdp->rd_tlen);
1057be0b58deSxsa lp = TAILQ_FIRST(&(plines->l_lines));
1058be0b58deSxsa
1059be0b58deSxsa /* skip first bogus line */
1060be0b58deSxsa for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
1061be0b58deSxsa lp = TAILQ_NEXT(lp, l_list)) {
1062be0b58deSxsa if (lp->l_len < 2)
1063be0b58deSxsa errx(1,
1064be0b58deSxsa "line too short, RCS patch seems broken");
1065be0b58deSxsa op = *(lp->l_line);
1066be0b58deSxsa /* NUL-terminate line buffer for strtol() safety. */
1067be0b58deSxsa tmp = lp->l_line[lp->l_len - 1];
1068be0b58deSxsa lp->l_line[lp->l_len - 1] = '\0';
106932cdf4a0Sdjm (void)strtol((lp->l_line + 1), &ep, 10);
1070be0b58deSxsa ep++;
1071be0b58deSxsa nbln = (int)strtol(ep, &ep, 10);
1072be0b58deSxsa /* Restore the last byte of the buffer */
1073be0b58deSxsa lp->l_line[lp->l_len - 1] = tmp;
1074be0b58deSxsa if (nbln < 0)
1075be0b58deSxsa errx(1, "invalid line number specification "
1076be0b58deSxsa "in RCS patch");
1077be0b58deSxsa
1078be0b58deSxsa if (op == 'a') {
1079be0b58deSxsa added += nbln;
1080be0b58deSxsa for (i = 0; i < nbln; i++) {
1081be0b58deSxsa lp = TAILQ_NEXT(lp, l_list);
1082be0b58deSxsa if (lp == NULL)
1083be0b58deSxsa errx(1, "truncated RCS patch");
1084be0b58deSxsa }
1085be0b58deSxsa } else if (op == 'd')
1086be0b58deSxsa removed += nbln;
1087be0b58deSxsa else
1088be0b58deSxsa errx(1, "unknown RCS patch operation '%c'", op);
1089be0b58deSxsa }
1090be0b58deSxsa
1091e0641964Stobias rcs_freelines(plines);
1092e0641964Stobias
1093be0b58deSxsa *ladded = added;
1094be0b58deSxsa *lremoved = removed;
1095be0b58deSxsa }
1096be0b58deSxsa
10972dc36bedSjoris /*
10982dc36bedSjoris * rcs_rev_add()
10992dc36bedSjoris *
11002dc36bedSjoris * Add a revision to the RCS file <rf>. The new revision's number can be
11012dc36bedSjoris * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
11022dc36bedSjoris * new revision will have a number equal to the previous head revision plus
11032dc36bedSjoris * one). The <msg> argument specifies the log message for that revision, and
11042dc36bedSjoris * <date> specifies the revision's date (a value of -1 is
11052dc36bedSjoris * equivalent to using the current time).
11069d97bd6aSray * If <author> is NULL, set the author for this revision to the current user.
11072dc36bedSjoris * Returns 0 on success, or -1 on failure.
11082dc36bedSjoris */
11092dc36bedSjoris int
rcs_rev_add(RCSFILE * rf,RCSNUM * rev,const char * msg,time_t date,const char * author)11102dc36bedSjoris rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
11119d97bd6aSray const char *author)
11122dc36bedSjoris {
11132dc36bedSjoris time_t now;
11142dc36bedSjoris struct passwd *pw;
11152dc36bedSjoris struct rcs_delta *ordp, *rdp;
11162dc36bedSjoris
11172dc36bedSjoris if (rev == RCS_HEAD_REV) {
11182dc36bedSjoris if (rf->rf_flags & RCS_CREATE) {
11192dc36bedSjoris if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
11202dc36bedSjoris return (-1);
1121509a3673Stobias rf->rf_head = rev;
11222dc36bedSjoris } else {
11232dc36bedSjoris rev = rcsnum_inc(rf->rf_head);
11242dc36bedSjoris }
11252dc36bedSjoris } else {
11262dc36bedSjoris if ((rdp = rcs_findrev(rf, rev)) != NULL) {
11272dc36bedSjoris rcs_errno = RCS_ERR_DUPENT;
11282dc36bedSjoris return (-1);
11292dc36bedSjoris }
11302dc36bedSjoris }
11312dc36bedSjoris
11322dc36bedSjoris rdp = xcalloc(1, sizeof(*rdp));
11332dc36bedSjoris
11342dc36bedSjoris TAILQ_INIT(&(rdp->rd_branches));
11352dc36bedSjoris
11362dc36bedSjoris rdp->rd_num = rcsnum_alloc();
11372dc36bedSjoris rcsnum_cpy(rev, rdp->rd_num, 0);
11382dc36bedSjoris
11392dc36bedSjoris rdp->rd_next = rcsnum_alloc();
11402dc36bedSjoris
11412dc36bedSjoris if (!(rf->rf_flags & RCS_CREATE)) {
11422dc36bedSjoris /* next should point to the previous HEAD */
11432dc36bedSjoris ordp = TAILQ_FIRST(&(rf->rf_delta));
11442dc36bedSjoris rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
11452dc36bedSjoris }
11462dc36bedSjoris
11479d97bd6aSray if (!author && !(author = getlogin())) {
11489d97bd6aSray if (!(pw = getpwuid(getuid())))
11499d97bd6aSray errx(1, "getpwuid failed");
11509d97bd6aSray author = pw->pw_name;
11519d97bd6aSray }
11529d97bd6aSray rdp->rd_author = xstrdup(author);
11532dc36bedSjoris rdp->rd_state = xstrdup(RCS_STATE_EXP);
11542dc36bedSjoris rdp->rd_log = xstrdup(msg);
11552dc36bedSjoris
11562dc36bedSjoris if (date != (time_t)(-1))
11572dc36bedSjoris now = date;
11582dc36bedSjoris else
11592dc36bedSjoris time(&now);
11602dc36bedSjoris gmtime_r(&now, &(rdp->rd_date));
11612dc36bedSjoris
11622dc36bedSjoris TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
11632dc36bedSjoris rf->rf_ndelta++;
11642dc36bedSjoris
11652dc36bedSjoris /* not synced anymore */
11662dc36bedSjoris rf->rf_flags &= ~RCS_SYNCED;
11672dc36bedSjoris
11682dc36bedSjoris return (0);
11692dc36bedSjoris }
11702dc36bedSjoris
11712dc36bedSjoris /*
11722dc36bedSjoris * rcs_rev_remove()
11732dc36bedSjoris *
11742dc36bedSjoris * Remove the revision whose number is <rev> from the RCS file <rf>.
11752dc36bedSjoris */
11762dc36bedSjoris int
rcs_rev_remove(RCSFILE * rf,RCSNUM * rev)11772dc36bedSjoris rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
11782dc36bedSjoris {
11793de4517bSniallo char *path_tmp1, *path_tmp2;
11802dc36bedSjoris struct rcs_delta *rdp, *prevrdp, *nextrdp;
11813de4517bSniallo BUF *newdeltatext, *nextbuf, *prevbuf, *newdiff;
11823de4517bSniallo
11833de4517bSniallo nextrdp = prevrdp = NULL;
1184f4a30fb9Sniallo path_tmp1 = path_tmp2 = NULL;
11852dc36bedSjoris
11862dc36bedSjoris if (rev == RCS_HEAD_REV)
11872dc36bedSjoris rev = rf->rf_head;
11882dc36bedSjoris
11892dc36bedSjoris /* do we actually have that revision? */
11902dc36bedSjoris if ((rdp = rcs_findrev(rf, rev)) == NULL) {
11912dc36bedSjoris rcs_errno = RCS_ERR_NOENT;
11922dc36bedSjoris return (-1);
11932dc36bedSjoris }
11942dc36bedSjoris
11952dc36bedSjoris /*
11962dc36bedSjoris * This is confusing, the previous delta is next in the TAILQ list.
11972dc36bedSjoris * the next delta is the previous one in the TAILQ list.
11982dc36bedSjoris *
11992dc36bedSjoris * When the HEAD revision got specified, nextrdp will be NULL.
12002dc36bedSjoris * When the first revision got specified, prevrdp will be NULL.
12012dc36bedSjoris */
12022dc36bedSjoris prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
12037a9e6d11Sray nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list);
12042dc36bedSjoris
12053de4517bSniallo newdeltatext = prevbuf = nextbuf = NULL;
12062dc36bedSjoris
12072dc36bedSjoris if (prevrdp != NULL) {
12082dc36bedSjoris if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL)
12092dc36bedSjoris errx(1, "error getting revision");
12102dc36bedSjoris }
12112dc36bedSjoris
12122dc36bedSjoris if (prevrdp != NULL && nextrdp != NULL) {
12132dc36bedSjoris if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL)
12142dc36bedSjoris errx(1, "error getting revision");
12152dc36bedSjoris
12160ee14128Sray newdiff = buf_alloc(64);
12172dc36bedSjoris
12182dc36bedSjoris /* calculate new diff */
1219cdc530d2Sxsa (void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir);
12207bb3ddb0Sray buf_write_stmp(nextbuf, path_tmp1);
12217bb3ddb0Sray buf_free(nextbuf);
12222dc36bedSjoris
1223cdc530d2Sxsa (void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir);
12247bb3ddb0Sray buf_write_stmp(prevbuf, path_tmp2);
12257bb3ddb0Sray buf_free(prevbuf);
12262dc36bedSjoris
12272dc36bedSjoris diff_format = D_RCSDIFF;
122868b523edSray if (diffreg(path_tmp1, path_tmp2, newdiff, D_FORCEASCII) == D_ERROR)
1229f049f428Sray errx(1, "diffreg failed");
12302dc36bedSjoris
12313de4517bSniallo newdeltatext = newdiff;
12322dc36bedSjoris } else if (nextrdp == NULL && prevrdp != NULL) {
12333de4517bSniallo newdeltatext = prevbuf;
12342dc36bedSjoris }
12352dc36bedSjoris
12362dc36bedSjoris if (newdeltatext != NULL) {
12372dc36bedSjoris if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
12382dc36bedSjoris errx(1, "error setting new deltatext");
12392dc36bedSjoris }
12402dc36bedSjoris
12412dc36bedSjoris TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
12422dc36bedSjoris
12432dc36bedSjoris /* update pointers */
12442dc36bedSjoris if (prevrdp != NULL && nextrdp != NULL) {
12452dc36bedSjoris rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
12462dc36bedSjoris } else if (prevrdp != NULL) {
12472dc36bedSjoris if (rcs_head_set(rf, prevrdp->rd_num) < 0)
12482dc36bedSjoris errx(1, "rcs_head_set failed");
12492dc36bedSjoris } else if (nextrdp != NULL) {
12502dc36bedSjoris rcsnum_free(nextrdp->rd_next);
12512dc36bedSjoris nextrdp->rd_next = rcsnum_alloc();
12522dc36bedSjoris } else {
12532dc36bedSjoris rcsnum_free(rf->rf_head);
12542dc36bedSjoris rf->rf_head = NULL;
12552dc36bedSjoris }
12562dc36bedSjoris
12572dc36bedSjoris rf->rf_ndelta--;
12582dc36bedSjoris rf->rf_flags &= ~RCS_SYNCED;
12592dc36bedSjoris
12602dc36bedSjoris rcs_freedelta(rdp);
12612dc36bedSjoris
12628ac837e5Snicm free(path_tmp1);
12638ac837e5Snicm free(path_tmp2);
1264cdc530d2Sxsa
12652dc36bedSjoris return (0);
12662dc36bedSjoris }
12672dc36bedSjoris
12682dc36bedSjoris /*
12692dc36bedSjoris * rcs_findrev()
12702dc36bedSjoris *
12712dc36bedSjoris * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
12722dc36bedSjoris * The revision number is given in <rev>.
12732dc36bedSjoris *
12742dc36bedSjoris * If the given revision is a branch number, we translate it into the latest
12752dc36bedSjoris * revision on the branch.
12762dc36bedSjoris *
12772dc36bedSjoris * Returns a pointer to the delta on success, or NULL on failure.
12782dc36bedSjoris */
12792dc36bedSjoris struct rcs_delta *
rcs_findrev(RCSFILE * rfp,RCSNUM * rev)12802dc36bedSjoris rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
12812dc36bedSjoris {
12822dc36bedSjoris u_int cmplen;
12832dc36bedSjoris struct rcs_delta *rdp;
12842dc36bedSjoris RCSNUM *brev, *frev;
12852dc36bedSjoris
12862dc36bedSjoris /*
12872dc36bedSjoris * We need to do more parsing if the last revision in the linked list
12882dc36bedSjoris * is greater than the requested revision.
12892dc36bedSjoris */
12902dc36bedSjoris rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
12912dc36bedSjoris if (rdp == NULL ||
12922dc36bedSjoris rcsnum_cmp(rdp->rd_num, rev, 0) == -1) {
1293fead43dfStobias if (rcsparse_deltas(rfp, rev))
1294fead43dfStobias return (NULL);
12952dc36bedSjoris }
12962dc36bedSjoris
12972dc36bedSjoris /*
12982dc36bedSjoris * Translate a branch into the latest revision on the branch itself.
12992dc36bedSjoris */
13002dc36bedSjoris if (RCSNUM_ISBRANCH(rev)) {
13012dc36bedSjoris brev = rcsnum_brtorev(rev);
13022dc36bedSjoris frev = brev;
13032dc36bedSjoris for (;;) {
13042dc36bedSjoris rdp = rcs_findrev(rfp, frev);
13052dc36bedSjoris if (rdp == NULL)
13062dc36bedSjoris return (NULL);
13072dc36bedSjoris
13082dc36bedSjoris if (rdp->rd_next->rn_len == 0)
13092dc36bedSjoris break;
13102dc36bedSjoris
13112dc36bedSjoris frev = rdp->rd_next;
13122dc36bedSjoris }
13132dc36bedSjoris
13142dc36bedSjoris rcsnum_free(brev);
13152dc36bedSjoris return (rdp);
13162dc36bedSjoris }
13172dc36bedSjoris
13182dc36bedSjoris cmplen = rev->rn_len;
13192dc36bedSjoris
13202dc36bedSjoris TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
13212dc36bedSjoris if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0)
13222dc36bedSjoris return (rdp);
13232dc36bedSjoris }
13242dc36bedSjoris
13252dc36bedSjoris return (NULL);
13262dc36bedSjoris }
13272dc36bedSjoris
13282dc36bedSjoris /*
13292dc36bedSjoris * rcs_kwexp_set()
13302dc36bedSjoris *
13312dc36bedSjoris * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
13322dc36bedSjoris */
13332dc36bedSjoris void
rcs_kwexp_set(RCSFILE * file,int mode)13342dc36bedSjoris rcs_kwexp_set(RCSFILE *file, int mode)
13352dc36bedSjoris {
13362dc36bedSjoris int i;
13372dc36bedSjoris char *tmp, buf[8] = "";
13382dc36bedSjoris
13392dc36bedSjoris if (RCS_KWEXP_INVAL(mode))
13402dc36bedSjoris return;
13412dc36bedSjoris
13422dc36bedSjoris i = 0;
13432dc36bedSjoris if (mode == RCS_KWEXP_NONE)
13442dc36bedSjoris buf[0] = 'b';
13452dc36bedSjoris else if (mode == RCS_KWEXP_OLD)
13462dc36bedSjoris buf[0] = 'o';
13472dc36bedSjoris else {
13482dc36bedSjoris if (mode & RCS_KWEXP_NAME)
13492dc36bedSjoris buf[i++] = 'k';
13502dc36bedSjoris if (mode & RCS_KWEXP_VAL)
13512dc36bedSjoris buf[i++] = 'v';
13522dc36bedSjoris if (mode & RCS_KWEXP_LKR)
13532dc36bedSjoris buf[i++] = 'l';
13542dc36bedSjoris }
13552dc36bedSjoris
13562dc36bedSjoris tmp = xstrdup(buf);
13578ac837e5Snicm free(file->rf_expand);
13582dc36bedSjoris file->rf_expand = tmp;
13592dc36bedSjoris /* not synced anymore */
13602dc36bedSjoris file->rf_flags &= ~RCS_SYNCED;
13612dc36bedSjoris }
13622dc36bedSjoris
13632dc36bedSjoris /*
13642dc36bedSjoris * rcs_kwexp_get()
13652dc36bedSjoris *
13662dc36bedSjoris * Retrieve the keyword expansion mode to be used for the RCS file <file>.
13672dc36bedSjoris */
13682dc36bedSjoris int
rcs_kwexp_get(RCSFILE * file)13692dc36bedSjoris rcs_kwexp_get(RCSFILE *file)
13702dc36bedSjoris {
1371f2a06cf4Stobias if (file->rf_expand == NULL)
1372f2a06cf4Stobias return (RCS_KWEXP_DEFAULT);
1373f2a06cf4Stobias
1374f2a06cf4Stobias return (rcs_kflag_get(file->rf_expand));
13752dc36bedSjoris }
13762dc36bedSjoris
13772dc36bedSjoris /*
13782dc36bedSjoris * rcs_kflag_get()
13792dc36bedSjoris *
13802dc36bedSjoris * Get the keyword expansion mode from a set of character flags given in
13812dc36bedSjoris * <flags> and return the appropriate flag mask. In case of an error, the
13822dc36bedSjoris * returned mask will have the RCS_KWEXP_ERR bit set to 1.
13832dc36bedSjoris */
13842dc36bedSjoris int
rcs_kflag_get(const char * flags)13852dc36bedSjoris rcs_kflag_get(const char *flags)
13862dc36bedSjoris {
13872dc36bedSjoris int fl;
13882dc36bedSjoris size_t len;
13892dc36bedSjoris const char *fp;
13902dc36bedSjoris
1391f2a06cf4Stobias if (flags == NULL || !(len = strlen(flags)))
1392f2a06cf4Stobias return (RCS_KWEXP_ERR);
13932dc36bedSjoris
1394f2a06cf4Stobias fl = 0;
13952dc36bedSjoris for (fp = flags; *fp != '\0'; fp++) {
13962dc36bedSjoris if (*fp == 'k')
13972dc36bedSjoris fl |= RCS_KWEXP_NAME;
13982dc36bedSjoris else if (*fp == 'v')
13992dc36bedSjoris fl |= RCS_KWEXP_VAL;
14002dc36bedSjoris else if (*fp == 'l')
14012dc36bedSjoris fl |= RCS_KWEXP_LKR;
14022dc36bedSjoris else if (*fp == 'o') {
14032dc36bedSjoris if (len != 1)
14042dc36bedSjoris fl |= RCS_KWEXP_ERR;
14052dc36bedSjoris fl |= RCS_KWEXP_OLD;
14062dc36bedSjoris } else if (*fp == 'b') {
14072dc36bedSjoris if (len != 1)
14082dc36bedSjoris fl |= RCS_KWEXP_ERR;
1409f2a06cf4Stobias fl |= RCS_KWEXP_NONE;
14102dc36bedSjoris } else /* unknown letter */
14112dc36bedSjoris fl |= RCS_KWEXP_ERR;
14122dc36bedSjoris }
14132dc36bedSjoris
14142dc36bedSjoris return (fl);
14152dc36bedSjoris }
14162dc36bedSjoris
14172dc36bedSjoris /*
14182dc36bedSjoris * rcs_freedelta()
14192dc36bedSjoris *
14202dc36bedSjoris * Free the contents of a delta structure.
14212dc36bedSjoris */
14222dc36bedSjoris static void
rcs_freedelta(struct rcs_delta * rdp)14232dc36bedSjoris rcs_freedelta(struct rcs_delta *rdp)
14242dc36bedSjoris {
14252dc36bedSjoris struct rcs_branch *rb;
14262dc36bedSjoris
14272dc36bedSjoris rcsnum_free(rdp->rd_num);
14282dc36bedSjoris rcsnum_free(rdp->rd_next);
14292dc36bedSjoris
14308ac837e5Snicm free(rdp->rd_author);
14318ac837e5Snicm free(rdp->rd_locker);
14328ac837e5Snicm free(rdp->rd_state);
14338ac837e5Snicm free(rdp->rd_log);
14348ac837e5Snicm free(rdp->rd_text);
14352dc36bedSjoris
14362dc36bedSjoris while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
14372dc36bedSjoris TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
14382dc36bedSjoris rcsnum_free(rb->rb_num);
14398ac837e5Snicm free(rb);
14402dc36bedSjoris }
14412dc36bedSjoris
14428ac837e5Snicm free(rdp);
14432dc36bedSjoris }
14442dc36bedSjoris
14452dc36bedSjoris /*
14462dc36bedSjoris * rcs_strprint()
14472dc36bedSjoris *
14482dc36bedSjoris * Output an RCS string <str> of size <slen> to the stream <stream>. Any
14492dc36bedSjoris * '@' characters are escaped. Otherwise, the string can contain arbitrary
14502dc36bedSjoris * binary data.
14512dc36bedSjoris */
14522dc36bedSjoris static void
rcs_strprint(const u_char * str,size_t slen,FILE * stream)14532dc36bedSjoris rcs_strprint(const u_char *str, size_t slen, FILE *stream)
14542dc36bedSjoris {
14552dc36bedSjoris const u_char *ap, *ep, *sp;
14562dc36bedSjoris
14572dc36bedSjoris if (slen == 0)
14582dc36bedSjoris return;
14592dc36bedSjoris
14602dc36bedSjoris ep = str + slen - 1;
14612dc36bedSjoris
14622dc36bedSjoris for (sp = str; sp <= ep;) {
14632dc36bedSjoris ap = memchr(sp, '@', ep - sp);
14642dc36bedSjoris if (ap == NULL)
14652dc36bedSjoris ap = ep;
14662dc36bedSjoris (void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
14672dc36bedSjoris
14682dc36bedSjoris if (*ap == '@')
14692dc36bedSjoris putc('@', stream);
14702dc36bedSjoris sp = ap + 1;
14712dc36bedSjoris }
14722dc36bedSjoris }
14732dc36bedSjoris
14742dc36bedSjoris /*
14752dc36bedSjoris * rcs_expand_keywords()
14762dc36bedSjoris *
14772dc36bedSjoris * Return expansion any RCS keywords in <data>
14782dc36bedSjoris *
14792dc36bedSjoris * On error, return NULL.
14802dc36bedSjoris */
14813de4517bSniallo static BUF *
rcs_expand_keywords(char * rcsfile_in,struct rcs_delta * rdp,BUF * bp,int mode)1482ef21cefbSnicm rcs_expand_keywords(char *rcsfile_in, struct rcs_delta *rdp, BUF *bp, int mode)
14832dc36bedSjoris {
1484a2a1496fSray BUF *newbuf;
1485ef21cefbSnicm u_char *c, *kw, *fin;
1486b9fc9a72Sderaadt char buf[256], *tmpf, resolved[PATH_MAX], *rcsfile;
1487ef21cefbSnicm u_char *line, *line2;
1488ef21cefbSnicm u_int i, j;
14892dc36bedSjoris int kwtype;
1490ef21cefbSnicm int found;
14912dc36bedSjoris struct tm tb;
14922dc36bedSjoris
14932dc36bedSjoris tb = rdp->rd_date;
14942dc36bedSjoris if (timezone_flag != NULL)
14952dc36bedSjoris rcs_set_tz(timezone_flag, rdp, &tb);
14962dc36bedSjoris
1497ef21cefbSnicm if (realpath(rcsfile_in, resolved) == NULL)
1498ef21cefbSnicm rcsfile = rcsfile_in;
1499ef21cefbSnicm else
1500ef21cefbSnicm rcsfile = resolved;
15013de4517bSniallo
1502ef21cefbSnicm newbuf = buf_alloc(buf_len(bp));
1503a081a4acSniallo
15042dc36bedSjoris /*
15052dc36bedSjoris * Keyword formats:
15062dc36bedSjoris * $Keyword$
15072dc36bedSjoris * $Keyword: value$
15082dc36bedSjoris */
1509ef21cefbSnicm c = buf_get(bp);
1510ef21cefbSnicm fin = c + buf_len(bp);
1511ef21cefbSnicm /* Copying to newbuf is deferred until the first keyword. */
15122dc36bedSjoris found = 0;
1513ef21cefbSnicm
1514ef21cefbSnicm while (c < fin) {
1515ef21cefbSnicm kw = memchr(c, '$', fin - c);
1516ef21cefbSnicm if (kw == NULL)
1517ef21cefbSnicm break;
1518ef21cefbSnicm ++kw;
1519ef21cefbSnicm if (found) {
1520ef21cefbSnicm /* Copy everything up to and including the $. */
1521ef21cefbSnicm buf_append(newbuf, c, kw - c);
1522ef21cefbSnicm }
1523ef21cefbSnicm c = kw;
1524ef21cefbSnicm /* c points after the $ now. */
1525ef21cefbSnicm if (c == fin)
1526ef21cefbSnicm break;
1527ef21cefbSnicm if (!isalpha(*c)) /* all valid keywords start with a letter */
1528ef21cefbSnicm continue;
1529ef21cefbSnicm
1530ef21cefbSnicm for (i = 0; i < RCS_NKWORDS; ++i) {
1531a2a1496fSray size_t kwlen;
1532a2a1496fSray
1533ef21cefbSnicm kwlen = strlen(rcs_expkw[i].kw_str);
1534a2a1496fSray /*
1535ef21cefbSnicm * kwlen must be less than clen since clen includes
1536ef21cefbSnicm * either a terminating `$' or a `:'.
1537a2a1496fSray */
1538ef21cefbSnicm if (c + kwlen < fin &&
1539ef21cefbSnicm memcmp(c , rcs_expkw[i].kw_str, kwlen) == 0 &&
1540a2a1496fSray (c[kwlen] == '$' || c[kwlen] == ':')) {
1541a2a1496fSray c += kwlen;
15422dc36bedSjoris break;
15432dc36bedSjoris }
15442dc36bedSjoris }
1545ef21cefbSnicm if (i == RCS_NKWORDS)
15462dc36bedSjoris continue;
1547ef21cefbSnicm kwtype = rcs_expkw[i].kw_type;
15482dc36bedSjoris
15492dc36bedSjoris /*
1550ef21cefbSnicm * If the next character is ':' we need to look for an '$'
1551ef21cefbSnicm * before the end of the line to be sure it is in fact a
1552ef21cefbSnicm * keyword.
15532dc36bedSjoris */
15542dc36bedSjoris if (*c == ':') {
1555ef21cefbSnicm for (; c < fin; ++c) {
15562dc36bedSjoris if (*c == '$' || *c == '\n')
15572dc36bedSjoris break;
15582dc36bedSjoris }
15592dc36bedSjoris
15602dc36bedSjoris if (*c != '$') {
1561ef21cefbSnicm if (found)
1562ef21cefbSnicm buf_append(newbuf, kw, c - kw);
15632dc36bedSjoris continue;
15642dc36bedSjoris }
15652dc36bedSjoris }
1566ef21cefbSnicm ++c;
15672dc36bedSjoris
1568ef21cefbSnicm if (!found) {
1569ef21cefbSnicm found = 1;
1570ef21cefbSnicm /* Copy everything up to and including the $. */
1571ef21cefbSnicm buf_append(newbuf, buf_get(bp), kw - buf_get(bp));
15722dc36bedSjoris }
15732dc36bedSjoris
1574ef21cefbSnicm if (mode & RCS_KWEXP_NAME) {
1575ef21cefbSnicm buf_puts(newbuf, rcs_expkw[i].kw_str);
1576ef21cefbSnicm if (mode & RCS_KWEXP_VAL)
1577ef21cefbSnicm buf_puts(newbuf, ": ");
1578ef21cefbSnicm }
157929095643Sray
1580ef21cefbSnicm /* Order matters because of RCS_KW_ID and RCS_KW_HEADER. */
1581ef21cefbSnicm if (mode & RCS_KWEXP_VAL) {
1582ef21cefbSnicm if (kwtype & (RCS_KW_RCSFILE|RCS_KW_LOG)) {
1583ef21cefbSnicm if ((kwtype & RCS_KW_FULLPATH) ||
1584ef21cefbSnicm (tmpf = strrchr(rcsfile, '/')) == NULL)
1585ef21cefbSnicm buf_puts(newbuf, rcsfile);
1586ef21cefbSnicm else
1587ef21cefbSnicm buf_puts(newbuf, tmpf + 1);
1588ef21cefbSnicm buf_putc(newbuf, ' ');
15892dc36bedSjoris }
15902dc36bedSjoris
15912dc36bedSjoris if (kwtype & RCS_KW_REVISION) {
15926b99bb86Sray rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1593ef21cefbSnicm buf_puts(newbuf, buf);
1594ef21cefbSnicm buf_putc(newbuf, ' ');
15952dc36bedSjoris }
15962dc36bedSjoris
15972dc36bedSjoris if (kwtype & RCS_KW_DATE) {
1598ef21cefbSnicm strftime(buf, sizeof(buf),
1599ef21cefbSnicm "%Y/%m/%d %H:%M:%S ", &tb);
1600ef21cefbSnicm buf_puts(newbuf, buf);
16012dc36bedSjoris }
16022dc36bedSjoris
16032dc36bedSjoris if (kwtype & RCS_KW_AUTHOR) {
1604ef21cefbSnicm buf_puts(newbuf, rdp->rd_author);
1605ef21cefbSnicm buf_putc(newbuf, ' ');
16062dc36bedSjoris }
16072dc36bedSjoris
16082dc36bedSjoris if (kwtype & RCS_KW_STATE) {
1609ef21cefbSnicm buf_puts(newbuf, rdp->rd_state);
1610ef21cefbSnicm buf_putc(newbuf, ' ');
16112dc36bedSjoris }
16122dc36bedSjoris
1613ef21cefbSnicm /* Order does not matter anymore below. */
16142dc36bedSjoris if (kwtype & RCS_KW_SOURCE) {
1615ef21cefbSnicm buf_puts(newbuf, rcsfile);
1616ef21cefbSnicm buf_putc(newbuf, ' ');
16172dc36bedSjoris }
16182dc36bedSjoris
1619b5a78d44Sschwarze if (kwtype & RCS_KW_MDOCDATE) {
1620b5a78d44Sschwarze strftime(buf, sizeof(buf), "%B", &tb);
1621b5a78d44Sschwarze buf_puts(newbuf, buf);
1622b5a78d44Sschwarze /* Only one blank before single-digit day. */
1623b5a78d44Sschwarze snprintf(buf, sizeof(buf), " %d", tb.tm_mday);
1624b5a78d44Sschwarze buf_puts(newbuf, buf);
1625b5a78d44Sschwarze strftime(buf, sizeof(buf), " %Y ", &tb);
1626b5a78d44Sschwarze buf_puts(newbuf, buf);
1627b5a78d44Sschwarze }
1628b5a78d44Sschwarze
16292dc36bedSjoris if (kwtype & RCS_KW_NAME)
1630ef21cefbSnicm buf_putc(newbuf, ' ');
1631ef21cefbSnicm
1632ef21cefbSnicm if ((kwtype & RCS_KW_LOCKER)) {
1633ef21cefbSnicm if (rdp->rd_locker) {
1634ef21cefbSnicm buf_puts(newbuf, rdp->rd_locker);
1635ef21cefbSnicm buf_putc(newbuf, ' ');
1636ef21cefbSnicm }
1637ef21cefbSnicm }
16382dc36bedSjoris }
16392dc36bedSjoris
1640ef21cefbSnicm /* End the expansion. */
16412dc36bedSjoris if (mode & RCS_KWEXP_NAME)
1642ef21cefbSnicm buf_putc(newbuf, '$');
16432dc36bedSjoris
1644ef21cefbSnicm if (kwtype & RCS_KW_LOG) {
1645ef21cefbSnicm line = memrchr(buf_get(bp), '\n', kw - buf_get(bp) - 1);
1646ef21cefbSnicm if (line == NULL)
1647ef21cefbSnicm line = buf_get(bp);
1648ef21cefbSnicm else
1649ef21cefbSnicm ++line;
1650ef21cefbSnicm line2 = kw - 1;
1651ef21cefbSnicm while (line2 > line && line2[-1] == ' ')
1652ef21cefbSnicm --line2;
16532dc36bedSjoris
1654ef21cefbSnicm buf_putc(newbuf, '\n');
1655ef21cefbSnicm buf_append(newbuf, line, kw - 1 - line);
1656ef21cefbSnicm buf_puts(newbuf, "Revision ");
1657ef21cefbSnicm rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1658ef21cefbSnicm buf_puts(newbuf, buf);
1659ef21cefbSnicm buf_puts(newbuf, " ");
1660ef21cefbSnicm strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &tb);
1661ef21cefbSnicm buf_puts(newbuf, buf);
1662ef21cefbSnicm
1663ef21cefbSnicm buf_puts(newbuf, " ");
1664ef21cefbSnicm buf_puts(newbuf, rdp->rd_author);
1665ef21cefbSnicm buf_putc(newbuf, '\n');
1666ef21cefbSnicm
1667ef21cefbSnicm for (i = 0; rdp->rd_log[i]; i += j) {
1668ef21cefbSnicm j = strcspn(rdp->rd_log + i, "\n");
1669ef21cefbSnicm if (j == 0)
1670ef21cefbSnicm buf_append(newbuf, line, line2 - line);
1671ef21cefbSnicm else
1672ef21cefbSnicm buf_append(newbuf, line, kw - 1 - line);
1673ef21cefbSnicm if (rdp->rd_log[i + j])
1674ef21cefbSnicm ++j;
1675ef21cefbSnicm buf_append(newbuf, rdp->rd_log + i, j);
1676ef21cefbSnicm }
1677cfc6befaSjoris
1678cfc6befaSjoris if (i > 0 && rdp->rd_log[i - 1] != '\n')
1679cfc6befaSjoris buf_putc(newbuf, '\n');
1680cfc6befaSjoris
1681ef21cefbSnicm buf_append(newbuf, line, line2 - line);
1682ef21cefbSnicm for (j = 0; c + j < fin; ++j) {
1683ef21cefbSnicm if (c[j] != ' ')
1684ef21cefbSnicm break;
1685ef21cefbSnicm }
1686ef21cefbSnicm if (c + j == fin || c[j] == '\n')
1687ef21cefbSnicm c += j;
16882dc36bedSjoris }
16892dc36bedSjoris }
1690a081a4acSniallo
1691ef21cefbSnicm if (found) {
1692ef21cefbSnicm buf_append(newbuf, c, fin - c);
1693ef21cefbSnicm buf_free(bp);
1694a2a1496fSray return (newbuf);
1695ef21cefbSnicm } else {
1696ef21cefbSnicm buf_free(newbuf);
1697ef21cefbSnicm return (bp);
1698ef21cefbSnicm }
16992dc36bedSjoris }
17002dc36bedSjoris
17012dc36bedSjoris /*
17022dc36bedSjoris * rcs_deltatext_set()
17032dc36bedSjoris *
17042dc36bedSjoris * Set deltatext for <rev> in RCS file <rfp> to <dtext>
17052dc36bedSjoris * Returns -1 on error, 0 on success.
17062dc36bedSjoris */
17072dc36bedSjoris int
rcs_deltatext_set(RCSFILE * rfp,RCSNUM * rev,BUF * bp)17083de4517bSniallo rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
17092dc36bedSjoris {
17102dc36bedSjoris size_t len;
17113de4517bSniallo u_char *dtext;
17122dc36bedSjoris struct rcs_delta *rdp;
17132dc36bedSjoris
17142dc36bedSjoris /* Write operations require full parsing */
1715fead43dfStobias if (rcsparse_deltatexts(rfp, NULL))
1716fead43dfStobias return (-1);
17172dc36bedSjoris
17182dc36bedSjoris if ((rdp = rcs_findrev(rfp, rev)) == NULL)
17192dc36bedSjoris return (-1);
17202dc36bedSjoris
17218ac837e5Snicm free(rdp->rd_text);
17222dc36bedSjoris
17237bb3ddb0Sray len = buf_len(bp);
17247bb3ddb0Sray dtext = buf_release(bp);
17253de4517bSniallo bp = NULL;
17267bb3ddb0Sray
17272dc36bedSjoris if (len != 0) {
17283de4517bSniallo rdp->rd_text = xmalloc(len);
17292dc36bedSjoris rdp->rd_tlen = len;
17303de4517bSniallo (void)memcpy(rdp->rd_text, dtext, len);
17312dc36bedSjoris } else {
17322dc36bedSjoris rdp->rd_text = NULL;
17332dc36bedSjoris rdp->rd_tlen = 0;
17342dc36bedSjoris }
17352dc36bedSjoris
17368ac837e5Snicm free(dtext);
1737a3aab4b0Stobias
17382dc36bedSjoris return (0);
17392dc36bedSjoris }
17402dc36bedSjoris
17412dc36bedSjoris /*
17422dc36bedSjoris * rcs_rev_setlog()
17432dc36bedSjoris *
17447a347c71Stobias * Sets the log message of revision <rev> to <logtext>.
17452dc36bedSjoris */
17462dc36bedSjoris int
rcs_rev_setlog(RCSFILE * rfp,RCSNUM * rev,const char * logtext)17472dc36bedSjoris rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
17482dc36bedSjoris {
17492dc36bedSjoris struct rcs_delta *rdp;
17502dc36bedSjoris
17512dc36bedSjoris if ((rdp = rcs_findrev(rfp, rev)) == NULL)
17522dc36bedSjoris return (-1);
17532dc36bedSjoris
17548ac837e5Snicm free(rdp->rd_log);
17552dc36bedSjoris
17562dc36bedSjoris rdp->rd_log = xstrdup(logtext);
17572dc36bedSjoris rfp->rf_flags &= ~RCS_SYNCED;
17582dc36bedSjoris return (0);
17592dc36bedSjoris }
17602dc36bedSjoris /*
17612dc36bedSjoris * rcs_rev_getdate()
17622dc36bedSjoris *
17632dc36bedSjoris * Get the date corresponding to a given revision.
17642dc36bedSjoris * Returns the date on success, -1 on failure.
17652dc36bedSjoris */
17662dc36bedSjoris time_t
rcs_rev_getdate(RCSFILE * rfp,RCSNUM * rev)17672dc36bedSjoris rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
17682dc36bedSjoris {
17692dc36bedSjoris struct rcs_delta *rdp;
17702dc36bedSjoris
17712dc36bedSjoris if ((rdp = rcs_findrev(rfp, rev)) == NULL)
17722dc36bedSjoris return (-1);
17732dc36bedSjoris
17742dc36bedSjoris return (mktime(&rdp->rd_date));
17752dc36bedSjoris }
17762dc36bedSjoris
17772dc36bedSjoris /*
17782dc36bedSjoris * rcs_state_set()
17792dc36bedSjoris *
17802dc36bedSjoris * Sets the state of revision <rev> to <state>
17812dc36bedSjoris * NOTE: default state is 'Exp'. States may not contain spaces.
17822dc36bedSjoris *
17832dc36bedSjoris * Returns -1 on failure, 0 on success.
17842dc36bedSjoris */
17852dc36bedSjoris int
rcs_state_set(RCSFILE * rfp,RCSNUM * rev,const char * state)17862dc36bedSjoris rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
17872dc36bedSjoris {
17882dc36bedSjoris struct rcs_delta *rdp;
17892dc36bedSjoris
17902dc36bedSjoris if ((rdp = rcs_findrev(rfp, rev)) == NULL)
17912dc36bedSjoris return (-1);
17922dc36bedSjoris
17938ac837e5Snicm free(rdp->rd_state);
17942dc36bedSjoris
17952dc36bedSjoris rdp->rd_state = xstrdup(state);
17962dc36bedSjoris
17972dc36bedSjoris rfp->rf_flags &= ~RCS_SYNCED;
17982dc36bedSjoris
17992dc36bedSjoris return (0);
18002dc36bedSjoris }
18012dc36bedSjoris
18022dc36bedSjoris /*
18032dc36bedSjoris * rcs_state_check()
18042dc36bedSjoris *
18052dc36bedSjoris * Check if string <state> is valid.
18062dc36bedSjoris *
18072dc36bedSjoris * Returns 0 if the string is valid, -1 otherwise.
18082dc36bedSjoris */
18092dc36bedSjoris int
rcs_state_check(const char * state)18102dc36bedSjoris rcs_state_check(const char *state)
18112dc36bedSjoris {
1812b7f54172Stobias int ret;
181303829ff5Sderaadt const unsigned char *cp;
1814b7f54172Stobias
1815b7f54172Stobias ret = 0;
1816b7f54172Stobias cp = state;
1817b7f54172Stobias if (!isalpha(*cp++))
18182dc36bedSjoris return (-1);
18192dc36bedSjoris
1820b7f54172Stobias for (; *cp != '\0'; cp++)
1821b7f54172Stobias if (!isgraph(*cp) || (strchr(rcs_state_invch, *cp) != NULL)) {
1822b7f54172Stobias ret = -1;
1823b7f54172Stobias break;
1824b7f54172Stobias }
1825b7f54172Stobias
1826b7f54172Stobias return (ret);
18272dc36bedSjoris }
18282dc36bedSjoris
18292dc36bedSjoris /*
18302dc36bedSjoris * rcs_kwexp_buf()
18312dc36bedSjoris *
18322dc36bedSjoris * Do keyword expansion on a buffer if necessary
18332dc36bedSjoris *
18342dc36bedSjoris */
18352dc36bedSjoris BUF *
rcs_kwexp_buf(BUF * bp,RCSFILE * rf,RCSNUM * rev)18362dc36bedSjoris rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
18372dc36bedSjoris {
18382dc36bedSjoris struct rcs_delta *rdp;
18392dc36bedSjoris int expmode;
18402dc36bedSjoris
18412dc36bedSjoris /*
18422dc36bedSjoris * Do keyword expansion if required.
18432dc36bedSjoris */
18442dc36bedSjoris expmode = rcs_kwexp_get(rf);
18452dc36bedSjoris
18462dc36bedSjoris if (!(expmode & RCS_KWEXP_NONE)) {
18472dc36bedSjoris if ((rdp = rcs_findrev(rf, rev)) == NULL)
18482dc36bedSjoris errx(1, "could not fetch revision");
18493de4517bSniallo return (rcs_expand_keywords(rf->rf_path, rdp, bp, expmode));
18502dc36bedSjoris }
18512dc36bedSjoris return (bp);
18522dc36bedSjoris }
1853