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