Annotation of src/usr.bin/rcs/rcs.c, Revision 1.74
1.74 ! djm 1: /* $OpenBSD: rcs.c,v 1.73 2011/03/27 18:22:50 jasper Exp $ */
1.1 joris 2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
9: *
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. The name of the author may not be used to endorse or promote products
13: * derived from this software without specific prior written permission.
14: *
15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: */
26:
1.36 xsa 27: #include <sys/stat.h>
28:
29: #include <ctype.h>
30: #include <err.h>
31: #include <errno.h>
32: #include <libgen.h>
33: #include <pwd.h>
34: #include <stdarg.h>
35: #include <stdio.h>
36: #include <stdlib.h>
37: #include <string.h>
38: #include <unistd.h>
1.1 joris 39:
40: #include "diff.h"
41: #include "rcs.h"
1.68 tobias 42: #include "rcsparse.h"
1.1 joris 43: #include "rcsprog.h"
1.4 xsa 44: #include "rcsutil.h"
1.1 joris 45: #include "xmalloc.h"
46:
47: #define RCS_KWEXP_SIZE 1024
48:
49:
1.66 tobias 50: /* invalid characters in RCS states */
51: static const char rcs_state_invch[] = RCS_STATE_INVALCHAR;
52:
1.1 joris 53: /* invalid characters in RCS symbol names */
54: static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
55:
56: /* comment leaders, depending on the file's suffix */
57: static const struct rcs_comment {
58: const char *rc_suffix;
59: const char *rc_cstr;
60: } rcs_comments[] = {
61: { "1", ".\\\" " },
62: { "2", ".\\\" " },
63: { "3", ".\\\" " },
64: { "4", ".\\\" " },
65: { "5", ".\\\" " },
66: { "6", ".\\\" " },
67: { "7", ".\\\" " },
68: { "8", ".\\\" " },
69: { "9", ".\\\" " },
70: { "a", "-- " }, /* Ada */
71: { "ada", "-- " },
72: { "adb", "-- " },
73: { "asm", ";; " }, /* assembler (MS-DOS) */
74: { "ads", "-- " }, /* Ada */
75: { "bat", ":: " }, /* batch (MS-DOS) */
76: { "body", "-- " }, /* Ada */
77: { "c", " * " }, /* C */
78: { "c++", "// " }, /* C++ */
79: { "cc", "// " },
80: { "cpp", "// " },
81: { "cxx", "// " },
82: { "m", "// " }, /* Objective-C */
83: { "cl", ";;; " }, /* Common Lisp */
84: { "cmd", ":: " }, /* command (OS/2) */
85: { "cmf", "c " }, /* CM Fortran */
86: { "csh", "# " }, /* shell */
87: { "e", "# " }, /* efl */
88: { "epsf", "% " }, /* encapsulated postscript */
89: { "epsi", "% " }, /* encapsulated postscript */
90: { "el", "; " }, /* Emacs Lisp */
91: { "f", "c " }, /* Fortran */
92: { "for", "c " },
93: { "h", " * " }, /* C-header */
94: { "hh", "// " }, /* C++ header */
95: { "hpp", "// " },
96: { "hxx", "// " },
97: { "in", "# " }, /* for Makefile.in */
98: { "l", " * " }, /* lex */
99: { "mac", ";; " }, /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
100: { "mak", "# " }, /* makefile, e.g. Visual C++ */
101: { "me", ".\\\" " }, /* me-macros t/nroff */
102: { "ml", "; " }, /* mocklisp */
103: { "mm", ".\\\" " }, /* mm-macros t/nroff */
104: { "ms", ".\\\" " }, /* ms-macros t/nroff */
105: { "man", ".\\\" " }, /* man-macros t/nroff */
106: { "p", " * " }, /* pascal */
107: { "pas", " * " },
108: { "pl", "# " }, /* Perl (conflict with Prolog) */
109: { "pm", "# " }, /* Perl module */
110: { "ps", "% " }, /* postscript */
111: { "psw", "% " }, /* postscript wrap */
112: { "pswm", "% " }, /* postscript wrap */
113: { "r", "# " }, /* ratfor */
114: { "rc", " * " }, /* Microsoft Windows resource file */
115: { "red", "% " }, /* psl/rlisp */
116: { "sh", "# " }, /* shell */
117: { "sl", "% " }, /* psl */
118: { "spec", "-- " }, /* Ada */
119: { "tex", "% " }, /* tex */
120: { "y", " * " }, /* yacc */
121: { "ye", " * " }, /* yacc-efl */
122: { "yr", " * " }, /* yacc-ratfor */
123: };
124:
125: struct rcs_kw rcs_expkw[] = {
126: { "Author", RCS_KW_AUTHOR },
127: { "Date", RCS_KW_DATE },
128: { "Header", RCS_KW_HEADER },
129: { "Id", RCS_KW_ID },
1.49 deraadt 130: { "OpenBSD", RCS_KW_ID },
1.1 joris 131: { "Log", RCS_KW_LOG },
132: { "Name", RCS_KW_NAME },
133: { "RCSfile", RCS_KW_RCSFILE },
134: { "Revision", RCS_KW_REVISION },
135: { "Source", RCS_KW_SOURCE },
136: { "State", RCS_KW_STATE },
137: };
138:
139: #define NB_COMTYPES (sizeof(rcs_comments)/sizeof(rcs_comments[0]))
140:
141: static const char *rcs_errstrs[] = {
142: "No error",
143: "No such entry",
144: "Duplicate entry found",
145: "Bad RCS number",
146: "Invalid RCS symbol",
147: "Parse error",
148: };
149:
150: #define RCS_NERR (sizeof(rcs_errstrs)/sizeof(rcs_errstrs[0]))
151:
152: int rcs_errno = RCS_ERR_NOERR;
153: char *timezone_flag = NULL;
154:
155: int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *);
1.9 xsa 156: static int rcs_movefile(char *, char *, mode_t, u_int);
1.68 tobias 157:
1.1 joris 158: static void rcs_freedelta(struct rcs_delta *);
159: static void rcs_strprint(const u_char *, size_t, FILE *);
160:
1.12 niallo 161: static BUF *rcs_expand_keywords(char *, struct rcs_delta *, BUF *, int);
1.1 joris 162:
163: RCSFILE *
1.3 joris 164: rcs_open(const char *path, int fd, int flags, ...)
1.1 joris 165: {
1.3 joris 166: int mode;
1.1 joris 167: mode_t fmode;
168: RCSFILE *rfp;
169: va_list vap;
170: struct rcs_delta *rdp;
171: struct rcs_lock *lkr;
172:
173: fmode = S_IRUSR|S_IRGRP|S_IROTH;
174: flags &= 0xffff; /* ditch any internal flags */
175:
1.3 joris 176: if (flags & RCS_CREATE) {
177: va_start(vap, flags);
178: mode = va_arg(vap, int);
179: va_end(vap);
180: fmode = (mode_t)mode;
1.1 joris 181: }
182:
183: rfp = xcalloc(1, sizeof(*rfp));
184:
185: rfp->rf_path = xstrdup(path);
186: rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
187: rfp->rf_mode = fmode;
1.69 tobias 188: if (fd == -1)
189: rfp->rf_file = NULL;
190: else if ((rfp->rf_file = fdopen(fd, "r")) == NULL)
191: err(1, "rcs_open: fdopen: `%s'", path);
1.1 joris 192:
193: TAILQ_INIT(&(rfp->rf_delta));
194: TAILQ_INIT(&(rfp->rf_access));
195: TAILQ_INIT(&(rfp->rf_symbols));
196: TAILQ_INIT(&(rfp->rf_locks));
197:
1.11 ray 198: if (!(rfp->rf_flags & RCS_CREATE)) {
1.68 tobias 199: if (rcsparse_init(rfp))
200: errx(1, "could not parse admin data");
1.1 joris 201:
1.11 ray 202: /* fill in rd_locker */
203: TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
204: if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
205: rcs_close(rfp);
206: return (NULL);
207: }
208:
209: rdp->rd_locker = xstrdup(lkr->rl_name);
1.1 joris 210: }
211: }
212:
213: return (rfp);
214: }
215:
216: /*
217: * rcs_close()
218: *
219: * Close an RCS file handle.
220: */
221: void
222: rcs_close(RCSFILE *rfp)
223: {
224: struct rcs_delta *rdp;
225: struct rcs_access *rap;
226: struct rcs_lock *rlp;
227: struct rcs_sym *rsp;
228:
229: if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
230: rcs_write(rfp);
231:
232: while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
233: rdp = TAILQ_FIRST(&(rfp->rf_delta));
234: TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
235: rcs_freedelta(rdp);
236: }
237:
238: while (!TAILQ_EMPTY(&(rfp->rf_access))) {
239: rap = TAILQ_FIRST(&(rfp->rf_access));
240: TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
241: xfree(rap->ra_name);
242: xfree(rap);
243: }
244:
245: while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
246: rsp = TAILQ_FIRST(&(rfp->rf_symbols));
247: TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
248: rcsnum_free(rsp->rs_num);
249: xfree(rsp->rs_name);
250: xfree(rsp);
251: }
252:
253: while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
254: rlp = TAILQ_FIRST(&(rfp->rf_locks));
255: TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
256: rcsnum_free(rlp->rl_num);
257: xfree(rlp->rl_name);
258: xfree(rlp);
259: }
260:
261: if (rfp->rf_head != NULL)
262: rcsnum_free(rfp->rf_head);
263: if (rfp->rf_branch != NULL)
264: rcsnum_free(rfp->rf_branch);
265:
1.69 tobias 266: if (rfp->rf_file != NULL)
267: fclose(rfp->rf_file);
1.1 joris 268: if (rfp->rf_path != NULL)
269: xfree(rfp->rf_path);
270: if (rfp->rf_comment != NULL)
271: xfree(rfp->rf_comment);
272: if (rfp->rf_expand != NULL)
273: xfree(rfp->rf_expand);
274: if (rfp->rf_desc != NULL)
275: xfree(rfp->rf_desc);
276: if (rfp->rf_pdata != NULL)
1.68 tobias 277: rcsparse_free(rfp);
1.1 joris 278: xfree(rfp);
279: }
280:
281: /*
282: * rcs_write()
283: *
284: * Write the contents of the RCS file handle <rfp> to disk in the file whose
285: * path is in <rf_path>.
286: */
1.9 xsa 287: void
1.1 joris 288: rcs_write(RCSFILE *rfp)
289: {
290: FILE *fp;
1.40 xsa 291: char numbuf[RCS_REV_BUFSZ], *fn;
1.1 joris 292: struct rcs_access *ap;
293: struct rcs_sym *symp;
294: struct rcs_branch *brp;
295: struct rcs_delta *rdp;
296: struct rcs_lock *lkp;
297: size_t len;
1.27 deraadt 298: int fd;
1.1 joris 299:
1.17 niallo 300: fn = NULL;
1.27 deraadt 301: fd = -1;
1.1 joris 302:
303: if (rfp->rf_flags & RCS_SYNCED)
1.9 xsa 304: return;
1.1 joris 305:
306: /* Write operations need the whole file parsed */
1.68 tobias 307: if (rcsparse_deltatexts(rfp, NULL))
308: errx(1, "problem parsing deltatexts");
1.1 joris 309:
1.7 xsa 310: (void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", rcs_tmpdir);
311:
1.1 joris 312: if ((fd = mkstemp(fn)) == -1)
1.2 xsa 313: err(1, "%s", fn);
1.1 joris 314:
315: if ((fp = fdopen(fd, "w+")) == NULL) {
1.5 ray 316: int saved_errno;
317:
318: saved_errno = errno;
1.2 xsa 319: (void)unlink(fn);
1.5 ray 320: errno = saved_errno;
1.2 xsa 321: err(1, "%s", fn);
1.1 joris 322: }
1.52 joris 323:
1.58 ray 324: worklist_add(fn, &temp_files);
1.1 joris 325:
326: if (rfp->rf_head != NULL)
327: rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
328: else
329: numbuf[0] = '\0';
330:
331: fprintf(fp, "head\t%s;\n", numbuf);
332:
333: if (rfp->rf_branch != NULL) {
334: rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
335: fprintf(fp, "branch\t%s;\n", numbuf);
336: }
337:
338: fputs("access", fp);
339: TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
340: fprintf(fp, "\n\t%s", ap->ra_name);
341: }
342: fputs(";\n", fp);
343:
344: fprintf(fp, "symbols");
345: TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
1.45 tobias 346: if (RCSNUM_ISBRANCH(symp->rs_num))
347: rcsnum_addmagic(symp->rs_num);
1.1 joris 348: rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
1.30 ray 349: fprintf(fp, "\n\t%s:%s", symp->rs_name, numbuf);
1.1 joris 350: }
351: fprintf(fp, ";\n");
352:
353: fprintf(fp, "locks");
354: TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
355: rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
356: fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
357: }
358:
359: fprintf(fp, ";");
360:
361: if (rfp->rf_flags & RCS_SLOCK)
362: fprintf(fp, " strict;");
363: fputc('\n', fp);
364:
365: fputs("comment\t@", fp);
366: if (rfp->rf_comment != NULL) {
367: rcs_strprint((const u_char *)rfp->rf_comment,
368: strlen(rfp->rf_comment), fp);
369: fputs("@;\n", fp);
370: } else
371: fputs("# @;\n", fp);
372:
373: if (rfp->rf_expand != NULL) {
374: fputs("expand @", fp);
375: rcs_strprint((const u_char *)rfp->rf_expand,
376: strlen(rfp->rf_expand), fp);
377: fputs("@;\n", fp);
378: }
379:
380: fputs("\n\n", fp);
381:
382: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
383: fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
384: sizeof(numbuf)));
385: fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
386: rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
387: rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
388: rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
389: fprintf(fp, "\tauthor %s;\tstate %s;\n",
390: rdp->rd_author, rdp->rd_state);
391: fputs("branches", fp);
392: TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
1.44 tobias 393: fprintf(fp, "\n\t%s", rcsnum_tostr(brp->rb_num, numbuf,
1.1 joris 394: sizeof(numbuf)));
395: }
396: fputs(";\n", fp);
397: fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
398: numbuf, sizeof(numbuf)));
399: }
400:
401: fputs("\ndesc\n@", fp);
402: if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
403: rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
404: if (rfp->rf_desc[len-1] != '\n')
405: fputc('\n', fp);
406: }
407: fputs("@\n", fp);
408:
409: /* deltatexts */
410: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
411: fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
412: sizeof(numbuf)));
413: fputs("log\n@", fp);
414: if (rdp->rd_log != NULL) {
415: len = strlen(rdp->rd_log);
416: rcs_strprint((const u_char *)rdp->rd_log, len, fp);
1.56 nicm 417: if (len == 0 || rdp->rd_log[len-1] != '\n')
1.1 joris 418: fputc('\n', fp);
419: }
420: fputs("@\ntext\n@", fp);
1.35 niallo 421: if (rdp->rd_text != NULL)
1.1 joris 422: rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
423: fputs("@\n", fp);
424: }
1.9 xsa 425: (void)fclose(fp);
1.1 joris 426:
1.9 xsa 427: if (rcs_movefile(fn, rfp->rf_path, rfp->rf_mode, rfp->rf_flags) == -1) {
428: (void)unlink(fn);
429: errx(1, "rcs_movefile failed");
430: }
1.1 joris 431:
1.9 xsa 432: rfp->rf_flags |= RCS_SYNCED;
1.1 joris 433:
1.9 xsa 434: if (fn != NULL)
435: xfree(fn);
436: }
1.1 joris 437:
1.9 xsa 438: /*
439: * rcs_movefile()
440: *
441: * Move a file using rename(2) if possible and copying if not.
442: * Returns 0 on success, -1 on failure.
443: */
444: static int
445: rcs_movefile(char *from, char *to, mode_t perm, u_int to_flags)
446: {
447: FILE *src, *dst;
448: size_t nread, nwritten;
449: char *buf;
450: int ret;
1.1 joris 451:
1.9 xsa 452: ret = -1;
1.1 joris 453:
1.9 xsa 454: if (rename(from, to) == 0) {
455: if (chmod(to, perm) == -1) {
456: warn("%s", to);
457: return (-1);
1.1 joris 458: }
1.9 xsa 459: return (0);
460: } else if (errno != EXDEV) {
461: warn("failed to access temp RCS output file");
462: return (-1);
1.1 joris 463: }
464:
1.9 xsa 465: if ((chmod(to, S_IWUSR) == -1) && !(to_flags & RCS_CREATE)) {
466: warnx("chmod(%s, 0%o) failed", to, S_IWUSR);
467: return (-1);
1.1 joris 468: }
469:
1.9 xsa 470: /* different filesystem, have to copy the file */
471: if ((src = fopen(from, "r")) == NULL) {
472: warn("%s", from);
473: return (-1);
474: }
475: if ((dst = fopen(to, "w")) == NULL) {
476: warn("%s", to);
1.72 jasper 477: (void)fclose(src);
1.9 xsa 478: return (-1);
479: }
480: if (fchmod(fileno(dst), perm)) {
481: warn("%s", to);
482: (void)unlink(to);
1.72 jasper 483: (void)fclose(src);
484: (void)fclose(dst);
1.9 xsa 485: return (-1);
486: }
487:
488: buf = xmalloc(MAXBSIZE);
489: while ((nread = fread(buf, sizeof(char), MAXBSIZE, src)) != 0) {
490: if (ferror(src)) {
491: warnx("failed to read `%s'", from);
492: (void)unlink(to);
493: goto out;
494: }
495: nwritten = fwrite(buf, sizeof(char), nread, dst);
496: if (nwritten != nread) {
497: warnx("failed to write `%s'", to);
498: (void)unlink(to);
499: goto out;
500: }
501: }
1.1 joris 502:
1.7 xsa 503: ret = 0;
1.9 xsa 504:
505: (void)unlink(from);
506:
1.7 xsa 507: out:
1.72 jasper 508: (void)fclose(src);
509: (void)fclose(dst);
1.9 xsa 510: xfree(buf);
1.7 xsa 511:
512: return (ret);
1.1 joris 513: }
514:
515: /*
516: * rcs_head_get()
517: *
518: * Retrieve the revision number of the head revision for the RCS file <file>.
519: */
520: const RCSNUM *
521: rcs_head_get(RCSFILE *file)
522: {
523: return (file->rf_head);
524: }
525:
526: /*
527: * rcs_head_set()
528: *
529: * Set the revision number of the head revision for the RCS file <file> to
530: * <rev>, which must reference a valid revision within the file.
531: */
532: int
533: rcs_head_set(RCSFILE *file, RCSNUM *rev)
534: {
535: if (rcs_findrev(file, rev) == NULL)
536: return (-1);
537:
538: if (file->rf_head == NULL)
539: file->rf_head = rcsnum_alloc();
540:
541: rcsnum_cpy(rev, file->rf_head, 0);
542: file->rf_flags &= ~RCS_SYNCED;
543: return (0);
544: }
545:
546:
547: /*
548: * rcs_branch_get()
549: *
550: * Retrieve the default branch number for the RCS file <file>.
551: * Returns the number on success. If NULL is returned, then there is no
552: * default branch for this file.
553: */
554: const RCSNUM *
555: rcs_branch_get(RCSFILE *file)
556: {
557: return (file->rf_branch);
558: }
559:
560: /*
561: * rcs_branch_set()
562: *
563: * Set the default branch for the RCS file <file> to <bnum>.
564: * Returns 0 on success, -1 on failure.
565: */
566: int
567: rcs_branch_set(RCSFILE *file, const RCSNUM *bnum)
568: {
569: if (file->rf_branch == NULL)
570: file->rf_branch = rcsnum_alloc();
571:
572: rcsnum_cpy(bnum, file->rf_branch, 0);
573: file->rf_flags &= ~RCS_SYNCED;
574: return (0);
575: }
576:
577: /*
578: * rcs_access_add()
579: *
580: * Add the login name <login> to the access list for the RCS file <file>.
581: * Returns 0 on success, or -1 on failure.
582: */
583: int
584: rcs_access_add(RCSFILE *file, const char *login)
585: {
586: struct rcs_access *ap;
587:
588: /* first look for duplication */
589: TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
590: if (strcmp(ap->ra_name, login) == 0) {
591: rcs_errno = RCS_ERR_DUPENT;
592: return (-1);
593: }
594: }
595:
596: ap = xmalloc(sizeof(*ap));
597: ap->ra_name = xstrdup(login);
598: TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
599:
600: /* not synced anymore */
601: file->rf_flags &= ~RCS_SYNCED;
602: return (0);
603: }
604:
605: /*
606: * rcs_access_remove()
607: *
608: * Remove an entry with login name <login> from the access list of the RCS
609: * file <file>.
610: * Returns 0 on success, or -1 on failure.
611: */
612: int
613: rcs_access_remove(RCSFILE *file, const char *login)
614: {
615: struct rcs_access *ap;
616:
617: TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
618: if (strcmp(ap->ra_name, login) == 0)
619: break;
620:
621: if (ap == NULL) {
622: rcs_errno = RCS_ERR_NOENT;
623: return (-1);
624: }
625:
626: TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
627: xfree(ap->ra_name);
628: xfree(ap);
629:
630: /* not synced anymore */
631: file->rf_flags &= ~RCS_SYNCED;
632: return (0);
633: }
634:
635: /*
636: * rcs_sym_add()
637: *
638: * Add a symbol to the list of symbols for the RCS file <rfp>. The new symbol
639: * is named <sym> and is bound to the RCS revision <snum>.
640: * Returns 0 on success, or -1 on failure.
641: */
642: int
643: rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
644: {
645: struct rcs_sym *symp;
646:
647: if (!rcs_sym_check(sym)) {
648: rcs_errno = RCS_ERR_BADSYM;
649: return (-1);
650: }
651:
652: /* first look for duplication */
653: TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
654: if (strcmp(symp->rs_name, sym) == 0) {
655: rcs_errno = RCS_ERR_DUPENT;
656: return (-1);
657: }
658: }
659:
660: symp = xmalloc(sizeof(*symp));
661: symp->rs_name = xstrdup(sym);
662: symp->rs_num = rcsnum_alloc();
663: rcsnum_cpy(snum, symp->rs_num, 0);
664:
665: TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
666:
667: /* not synced anymore */
668: rfp->rf_flags &= ~RCS_SYNCED;
669: return (0);
670: }
671:
672: /*
673: * rcs_sym_remove()
674: *
675: * Remove the symbol with name <sym> from the symbol list for the RCS file
676: * <file>. If no such symbol is found, the call fails and returns with an
677: * error.
678: * Returns 0 on success, or -1 on failure.
679: */
680: int
681: rcs_sym_remove(RCSFILE *file, const char *sym)
682: {
683: struct rcs_sym *symp;
684:
685: if (!rcs_sym_check(sym)) {
686: rcs_errno = RCS_ERR_BADSYM;
687: return (-1);
688: }
689:
690: TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
691: if (strcmp(symp->rs_name, sym) == 0)
692: break;
693:
694: if (symp == NULL) {
695: rcs_errno = RCS_ERR_NOENT;
696: return (-1);
697: }
698:
699: TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
700: xfree(symp->rs_name);
701: rcsnum_free(symp->rs_num);
702: xfree(symp);
703:
704: /* not synced anymore */
705: file->rf_flags &= ~RCS_SYNCED;
706: return (0);
707: }
708:
709: /*
710: * rcs_sym_getrev()
711: *
712: * Retrieve the RCS revision number associated with the symbol <sym> for the
713: * RCS file <file>. The returned value is a dynamically-allocated copy and
714: * should be freed by the caller once they are done with it.
715: * Returns the RCSNUM on success, or NULL on failure.
716: */
717: RCSNUM *
718: rcs_sym_getrev(RCSFILE *file, const char *sym)
719: {
720: RCSNUM *num;
721: struct rcs_sym *symp;
722:
723: if (!rcs_sym_check(sym)) {
724: rcs_errno = RCS_ERR_BADSYM;
725: return (NULL);
726: }
727:
728: num = NULL;
729: TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
730: if (strcmp(symp->rs_name, sym) == 0)
731: break;
732:
733: if (symp == NULL) {
734: rcs_errno = RCS_ERR_NOENT;
735: } else {
736: num = rcsnum_alloc();
737: rcsnum_cpy(symp->rs_num, num, 0);
738: }
739:
740: return (num);
741: }
742:
743: /*
744: * rcs_sym_check()
745: *
746: * Check the RCS symbol name <sym> for any unsupported characters.
747: * Returns 1 if the tag is correct, 0 if it isn't valid.
748: */
749: int
750: rcs_sym_check(const char *sym)
751: {
752: int ret;
753: const char *cp;
754:
755: ret = 1;
756: cp = sym;
757: if (!isalpha(*cp++))
758: return (0);
759:
760: for (; *cp != '\0'; cp++)
761: if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
762: ret = 0;
763: break;
764: }
765:
766: return (ret);
767: }
768:
769: /*
770: * rcs_lock_getmode()
771: *
772: * Retrieve the locking mode of the RCS file <file>.
773: */
774: int
775: rcs_lock_getmode(RCSFILE *file)
776: {
777: return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
778: }
779:
780: /*
781: * rcs_lock_setmode()
782: *
783: * Set the locking mode of the RCS file <file> to <mode>, which must either
784: * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
785: * Returns the previous mode on success, or -1 on failure.
786: */
787: int
788: rcs_lock_setmode(RCSFILE *file, int mode)
789: {
790: int pmode;
791: pmode = rcs_lock_getmode(file);
792:
793: if (mode == RCS_LOCK_STRICT)
794: file->rf_flags |= RCS_SLOCK;
795: else if (mode == RCS_LOCK_LOOSE)
796: file->rf_flags &= ~RCS_SLOCK;
797: else
798: errx(1, "rcs_lock_setmode: invalid mode `%d'", mode);
799:
800: file->rf_flags &= ~RCS_SYNCED;
801: return (pmode);
802: }
803:
804: /*
805: * rcs_lock_add()
806: *
807: * Add an RCS lock for the user <user> on revision <rev>.
808: * Returns 0 on success, or -1 on failure.
809: */
810: int
811: rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
812: {
813: struct rcs_lock *lkp;
814:
815: /* first look for duplication */
816: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
817: if (strcmp(lkp->rl_name, user) == 0 &&
818: rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
819: rcs_errno = RCS_ERR_DUPENT;
820: return (-1);
821: }
822: }
823:
824: lkp = xmalloc(sizeof(*lkp));
825: lkp->rl_name = xstrdup(user);
826: lkp->rl_num = rcsnum_alloc();
827: rcsnum_cpy(rev, lkp->rl_num, 0);
828:
829: TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
830:
831: /* not synced anymore */
832: file->rf_flags &= ~RCS_SYNCED;
833: return (0);
834: }
835:
836:
837: /*
838: * rcs_lock_remove()
839: *
840: * Remove the RCS lock on revision <rev>.
841: * Returns 0 on success, or -1 on failure.
842: */
843: int
844: rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
845: {
846: struct rcs_lock *lkp;
847:
848: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
849: if (strcmp(lkp->rl_name, user) == 0 &&
850: rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
851: break;
852: }
853:
854: if (lkp == NULL) {
855: rcs_errno = RCS_ERR_NOENT;
856: return (-1);
857: }
858:
859: TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
860: rcsnum_free(lkp->rl_num);
861: xfree(lkp->rl_name);
862: xfree(lkp);
863:
864: /* not synced anymore */
865: file->rf_flags &= ~RCS_SYNCED;
866: return (0);
867: }
868:
869: /*
870: * rcs_desc_get()
871: *
872: * Retrieve the description for the RCS file <file>.
873: */
874: const char *
875: rcs_desc_get(RCSFILE *file)
876: {
877: return (file->rf_desc);
878: }
879:
880: /*
881: * rcs_desc_set()
882: *
883: * Set the description for the RCS file <file>.
884: */
885: void
886: rcs_desc_set(RCSFILE *file, const char *desc)
887: {
888: char *tmp;
889:
890: tmp = xstrdup(desc);
891: if (file->rf_desc != NULL)
892: xfree(file->rf_desc);
893: file->rf_desc = tmp;
894: file->rf_flags &= ~RCS_SYNCED;
895: }
896:
897: /*
898: * rcs_comment_lookup()
899: *
900: * Lookup the assumed comment leader based on a file's suffix.
901: * Returns a pointer to the string on success, or NULL on failure.
902: */
903: const char *
904: rcs_comment_lookup(const char *filename)
905: {
906: int i;
907: const char *sp;
908:
909: if ((sp = strrchr(filename, '.')) == NULL) {
910: rcs_errno = RCS_ERR_NOENT;
911: return (NULL);
912: }
913: sp++;
914:
915: for (i = 0; i < (int)NB_COMTYPES; i++)
916: if (strcmp(rcs_comments[i].rc_suffix, sp) == 0)
917: return (rcs_comments[i].rc_cstr);
918: return (NULL);
919: }
920:
921: /*
922: * rcs_comment_get()
923: *
924: * Retrieve the comment leader for the RCS file <file>.
925: */
926: const char *
927: rcs_comment_get(RCSFILE *file)
928: {
929: return (file->rf_comment);
930: }
931:
932: /*
933: * rcs_comment_set()
934: *
935: * Set the comment leader for the RCS file <file>.
936: */
937: void
938: rcs_comment_set(RCSFILE *file, const char *comment)
939: {
940: char *tmp;
941:
942: tmp = xstrdup(comment);
943: if (file->rf_comment != NULL)
944: xfree(file->rf_comment);
945: file->rf_comment = tmp;
946: file->rf_flags &= ~RCS_SYNCED;
947: }
948:
949: int
950: rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
951: {
952: char op, *ep;
953: struct rcs_line *lp, *dlp, *ndlp;
954: int i, lineno, nbln;
1.35 niallo 955: u_char tmp;
1.1 joris 956:
957: dlp = TAILQ_FIRST(&(dlines->l_lines));
958: lp = TAILQ_FIRST(&(plines->l_lines));
959:
960: /* skip first bogus line */
961: for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
962: lp = TAILQ_NEXT(lp, l_list)) {
1.35 niallo 963: if (lp->l_len < 2)
964: errx(1, "line too short, RCS patch seems broken");
1.1 joris 965: op = *(lp->l_line);
1.35 niallo 966: /* NUL-terminate line buffer for strtol() safety. */
967: tmp = lp->l_line[lp->l_len - 1];
968: lp->l_line[lp->l_len - 1] = '\0';
1.1 joris 969: lineno = (int)strtol((lp->l_line + 1), &ep, 10);
970: if (lineno > dlines->l_nblines || lineno < 0 ||
971: *ep != ' ')
972: errx(1, "invalid line specification in RCS patch");
973: ep++;
974: nbln = (int)strtol(ep, &ep, 10);
1.35 niallo 975: /* Restore the last byte of the buffer */
976: lp->l_line[lp->l_len - 1] = tmp;
977: if (nbln < 0)
1.1 joris 978: errx(1,
979: "invalid line number specification in RCS patch");
980:
981: /* find the appropriate line */
982: for (;;) {
983: if (dlp == NULL)
984: break;
985: if (dlp->l_lineno == lineno)
986: break;
987: if (dlp->l_lineno > lineno) {
1.58 ray 988: dlp = TAILQ_PREV(dlp, tqh, l_list);
1.1 joris 989: } else if (dlp->l_lineno < lineno) {
990: if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
991: ndlp->l_lineno > lineno)
992: break;
993: dlp = ndlp;
994: }
995: }
996: if (dlp == NULL)
997: errx(1, "can't find referenced line in RCS patch");
998:
999: if (op == 'd') {
1000: for (i = 0; (i < nbln) && (dlp != NULL); i++) {
1001: ndlp = TAILQ_NEXT(dlp, l_list);
1002: TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
1.21 niallo 1003: xfree(dlp);
1.1 joris 1004: dlp = ndlp;
1005: /* last line is gone - reset dlp */
1006: if (dlp == NULL) {
1007: ndlp = TAILQ_LAST(&(dlines->l_lines),
1.58 ray 1008: tqh);
1.1 joris 1009: dlp = ndlp;
1010: }
1011: }
1012: } else if (op == 'a') {
1013: for (i = 0; i < nbln; i++) {
1014: ndlp = lp;
1015: lp = TAILQ_NEXT(lp, l_list);
1016: if (lp == NULL)
1017: errx(1, "truncated RCS patch");
1018: TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
1019: TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
1020: lp, l_list);
1021: dlp = lp;
1022:
1023: /* we don't want lookup to block on those */
1024: lp->l_lineno = lineno;
1025:
1026: lp = ndlp;
1027: }
1028: } else
1029: errx(1, "unknown RCS patch operation `%c'", op);
1030:
1031: /* last line of the patch, done */
1032: if (lp->l_lineno == plines->l_nblines)
1033: break;
1034: }
1035:
1036: /* once we're done patching, rebuild the line numbers */
1037: lineno = 0;
1038: TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
1039: lp->l_lineno = lineno++;
1040: dlines->l_nblines = lineno - 1;
1041:
1042: return (0);
1043: }
1044:
1045: /*
1046: * rcs_getrev()
1047: *
1048: * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The
1049: * returned buffer is dynamically allocated and should be released using
1.59 ray 1050: * buf_free() once the caller is done using it.
1.1 joris 1051: */
1.61 tobias 1052: BUF *
1.1 joris 1053: rcs_getrev(RCSFILE *rfp, RCSNUM *frev)
1054: {
1055: u_int i, numlen;
1.6 niallo 1056: int isbranch, lookonbranch, found;
1.35 niallo 1057: size_t dlen, plen, len;
1.1 joris 1058: RCSNUM *crev, *rev, *brev;
1.35 niallo 1059: BUF *rbuf;
1.1 joris 1060: struct rcs_delta *rdp = NULL;
1061: struct rcs_branch *rb;
1.35 niallo 1062: u_char *data, *patch;
1.1 joris 1063:
1064: if (rfp->rf_head == NULL)
1065: return (NULL);
1066:
1067: if (frev == RCS_HEAD_REV)
1068: rev = rfp->rf_head;
1069: else
1070: rev = frev;
1071:
1072: /* XXX rcsnum_cmp() */
1073: for (i = 0; i < rfp->rf_head->rn_len; i++) {
1074: if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) {
1075: rcs_errno = RCS_ERR_NOENT;
1076: return (NULL);
1077: }
1078: }
1079:
1.54 jj 1080: /* No matter what, we'll need everything parsed up until the description
1081: so go for it. */
1.68 tobias 1082: if (rcsparse_deltas(rfp, NULL))
1083: return (NULL);
1.1 joris 1084:
1085: rdp = rcs_findrev(rfp, rfp->rf_head);
1086: if (rdp == NULL) {
1087: warnx("failed to get RCS HEAD revision");
1088: return (NULL);
1089: }
1090:
1091: if (rdp->rd_tlen == 0)
1.68 tobias 1092: if (rcsparse_deltatexts(rfp, rfp->rf_head))
1093: return (NULL);
1.1 joris 1094:
1095: len = rdp->rd_tlen;
1096: if (len == 0) {
1.60 ray 1097: rbuf = buf_alloc(1);
1.59 ray 1098: buf_empty(rbuf);
1.1 joris 1099: return (rbuf);
1100: }
1101:
1.60 ray 1102: rbuf = buf_alloc(len);
1.59 ray 1103: buf_append(rbuf, rdp->rd_text, len);
1.1 joris 1104:
1105: isbranch = 0;
1106: brev = NULL;
1107:
1108: /*
1109: * If a branch was passed, get the latest revision on it.
1110: */
1111: if (RCSNUM_ISBRANCH(rev)) {
1112: brev = rev;
1113: rdp = rcs_findrev(rfp, rev);
1.64 nicm 1114: if (rdp == NULL) {
1115: buf_free(rbuf);
1.1 joris 1116: return (NULL);
1.64 nicm 1117: }
1.1 joris 1118:
1119: rev = rdp->rd_num;
1120: } else {
1121: if (RCSNUM_ISBRANCHREV(rev)) {
1122: brev = rcsnum_revtobr(rev);
1123: isbranch = 1;
1124: }
1125: }
1126:
1127: lookonbranch = 0;
1128: crev = NULL;
1129:
1130: /* Apply patches backwards to get the right version.
1131: */
1132: do {
1.6 niallo 1133: found = 0;
1.26 deraadt 1134:
1.1 joris 1135: if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0)
1136: break;
1137:
1138: if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len &&
1139: !TAILQ_EMPTY(&(rdp->rd_branches)))
1140: lookonbranch = 1;
1141:
1142: if (isbranch && lookonbranch == 1) {
1143: lookonbranch = 0;
1144: TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
1145: /* XXX rcsnum_cmp() is totally broken for
1146: * this purpose.
1147: */
1.50 tobias 1148: numlen = MIN(brev->rn_len,
1149: rb->rb_num->rn_len - 1);
1.1 joris 1150: for (i = 0; i < numlen; i++) {
1151: if (rb->rb_num->rn_id[i] !=
1152: brev->rn_id[i])
1153: break;
1154: }
1155:
1156: if (i == numlen) {
1157: crev = rb->rb_num;
1.6 niallo 1158: found = 1;
1.1 joris 1159: break;
1160: }
1161: }
1.6 niallo 1162: if (found == 0)
1163: crev = rdp->rd_next;
1.1 joris 1164: } else {
1165: crev = rdp->rd_next;
1166: }
1167:
1168: rdp = rcs_findrev(rfp, crev);
1169: if (rdp == NULL) {
1.59 ray 1170: buf_free(rbuf);
1.1 joris 1171: return (NULL);
1172: }
1173:
1.35 niallo 1174: plen = rdp->rd_tlen;
1.59 ray 1175: dlen = buf_len(rbuf);
1.35 niallo 1176: patch = rdp->rd_text;
1.59 ray 1177: data = buf_release(rbuf);
1.1 joris 1178: /* check if we have parsed this rev's deltatext */
1179: if (rdp->rd_tlen == 0)
1.68 tobias 1180: if (rcsparse_deltatexts(rfp, rdp->rd_num))
1181: return (NULL);
1.1 joris 1182:
1.35 niallo 1183: rbuf = rcs_patchfile(data, dlen, patch, plen, rcs_patch_lines);
1.64 nicm 1184: xfree(data);
1.1 joris 1185:
1186: if (rbuf == NULL)
1187: break;
1188: } while (rcsnum_cmp(crev, rev, 0) != 0);
1189:
1190: return (rbuf);
1191: }
1.46 xsa 1192:
1193: void
1194: rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved)
1195: {
1196: struct rcs_lines *plines;
1197: struct rcs_line *lp;
1.73 jasper 1198: int added, i, nbln, removed;
1.46 xsa 1199: char op, *ep;
1200: u_char tmp;
1201:
1202: added = removed = 0;
1203:
1204: plines = rcs_splitlines(rdp->rd_text, rdp->rd_tlen);
1205: lp = TAILQ_FIRST(&(plines->l_lines));
1206:
1207: /* skip first bogus line */
1208: for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
1209: lp = TAILQ_NEXT(lp, l_list)) {
1210: if (lp->l_len < 2)
1211: errx(1,
1212: "line too short, RCS patch seems broken");
1213: op = *(lp->l_line);
1214: /* NUL-terminate line buffer for strtol() safety. */
1215: tmp = lp->l_line[lp->l_len - 1];
1216: lp->l_line[lp->l_len - 1] = '\0';
1.74 ! djm 1217: (void)strtol((lp->l_line + 1), &ep, 10);
1.46 xsa 1218: ep++;
1219: nbln = (int)strtol(ep, &ep, 10);
1220: /* Restore the last byte of the buffer */
1221: lp->l_line[lp->l_len - 1] = tmp;
1222: if (nbln < 0)
1223: errx(1, "invalid line number specification "
1224: "in RCS patch");
1225:
1226: if (op == 'a') {
1227: added += nbln;
1228: for (i = 0; i < nbln; i++) {
1229: lp = TAILQ_NEXT(lp, l_list);
1230: if (lp == NULL)
1231: errx(1, "truncated RCS patch");
1232: }
1233: } else if (op == 'd')
1234: removed += nbln;
1235: else
1236: errx(1, "unknown RCS patch operation '%c'", op);
1237: }
1.47 tobias 1238:
1239: rcs_freelines(plines);
1.46 xsa 1240:
1241: *ladded = added;
1242: *lremoved = removed;
1243: }
1244:
1.1 joris 1245:
1246: /*
1247: * rcs_rev_add()
1248: *
1249: * Add a revision to the RCS file <rf>. The new revision's number can be
1250: * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
1251: * new revision will have a number equal to the previous head revision plus
1252: * one). The <msg> argument specifies the log message for that revision, and
1253: * <date> specifies the revision's date (a value of -1 is
1254: * equivalent to using the current time).
1.57 ray 1255: * If <author> is NULL, set the author for this revision to the current user.
1.1 joris 1256: * Returns 0 on success, or -1 on failure.
1257: */
1258: int
1259: rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
1.57 ray 1260: const char *author)
1.1 joris 1261: {
1262: time_t now;
1263: struct passwd *pw;
1264: struct rcs_delta *ordp, *rdp;
1265:
1266: if (rev == RCS_HEAD_REV) {
1267: if (rf->rf_flags & RCS_CREATE) {
1268: if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
1269: return (-1);
1.62 tobias 1270: rf->rf_head = rev;
1.1 joris 1271: } else {
1272: rev = rcsnum_inc(rf->rf_head);
1273: }
1274: } else {
1275: if ((rdp = rcs_findrev(rf, rev)) != NULL) {
1276: rcs_errno = RCS_ERR_DUPENT;
1277: return (-1);
1278: }
1279: }
1280:
1281: rdp = xcalloc(1, sizeof(*rdp));
1282:
1283: TAILQ_INIT(&(rdp->rd_branches));
1284:
1285: rdp->rd_num = rcsnum_alloc();
1286: rcsnum_cpy(rev, rdp->rd_num, 0);
1287:
1288: rdp->rd_next = rcsnum_alloc();
1289:
1290: if (!(rf->rf_flags & RCS_CREATE)) {
1291: /* next should point to the previous HEAD */
1292: ordp = TAILQ_FIRST(&(rf->rf_delta));
1293: rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
1294: }
1295:
1.57 ray 1296: if (!author && !(author = getlogin())) {
1297: if (!(pw = getpwuid(getuid())))
1298: errx(1, "getpwuid failed");
1299: author = pw->pw_name;
1300: }
1301: rdp->rd_author = xstrdup(author);
1.1 joris 1302: rdp->rd_state = xstrdup(RCS_STATE_EXP);
1303: rdp->rd_log = xstrdup(msg);
1304:
1305: if (date != (time_t)(-1))
1306: now = date;
1307: else
1308: time(&now);
1309: gmtime_r(&now, &(rdp->rd_date));
1310:
1311: TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
1312: rf->rf_ndelta++;
1313:
1314: /* not synced anymore */
1315: rf->rf_flags &= ~RCS_SYNCED;
1316:
1317: return (0);
1318: }
1319:
1320: /*
1321: * rcs_rev_remove()
1322: *
1323: * Remove the revision whose number is <rev> from the RCS file <rf>.
1324: */
1325: int
1326: rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
1327: {
1.12 niallo 1328: char *path_tmp1, *path_tmp2;
1.1 joris 1329: struct rcs_delta *rdp, *prevrdp, *nextrdp;
1.12 niallo 1330: BUF *newdeltatext, *nextbuf, *prevbuf, *newdiff;
1331:
1332: nextrdp = prevrdp = NULL;
1.17 niallo 1333: path_tmp1 = path_tmp2 = NULL;
1.1 joris 1334:
1335: if (rev == RCS_HEAD_REV)
1336: rev = rf->rf_head;
1337:
1338: /* do we actually have that revision? */
1339: if ((rdp = rcs_findrev(rf, rev)) == NULL) {
1340: rcs_errno = RCS_ERR_NOENT;
1341: return (-1);
1342: }
1343:
1344: /*
1345: * This is confusing, the previous delta is next in the TAILQ list.
1346: * the next delta is the previous one in the TAILQ list.
1347: *
1348: * When the HEAD revision got specified, nextrdp will be NULL.
1349: * When the first revision got specified, prevrdp will be NULL.
1350: */
1351: prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
1.58 ray 1352: nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list);
1.1 joris 1353:
1.12 niallo 1354: newdeltatext = prevbuf = nextbuf = NULL;
1.1 joris 1355:
1356: if (prevrdp != NULL) {
1357: if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL)
1358: errx(1, "error getting revision");
1359: }
1360:
1361: if (prevrdp != NULL && nextrdp != NULL) {
1362: if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL)
1363: errx(1, "error getting revision");
1364:
1.60 ray 1365: newdiff = buf_alloc(64);
1.1 joris 1366:
1367: /* calculate new diff */
1.7 xsa 1368: (void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir);
1.59 ray 1369: buf_write_stmp(nextbuf, path_tmp1);
1370: buf_free(nextbuf);
1.1 joris 1371:
1.7 xsa 1372: (void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir);
1.59 ray 1373: buf_write_stmp(prevbuf, path_tmp2);
1374: buf_free(prevbuf);
1.1 joris 1375:
1376: diff_format = D_RCSDIFF;
1.53 ray 1377: if (diffreg(path_tmp1, path_tmp2, newdiff, D_FORCEASCII) == D_ERROR)
1.41 ray 1378: errx(1, "diffreg failed");
1.1 joris 1379:
1.12 niallo 1380: newdeltatext = newdiff;
1.1 joris 1381: } else if (nextrdp == NULL && prevrdp != NULL) {
1.12 niallo 1382: newdeltatext = prevbuf;
1.1 joris 1383: }
1384:
1385: if (newdeltatext != NULL) {
1386: if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
1387: errx(1, "error setting new deltatext");
1388: }
1389:
1390: TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
1391:
1392: /* update pointers */
1393: if (prevrdp != NULL && nextrdp != NULL) {
1394: rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
1395: } else if (prevrdp != NULL) {
1396: if (rcs_head_set(rf, prevrdp->rd_num) < 0)
1397: errx(1, "rcs_head_set failed");
1398: } else if (nextrdp != NULL) {
1399: rcsnum_free(nextrdp->rd_next);
1400: nextrdp->rd_next = rcsnum_alloc();
1401: } else {
1402: rcsnum_free(rf->rf_head);
1403: rf->rf_head = NULL;
1404: }
1405:
1406: rf->rf_ndelta--;
1407: rf->rf_flags &= ~RCS_SYNCED;
1408:
1409: rcs_freedelta(rdp);
1410:
1.7 xsa 1411: if (path_tmp1 != NULL)
1412: xfree(path_tmp1);
1413: if (path_tmp2 != NULL)
1414: xfree(path_tmp2);
1.1 joris 1415:
1416: return (0);
1417: }
1418:
1419: /*
1420: * rcs_findrev()
1421: *
1422: * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
1423: * The revision number is given in <rev>.
1424: *
1425: * If the given revision is a branch number, we translate it into the latest
1426: * revision on the branch.
1427: *
1428: * Returns a pointer to the delta on success, or NULL on failure.
1429: */
1430: struct rcs_delta *
1431: rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
1432: {
1433: u_int cmplen;
1434: struct rcs_delta *rdp;
1435: RCSNUM *brev, *frev;
1436:
1437: /*
1438: * We need to do more parsing if the last revision in the linked list
1439: * is greater than the requested revision.
1440: */
1441: rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1442: if (rdp == NULL ||
1443: rcsnum_cmp(rdp->rd_num, rev, 0) == -1) {
1.68 tobias 1444: if (rcsparse_deltas(rfp, rev))
1445: return (NULL);
1.1 joris 1446: }
1447:
1448: /*
1449: * Translate a branch into the latest revision on the branch itself.
1450: */
1451: if (RCSNUM_ISBRANCH(rev)) {
1452: brev = rcsnum_brtorev(rev);
1453: frev = brev;
1454: for (;;) {
1455: rdp = rcs_findrev(rfp, frev);
1456: if (rdp == NULL)
1457: return (NULL);
1458:
1459: if (rdp->rd_next->rn_len == 0)
1460: break;
1461:
1462: frev = rdp->rd_next;
1463: }
1464:
1465: rcsnum_free(brev);
1466: return (rdp);
1467: }
1468:
1469: cmplen = rev->rn_len;
1470:
1471: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1472: if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0)
1473: return (rdp);
1474: }
1475:
1476: return (NULL);
1477: }
1478:
1479: /*
1480: * rcs_kwexp_set()
1481: *
1482: * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
1483: */
1484: void
1485: rcs_kwexp_set(RCSFILE *file, int mode)
1486: {
1487: int i;
1488: char *tmp, buf[8] = "";
1489:
1490: if (RCS_KWEXP_INVAL(mode))
1491: return;
1492:
1493: i = 0;
1494: if (mode == RCS_KWEXP_NONE)
1495: buf[0] = 'b';
1496: else if (mode == RCS_KWEXP_OLD)
1497: buf[0] = 'o';
1498: else {
1499: if (mode & RCS_KWEXP_NAME)
1500: buf[i++] = 'k';
1501: if (mode & RCS_KWEXP_VAL)
1502: buf[i++] = 'v';
1503: if (mode & RCS_KWEXP_LKR)
1504: buf[i++] = 'l';
1505: }
1506:
1507: tmp = xstrdup(buf);
1508: if (file->rf_expand != NULL)
1509: xfree(file->rf_expand);
1510: file->rf_expand = tmp;
1511: /* not synced anymore */
1512: file->rf_flags &= ~RCS_SYNCED;
1513: }
1514:
1515: /*
1516: * rcs_kwexp_get()
1517: *
1518: * Retrieve the keyword expansion mode to be used for the RCS file <file>.
1519: */
1520: int
1521: rcs_kwexp_get(RCSFILE *file)
1522: {
1.71 tobias 1523: if (file->rf_expand == NULL)
1524: return (RCS_KWEXP_DEFAULT);
1525:
1526: return (rcs_kflag_get(file->rf_expand));
1.1 joris 1527: }
1528:
1529: /*
1530: * rcs_kflag_get()
1531: *
1532: * Get the keyword expansion mode from a set of character flags given in
1533: * <flags> and return the appropriate flag mask. In case of an error, the
1534: * returned mask will have the RCS_KWEXP_ERR bit set to 1.
1535: */
1536: int
1537: rcs_kflag_get(const char *flags)
1538: {
1539: int fl;
1540: size_t len;
1541: const char *fp;
1542:
1.71 tobias 1543: if (flags == NULL || !(len = strlen(flags)))
1544: return (RCS_KWEXP_ERR);
1545:
1.1 joris 1546: fl = 0;
1547: for (fp = flags; *fp != '\0'; fp++) {
1548: if (*fp == 'k')
1549: fl |= RCS_KWEXP_NAME;
1550: else if (*fp == 'v')
1551: fl |= RCS_KWEXP_VAL;
1552: else if (*fp == 'l')
1553: fl |= RCS_KWEXP_LKR;
1554: else if (*fp == 'o') {
1555: if (len != 1)
1556: fl |= RCS_KWEXP_ERR;
1557: fl |= RCS_KWEXP_OLD;
1558: } else if (*fp == 'b') {
1559: if (len != 1)
1560: fl |= RCS_KWEXP_ERR;
1.71 tobias 1561: fl |= RCS_KWEXP_NONE;
1.1 joris 1562: } else /* unknown letter */
1563: fl |= RCS_KWEXP_ERR;
1564: }
1565:
1566: return (fl);
1567: }
1568:
1569: /*
1570: * rcs_errstr()
1571: *
1572: * Get the error string matching the RCS error code <code>.
1573: */
1574: const char *
1575: rcs_errstr(int code)
1576: {
1577: const char *esp;
1578:
1579: if (code < 0 || (code >= (int)RCS_NERR && code != RCS_ERR_ERRNO))
1580: esp = NULL;
1581: else if (code == RCS_ERR_ERRNO)
1582: esp = strerror(errno);
1583: else
1584: esp = rcs_errstrs[code];
1585: return (esp);
1586: }
1587:
1588: /*
1589: * rcs_freedelta()
1590: *
1591: * Free the contents of a delta structure.
1592: */
1593: static void
1594: rcs_freedelta(struct rcs_delta *rdp)
1595: {
1596: struct rcs_branch *rb;
1597:
1598: if (rdp->rd_num != NULL)
1599: rcsnum_free(rdp->rd_num);
1600: if (rdp->rd_next != NULL)
1601: rcsnum_free(rdp->rd_next);
1602:
1603: if (rdp->rd_author != NULL)
1604: xfree(rdp->rd_author);
1605: if (rdp->rd_locker != NULL)
1606: xfree(rdp->rd_locker);
1607: if (rdp->rd_state != NULL)
1608: xfree(rdp->rd_state);
1609: if (rdp->rd_log != NULL)
1610: xfree(rdp->rd_log);
1611: if (rdp->rd_text != NULL)
1612: xfree(rdp->rd_text);
1613:
1614: while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
1615: TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
1616: rcsnum_free(rb->rb_num);
1617: xfree(rb);
1618: }
1619:
1620: xfree(rdp);
1621: }
1622:
1623: /*
1624: * rcs_strprint()
1625: *
1626: * Output an RCS string <str> of size <slen> to the stream <stream>. Any
1627: * '@' characters are escaped. Otherwise, the string can contain arbitrary
1628: * binary data.
1629: */
1630: static void
1631: rcs_strprint(const u_char *str, size_t slen, FILE *stream)
1632: {
1633: const u_char *ap, *ep, *sp;
1634:
1635: if (slen == 0)
1636: return;
1637:
1638: ep = str + slen - 1;
1639:
1640: for (sp = str; sp <= ep;) {
1641: ap = memchr(sp, '@', ep - sp);
1642: if (ap == NULL)
1643: ap = ep;
1644: (void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
1645:
1646: if (*ap == '@')
1647: putc('@', stream);
1648: sp = ap + 1;
1649: }
1650: }
1651:
1652: /*
1653: * rcs_expand_keywords()
1654: *
1655: * Return expansion any RCS keywords in <data>
1656: *
1657: * On error, return NULL.
1658: */
1.12 niallo 1659: static BUF *
1660: rcs_expand_keywords(char *rcsfile, struct rcs_delta *rdp, BUF *bp, int mode)
1.1 joris 1661: {
1.28 ray 1662: BUF *newbuf;
1.1 joris 1663: int kwtype;
1664: u_int j, found;
1.28 ray 1665: u_char *c, *kwstr, *start, *end, *fin;
1.1 joris 1666: char expbuf[256], buf[256];
1667: struct tm tb;
1668: char *fmt;
1.28 ray 1669: size_t len;
1.1 joris 1670:
1671: kwtype = 0;
1672: kwstr = NULL;
1673:
1674: /*
1675: * -z support for RCS
1676: */
1677: tb = rdp->rd_date;
1678: if (timezone_flag != NULL)
1679: rcs_set_tz(timezone_flag, rdp, &tb);
1680:
1.59 ray 1681: len = buf_len(bp);
1.12 niallo 1682:
1.59 ray 1683: c = buf_get(bp);
1.12 niallo 1684: found = 0;
1.28 ray 1685: /* Final character in buffer. */
1686: fin = c + len - 1;
1.12 niallo 1687:
1.28 ray 1688: /* If no keywords are found, return original buffer. */
1689: newbuf = bp;
1.18 niallo 1690:
1.1 joris 1691: /*
1692: * Keyword formats:
1693: * $Keyword$
1694: * $Keyword: value$
1695: */
1.18 niallo 1696: for (; c < fin; c++) {
1.1 joris 1697: if (*c == '$') {
1.28 ray 1698: BUF *tmpbuf;
1699: size_t clen;
1700:
1.1 joris 1701: /* remember start of this possible keyword */
1702: start = c;
1703:
1704: /* first following character has to be alphanumeric */
1705: c++;
1706: if (!isalpha(*c)) {
1707: c = start;
1708: continue;
1709: }
1710:
1.28 ray 1711: /* Number of characters between c and fin, inclusive. */
1712: clen = fin - c + 1;
1713:
1.1 joris 1714: /* look for any matching keywords */
1715: found = 0;
1716: for (j = 0; j < RCS_NKWORDS; j++) {
1.28 ray 1717: size_t kwlen;
1718:
1719: kwlen = strlen(rcs_expkw[j].kw_str);
1720: /*
1721: * kwlen must be less than clen since clen
1722: * includes either a terminating `$' or a `:'.
1723: */
1724: if (kwlen < clen &&
1725: memcmp(c, rcs_expkw[j].kw_str, kwlen) == 0 &&
1726: (c[kwlen] == '$' || c[kwlen] == ':')) {
1.1 joris 1727: found = 1;
1728: kwstr = rcs_expkw[j].kw_str;
1729: kwtype = rcs_expkw[j].kw_type;
1.28 ray 1730: c += kwlen;
1.1 joris 1731: break;
1732: }
1733: }
1734:
1735: /* unknown keyword, continue looking */
1736: if (found == 0) {
1737: c = start;
1738: continue;
1739: }
1740:
1741: /*
1742: * if the next character was ':' we need to look for
1743: * an '$' before the end of the line to be sure it is
1744: * in fact a keyword.
1745: */
1746: if (*c == ':') {
1.28 ray 1747: for (; c <= fin; ++c) {
1.1 joris 1748: if (*c == '$' || *c == '\n')
1749: break;
1750: }
1751:
1752: if (*c != '$') {
1753: c = start;
1754: continue;
1755: }
1756: }
1757: end = c + 1;
1758:
1759: /* start constructing the expansion */
1760: expbuf[0] = '\0';
1761:
1762: if (mode & RCS_KWEXP_NAME) {
1.31 ray 1763: char *tmp;
1764:
1765: (void)xasprintf(&tmp, "$%s%s", kwstr,
1766: (mode & RCS_KWEXP_VAL) ? ": " : "");
1767: if (strlcat(expbuf, tmp, sizeof(expbuf)) >= sizeof(expbuf))
1.5 ray 1768: errx(1, "rcs_expand_keywords: string truncated");
1.31 ray 1769: xfree(tmp);
1.1 joris 1770: }
1771:
1772: /*
1773: * order matters because of RCS_KW_ID and
1774: * RCS_KW_HEADER here
1775: */
1776: if (mode & RCS_KWEXP_VAL) {
1777: if (kwtype & RCS_KW_RCSFILE) {
1.31 ray 1778: char *tmp;
1779:
1780: (void)xasprintf(&tmp, "%s ",
1781: (kwtype & RCS_KW_FULLPATH) ? rcsfile : basename(rcsfile));
1782: if (strlcat(expbuf, tmp, sizeof(expbuf)) >= sizeof(expbuf))
1.5 ray 1783: errx(1, "rcs_expand_keywords: string truncated");
1.31 ray 1784: xfree(tmp);
1.1 joris 1785: }
1786:
1787: if (kwtype & RCS_KW_REVISION) {
1.31 ray 1788: char *tmp;
1789:
1.5 ray 1790: rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1.31 ray 1791: (void)xasprintf(&tmp, "%s ", buf);
1792: if (strlcat(expbuf, tmp, sizeof(expbuf)) >= sizeof(buf))
1.5 ray 1793: errx(1, "rcs_expand_keywords: string truncated");
1.31 ray 1794: xfree(tmp);
1.1 joris 1795: }
1796:
1797: if (kwtype & RCS_KW_DATE) {
1798: if (timezone_flag != NULL)
1799: fmt = "%Y/%m/%d %H:%M:%S%z ";
1800: else
1801: fmt = "%Y/%m/%d %H:%M:%S ";
1802:
1803: strftime(buf, sizeof(buf), fmt, &tb);
1.5 ray 1804: if (strlcat(expbuf, buf, sizeof(expbuf)) >= sizeof(expbuf))
1805: errx(1, "rcs_expand_keywords: string truncated");
1.1 joris 1806: }
1807:
1808: if (kwtype & RCS_KW_AUTHOR) {
1.31 ray 1809: char *tmp;
1810:
1811: (void)xasprintf(&tmp, "%s ", rdp->rd_author);
1812: if (strlcat(expbuf, tmp, sizeof(expbuf)) >= sizeof(expbuf))
1.5 ray 1813: errx(1, "rcs_expand_keywords: string truncated");
1.31 ray 1814: xfree(tmp);
1.1 joris 1815: }
1816:
1817: if (kwtype & RCS_KW_STATE) {
1.31 ray 1818: char *tmp;
1819:
1820: (void)xasprintf(&tmp, "%s ", rdp->rd_state);
1821: if (strlcat(expbuf, tmp, sizeof(expbuf)) >= sizeof(expbuf))
1.5 ray 1822: errx(1, "rcs_expand_keywords: string truncated");
1.31 ray 1823: xfree(tmp);
1.1 joris 1824: }
1825:
1826: /* order does not matter anymore below */
1827: if (kwtype & RCS_KW_LOG)
1.5 ray 1828: if (strlcat(expbuf, " ", sizeof(expbuf)) >= sizeof(expbuf))
1829: errx(1, "rcs_expand_keywords: string truncated");
1.1 joris 1830:
1831: if (kwtype & RCS_KW_SOURCE) {
1.31 ray 1832: char *tmp;
1833:
1834: (void)xasprintf(&tmp, "%s ", rcsfile);
1835: if (strlcat(expbuf, tmp, sizeof(expbuf)) >= sizeof(expbuf))
1.5 ray 1836: errx(1, "rcs_expand_keywords: string truncated");
1.31 ray 1837: xfree(tmp);
1.1 joris 1838: }
1839:
1840: if (kwtype & RCS_KW_NAME)
1.5 ray 1841: if (strlcat(expbuf, " ", sizeof(expbuf)) >= sizeof(expbuf))
1842: errx(1, "rcs_expand_keywords: string truncated");
1.1 joris 1843: }
1844:
1845: /* end the expansion */
1846: if (mode & RCS_KWEXP_NAME)
1.5 ray 1847: if (strlcat(expbuf, "$", sizeof(expbuf)) >= sizeof(expbuf))
1848: errx(1, "rcs_expand_keywords: string truncated");
1.1 joris 1849:
1.28 ray 1850: /* Concatenate everything together. */
1.60 ray 1851: tmpbuf = buf_alloc(len + strlen(expbuf));
1.28 ray 1852: /* Append everything before keyword. */
1.59 ray 1853: buf_append(tmpbuf, buf_get(newbuf),
1854: start - (unsigned char *)buf_get(newbuf));
1.28 ray 1855: /* Append keyword. */
1.59 ray 1856: buf_append(tmpbuf, expbuf, strlen(expbuf));
1.28 ray 1857: /* Point c to end of keyword. */
1.59 ray 1858: c = buf_get(tmpbuf) + buf_len(tmpbuf) - 1;
1.28 ray 1859: /* Append everything after keyword. */
1.59 ray 1860: buf_append(tmpbuf, end,
1861: ((unsigned char *)buf_get(newbuf) + buf_len(newbuf)) - end);
1.28 ray 1862: /* Point fin to end of data. */
1.59 ray 1863: fin = buf_get(tmpbuf) + buf_len(tmpbuf) - 1;
1.29 ray 1864: /* Recalculate new length. */
1.59 ray 1865: len = buf_len(tmpbuf);
1.28 ray 1866:
1867: /* tmpbuf is now ready, free old newbuf if allocated here. */
1868: if (newbuf != bp)
1.59 ray 1869: buf_free(newbuf);
1.28 ray 1870: newbuf = tmpbuf;
1.1 joris 1871: }
1872: }
1.18 niallo 1873:
1.28 ray 1874: return (newbuf);
1.1 joris 1875: }
1876:
1877: /*
1878: * rcs_deltatext_set()
1879: *
1880: * Set deltatext for <rev> in RCS file <rfp> to <dtext>
1881: * Returns -1 on error, 0 on success.
1882: */
1883: int
1.12 niallo 1884: rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
1.1 joris 1885: {
1886: size_t len;
1.12 niallo 1887: u_char *dtext;
1.1 joris 1888: struct rcs_delta *rdp;
1889:
1890: /* Write operations require full parsing */
1.68 tobias 1891: if (rcsparse_deltatexts(rfp, NULL))
1892: return (-1);
1.1 joris 1893:
1894: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1895: return (-1);
1896:
1897: if (rdp->rd_text != NULL)
1898: xfree(rdp->rd_text);
1899:
1.59 ray 1900: len = buf_len(bp);
1901: dtext = buf_release(bp);
1.12 niallo 1902: bp = NULL;
1.59 ray 1903:
1.1 joris 1904: if (len != 0) {
1.12 niallo 1905: rdp->rd_text = xmalloc(len);
1.1 joris 1906: rdp->rd_tlen = len;
1.12 niallo 1907: (void)memcpy(rdp->rd_text, dtext, len);
1.1 joris 1908: } else {
1909: rdp->rd_text = NULL;
1910: rdp->rd_tlen = 0;
1911: }
1.70 tobias 1912:
1913: if (dtext != NULL)
1914: xfree(dtext);
1.1 joris 1915:
1916: return (0);
1917: }
1918:
1919: /*
1920: * rcs_rev_setlog()
1921: *
1.51 tobias 1922: * Sets the log message of revision <rev> to <logtext>.
1.1 joris 1923: */
1924: int
1925: rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
1926: {
1927: struct rcs_delta *rdp;
1928:
1929: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1930: return (-1);
1931:
1932: if (rdp->rd_log != NULL)
1933: xfree(rdp->rd_log);
1934:
1935: rdp->rd_log = xstrdup(logtext);
1936: rfp->rf_flags &= ~RCS_SYNCED;
1937: return (0);
1938: }
1939: /*
1940: * rcs_rev_getdate()
1941: *
1942: * Get the date corresponding to a given revision.
1943: * Returns the date on success, -1 on failure.
1944: */
1945: time_t
1946: rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
1947: {
1948: struct rcs_delta *rdp;
1949:
1950: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1951: return (-1);
1952:
1953: return (mktime(&rdp->rd_date));
1954: }
1955:
1956: /*
1957: * rcs_state_set()
1958: *
1959: * Sets the state of revision <rev> to <state>
1960: * NOTE: default state is 'Exp'. States may not contain spaces.
1961: *
1962: * Returns -1 on failure, 0 on success.
1963: */
1964: int
1965: rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
1966: {
1967: struct rcs_delta *rdp;
1968:
1969: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1970: return (-1);
1971:
1972: if (rdp->rd_state != NULL)
1973: xfree(rdp->rd_state);
1974:
1975: rdp->rd_state = xstrdup(state);
1976:
1977: rfp->rf_flags &= ~RCS_SYNCED;
1978:
1979: return (0);
1980: }
1981:
1982: /*
1983: * rcs_state_check()
1984: *
1985: * Check if string <state> is valid.
1986: *
1987: * Returns 0 if the string is valid, -1 otherwise.
1988: */
1989: int
1990: rcs_state_check(const char *state)
1991: {
1.66 tobias 1992: int ret;
1993: const char *cp;
1994:
1995: ret = 0;
1996: cp = state;
1997: if (!isalpha(*cp++))
1.1 joris 1998: return (-1);
1999:
1.66 tobias 2000: for (; *cp != '\0'; cp++)
2001: if (!isgraph(*cp) || (strchr(rcs_state_invch, *cp) != NULL)) {
2002: ret = -1;
2003: break;
2004: }
2005:
2006: return (ret);
1.1 joris 2007: }
2008:
2009: /*
2010: * rcs_state_get()
2011: *
2012: * Get the state for a given revision of a specified RCSFILE.
2013: *
2014: * Returns NULL on failure.
2015: */
2016: const char *
2017: rcs_state_get(RCSFILE *rfp, RCSNUM *rev)
2018: {
2019: struct rcs_delta *rdp;
2020:
2021: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2022: return (NULL);
2023:
2024: return (rdp->rd_state);
2025: }
2026:
2027: /*
2028: * rcs_kwexp_buf()
2029: *
2030: * Do keyword expansion on a buffer if necessary
2031: *
2032: */
2033: BUF *
2034: rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
2035: {
2036: struct rcs_delta *rdp;
2037: int expmode;
2038:
2039: /*
2040: * Do keyword expansion if required.
2041: */
1.71 tobias 2042: expmode = rcs_kwexp_get(rf);
1.1 joris 2043:
2044: if (!(expmode & RCS_KWEXP_NONE)) {
2045: if ((rdp = rcs_findrev(rf, rev)) == NULL)
1.5 ray 2046: errx(1, "could not fetch revision");
1.12 niallo 2047: return (rcs_expand_keywords(rf->rf_path, rdp, bp, expmode));
1.1 joris 2048: }
2049: return (bp);
2050: }