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