Annotation of src/usr.bin/rcs/rcs.c, Revision 1.78
1.78 ! sobrado 1: /* $OpenBSD: rcs.c,v 1.77 2011/07/06 15:36:52 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 },
1.77 nicm 56: { "Locker", RCS_KW_LOCKER },
1.1 joris 57: { "Header", RCS_KW_HEADER },
58: { "Id", RCS_KW_ID },
1.49 deraadt 59: { "OpenBSD", RCS_KW_ID },
1.1 joris 60: { "Log", RCS_KW_LOG },
61: { "Name", RCS_KW_NAME },
62: { "RCSfile", RCS_KW_RCSFILE },
63: { "Revision", RCS_KW_REVISION },
64: { "Source", RCS_KW_SOURCE },
65: { "State", RCS_KW_STATE },
66: };
67:
68: int rcs_errno = RCS_ERR_NOERR;
69: char *timezone_flag = NULL;
70:
71: int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *);
1.9 xsa 72: static int rcs_movefile(char *, char *, mode_t, u_int);
1.68 tobias 73:
1.1 joris 74: static void rcs_freedelta(struct rcs_delta *);
75: static void rcs_strprint(const u_char *, size_t, FILE *);
76:
1.12 niallo 77: static BUF *rcs_expand_keywords(char *, struct rcs_delta *, BUF *, int);
1.1 joris 78:
79: RCSFILE *
1.3 joris 80: rcs_open(const char *path, int fd, int flags, ...)
1.1 joris 81: {
1.3 joris 82: int mode;
1.1 joris 83: mode_t fmode;
84: RCSFILE *rfp;
85: va_list vap;
86: struct rcs_delta *rdp;
87: struct rcs_lock *lkr;
88:
89: fmode = S_IRUSR|S_IRGRP|S_IROTH;
90: flags &= 0xffff; /* ditch any internal flags */
91:
1.3 joris 92: if (flags & RCS_CREATE) {
93: va_start(vap, flags);
94: mode = va_arg(vap, int);
95: va_end(vap);
96: fmode = (mode_t)mode;
1.1 joris 97: }
98:
99: rfp = xcalloc(1, sizeof(*rfp));
100:
101: rfp->rf_path = xstrdup(path);
102: rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
103: rfp->rf_mode = fmode;
1.69 tobias 104: if (fd == -1)
105: rfp->rf_file = NULL;
106: else if ((rfp->rf_file = fdopen(fd, "r")) == NULL)
107: err(1, "rcs_open: fdopen: `%s'", path);
1.1 joris 108:
109: TAILQ_INIT(&(rfp->rf_delta));
110: TAILQ_INIT(&(rfp->rf_access));
111: TAILQ_INIT(&(rfp->rf_symbols));
112: TAILQ_INIT(&(rfp->rf_locks));
113:
1.11 ray 114: if (!(rfp->rf_flags & RCS_CREATE)) {
1.68 tobias 115: if (rcsparse_init(rfp))
116: errx(1, "could not parse admin data");
1.1 joris 117:
1.11 ray 118: /* fill in rd_locker */
119: TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
120: if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
121: rcs_close(rfp);
122: return (NULL);
123: }
124:
125: rdp->rd_locker = xstrdup(lkr->rl_name);
1.1 joris 126: }
127: }
128:
129: return (rfp);
130: }
131:
132: /*
133: * rcs_close()
134: *
135: * Close an RCS file handle.
136: */
137: void
138: rcs_close(RCSFILE *rfp)
139: {
140: struct rcs_delta *rdp;
141: struct rcs_access *rap;
142: struct rcs_lock *rlp;
143: struct rcs_sym *rsp;
144:
145: if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
146: rcs_write(rfp);
147:
148: while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
149: rdp = TAILQ_FIRST(&(rfp->rf_delta));
150: TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
151: rcs_freedelta(rdp);
152: }
153:
154: while (!TAILQ_EMPTY(&(rfp->rf_access))) {
155: rap = TAILQ_FIRST(&(rfp->rf_access));
156: TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
157: xfree(rap->ra_name);
158: xfree(rap);
159: }
160:
161: while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
162: rsp = TAILQ_FIRST(&(rfp->rf_symbols));
163: TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
164: rcsnum_free(rsp->rs_num);
165: xfree(rsp->rs_name);
166: xfree(rsp);
167: }
168:
169: while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
170: rlp = TAILQ_FIRST(&(rfp->rf_locks));
171: TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
172: rcsnum_free(rlp->rl_num);
173: xfree(rlp->rl_name);
174: xfree(rlp);
175: }
176:
177: if (rfp->rf_head != NULL)
178: rcsnum_free(rfp->rf_head);
179: if (rfp->rf_branch != NULL)
180: rcsnum_free(rfp->rf_branch);
181:
1.69 tobias 182: if (rfp->rf_file != NULL)
183: fclose(rfp->rf_file);
1.1 joris 184: if (rfp->rf_path != NULL)
185: xfree(rfp->rf_path);
186: if (rfp->rf_comment != NULL)
187: xfree(rfp->rf_comment);
188: if (rfp->rf_expand != NULL)
189: xfree(rfp->rf_expand);
190: if (rfp->rf_desc != NULL)
191: xfree(rfp->rf_desc);
192: if (rfp->rf_pdata != NULL)
1.68 tobias 193: rcsparse_free(rfp);
1.1 joris 194: xfree(rfp);
195: }
196:
197: /*
198: * rcs_write()
199: *
200: * Write the contents of the RCS file handle <rfp> to disk in the file whose
201: * path is in <rf_path>.
202: */
1.9 xsa 203: void
1.1 joris 204: rcs_write(RCSFILE *rfp)
205: {
206: FILE *fp;
1.40 xsa 207: char numbuf[RCS_REV_BUFSZ], *fn;
1.1 joris 208: struct rcs_access *ap;
209: struct rcs_sym *symp;
210: struct rcs_branch *brp;
211: struct rcs_delta *rdp;
212: struct rcs_lock *lkp;
213: size_t len;
1.27 deraadt 214: int fd;
1.1 joris 215:
1.17 niallo 216: fn = NULL;
1.27 deraadt 217: fd = -1;
1.1 joris 218:
219: if (rfp->rf_flags & RCS_SYNCED)
1.9 xsa 220: return;
1.1 joris 221:
222: /* Write operations need the whole file parsed */
1.68 tobias 223: if (rcsparse_deltatexts(rfp, NULL))
224: errx(1, "problem parsing deltatexts");
1.1 joris 225:
1.7 xsa 226: (void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", rcs_tmpdir);
227:
1.1 joris 228: if ((fd = mkstemp(fn)) == -1)
1.2 xsa 229: err(1, "%s", fn);
1.1 joris 230:
231: if ((fp = fdopen(fd, "w+")) == NULL) {
1.5 ray 232: int saved_errno;
233:
234: saved_errno = errno;
1.2 xsa 235: (void)unlink(fn);
1.5 ray 236: errno = saved_errno;
1.2 xsa 237: err(1, "%s", fn);
1.1 joris 238: }
1.52 joris 239:
1.58 ray 240: worklist_add(fn, &temp_files);
1.1 joris 241:
242: if (rfp->rf_head != NULL)
243: rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
244: else
245: numbuf[0] = '\0';
246:
247: fprintf(fp, "head\t%s;\n", numbuf);
248:
249: if (rfp->rf_branch != NULL) {
250: rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
251: fprintf(fp, "branch\t%s;\n", numbuf);
252: }
253:
254: fputs("access", fp);
255: TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
256: fprintf(fp, "\n\t%s", ap->ra_name);
257: }
258: fputs(";\n", fp);
259:
260: fprintf(fp, "symbols");
261: TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
1.45 tobias 262: if (RCSNUM_ISBRANCH(symp->rs_num))
263: rcsnum_addmagic(symp->rs_num);
1.1 joris 264: rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
1.30 ray 265: fprintf(fp, "\n\t%s:%s", symp->rs_name, numbuf);
1.1 joris 266: }
267: fprintf(fp, ";\n");
268:
269: fprintf(fp, "locks");
270: TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
271: rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
272: fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
273: }
274:
275: fprintf(fp, ";");
276:
277: if (rfp->rf_flags & RCS_SLOCK)
278: fprintf(fp, " strict;");
279: fputc('\n', fp);
280:
281: fputs("comment\t@", fp);
282: if (rfp->rf_comment != NULL) {
283: rcs_strprint((const u_char *)rfp->rf_comment,
284: strlen(rfp->rf_comment), fp);
285: fputs("@;\n", fp);
286: } else
287: fputs("# @;\n", fp);
288:
289: if (rfp->rf_expand != NULL) {
290: fputs("expand @", fp);
291: rcs_strprint((const u_char *)rfp->rf_expand,
292: strlen(rfp->rf_expand), fp);
293: fputs("@;\n", fp);
294: }
295:
296: fputs("\n\n", fp);
297:
298: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
299: fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
300: sizeof(numbuf)));
301: fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
302: rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
303: rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
304: rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
305: fprintf(fp, "\tauthor %s;\tstate %s;\n",
306: rdp->rd_author, rdp->rd_state);
307: fputs("branches", fp);
308: TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
1.44 tobias 309: fprintf(fp, "\n\t%s", rcsnum_tostr(brp->rb_num, numbuf,
1.1 joris 310: sizeof(numbuf)));
311: }
312: fputs(";\n", fp);
313: fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
314: numbuf, sizeof(numbuf)));
315: }
316:
317: fputs("\ndesc\n@", fp);
318: if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
319: rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
320: if (rfp->rf_desc[len-1] != '\n')
321: fputc('\n', fp);
322: }
323: fputs("@\n", fp);
324:
325: /* deltatexts */
326: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
327: fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
328: sizeof(numbuf)));
329: fputs("log\n@", fp);
330: if (rdp->rd_log != NULL) {
331: len = strlen(rdp->rd_log);
332: rcs_strprint((const u_char *)rdp->rd_log, len, fp);
1.56 nicm 333: if (len == 0 || rdp->rd_log[len-1] != '\n')
1.1 joris 334: fputc('\n', fp);
335: }
336: fputs("@\ntext\n@", fp);
1.35 niallo 337: if (rdp->rd_text != NULL)
1.1 joris 338: rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
339: fputs("@\n", fp);
340: }
1.9 xsa 341: (void)fclose(fp);
1.1 joris 342:
1.9 xsa 343: if (rcs_movefile(fn, rfp->rf_path, rfp->rf_mode, rfp->rf_flags) == -1) {
344: (void)unlink(fn);
345: errx(1, "rcs_movefile failed");
346: }
1.1 joris 347:
1.9 xsa 348: rfp->rf_flags |= RCS_SYNCED;
1.1 joris 349:
1.9 xsa 350: if (fn != NULL)
351: xfree(fn);
352: }
1.1 joris 353:
1.9 xsa 354: /*
355: * rcs_movefile()
356: *
357: * Move a file using rename(2) if possible and copying if not.
358: * Returns 0 on success, -1 on failure.
359: */
360: static int
361: rcs_movefile(char *from, char *to, mode_t perm, u_int to_flags)
362: {
363: FILE *src, *dst;
364: size_t nread, nwritten;
365: char *buf;
366: int ret;
1.1 joris 367:
1.9 xsa 368: ret = -1;
1.1 joris 369:
1.9 xsa 370: if (rename(from, to) == 0) {
371: if (chmod(to, perm) == -1) {
372: warn("%s", to);
373: return (-1);
1.1 joris 374: }
1.9 xsa 375: return (0);
376: } else if (errno != EXDEV) {
377: warn("failed to access temp RCS output file");
378: return (-1);
1.1 joris 379: }
380:
1.9 xsa 381: if ((chmod(to, S_IWUSR) == -1) && !(to_flags & RCS_CREATE)) {
382: warnx("chmod(%s, 0%o) failed", to, S_IWUSR);
383: return (-1);
1.1 joris 384: }
385:
1.9 xsa 386: /* different filesystem, have to copy the file */
387: if ((src = fopen(from, "r")) == NULL) {
388: warn("%s", from);
389: return (-1);
390: }
391: if ((dst = fopen(to, "w")) == NULL) {
392: warn("%s", to);
1.72 jasper 393: (void)fclose(src);
1.9 xsa 394: return (-1);
395: }
396: if (fchmod(fileno(dst), perm)) {
397: warn("%s", to);
398: (void)unlink(to);
1.72 jasper 399: (void)fclose(src);
400: (void)fclose(dst);
1.9 xsa 401: return (-1);
402: }
403:
404: buf = xmalloc(MAXBSIZE);
405: while ((nread = fread(buf, sizeof(char), MAXBSIZE, src)) != 0) {
406: if (ferror(src)) {
407: warnx("failed to read `%s'", from);
408: (void)unlink(to);
409: goto out;
410: }
411: nwritten = fwrite(buf, sizeof(char), nread, dst);
412: if (nwritten != nread) {
413: warnx("failed to write `%s'", to);
414: (void)unlink(to);
415: goto out;
416: }
417: }
1.1 joris 418:
1.7 xsa 419: ret = 0;
1.9 xsa 420:
421: (void)unlink(from);
422:
1.7 xsa 423: out:
1.72 jasper 424: (void)fclose(src);
425: (void)fclose(dst);
1.9 xsa 426: xfree(buf);
1.7 xsa 427:
428: return (ret);
1.1 joris 429: }
430:
431: /*
432: * rcs_head_set()
433: *
434: * Set the revision number of the head revision for the RCS file <file> to
435: * <rev>, which must reference a valid revision within the file.
436: */
437: int
438: rcs_head_set(RCSFILE *file, RCSNUM *rev)
439: {
440: if (rcs_findrev(file, rev) == NULL)
441: return (-1);
442:
443: if (file->rf_head == NULL)
444: file->rf_head = rcsnum_alloc();
445:
446: rcsnum_cpy(rev, file->rf_head, 0);
447: file->rf_flags &= ~RCS_SYNCED;
448: return (0);
449: }
450:
451:
452: /*
453: * rcs_branch_get()
454: *
455: * Retrieve the default branch number for the RCS file <file>.
456: * Returns the number on success. If NULL is returned, then there is no
457: * default branch for this file.
458: */
459: const RCSNUM *
460: rcs_branch_get(RCSFILE *file)
461: {
462: return (file->rf_branch);
463: }
464:
465: /*
466: * rcs_access_add()
467: *
468: * Add the login name <login> to the access list for the RCS file <file>.
469: * Returns 0 on success, or -1 on failure.
470: */
471: int
472: rcs_access_add(RCSFILE *file, const char *login)
473: {
474: struct rcs_access *ap;
475:
476: /* first look for duplication */
477: TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
478: if (strcmp(ap->ra_name, login) == 0) {
479: rcs_errno = RCS_ERR_DUPENT;
480: return (-1);
481: }
482: }
483:
484: ap = xmalloc(sizeof(*ap));
485: ap->ra_name = xstrdup(login);
486: TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
487:
488: /* not synced anymore */
489: file->rf_flags &= ~RCS_SYNCED;
490: return (0);
491: }
492:
493: /*
494: * rcs_access_remove()
495: *
496: * Remove an entry with login name <login> from the access list of the RCS
497: * file <file>.
498: * Returns 0 on success, or -1 on failure.
499: */
500: int
501: rcs_access_remove(RCSFILE *file, const char *login)
502: {
503: struct rcs_access *ap;
504:
505: TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
506: if (strcmp(ap->ra_name, login) == 0)
507: break;
508:
509: if (ap == NULL) {
510: rcs_errno = RCS_ERR_NOENT;
511: return (-1);
512: }
513:
514: TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
515: xfree(ap->ra_name);
516: xfree(ap);
517:
518: /* not synced anymore */
519: file->rf_flags &= ~RCS_SYNCED;
520: return (0);
521: }
522:
523: /*
524: * rcs_sym_add()
525: *
526: * Add a symbol to the list of symbols for the RCS file <rfp>. The new symbol
527: * is named <sym> and is bound to the RCS revision <snum>.
528: * Returns 0 on success, or -1 on failure.
529: */
530: int
531: rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
532: {
533: struct rcs_sym *symp;
534:
535: if (!rcs_sym_check(sym)) {
536: rcs_errno = RCS_ERR_BADSYM;
537: return (-1);
538: }
539:
540: /* first look for duplication */
541: TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
542: if (strcmp(symp->rs_name, sym) == 0) {
543: rcs_errno = RCS_ERR_DUPENT;
544: return (-1);
545: }
546: }
547:
548: symp = xmalloc(sizeof(*symp));
549: symp->rs_name = xstrdup(sym);
550: symp->rs_num = rcsnum_alloc();
551: rcsnum_cpy(snum, symp->rs_num, 0);
552:
553: TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
554:
555: /* not synced anymore */
556: rfp->rf_flags &= ~RCS_SYNCED;
557: return (0);
558: }
559:
560: /*
561: * rcs_sym_remove()
562: *
563: * Remove the symbol with name <sym> from the symbol list for the RCS file
564: * <file>. If no such symbol is found, the call fails and returns with an
565: * error.
566: * Returns 0 on success, or -1 on failure.
567: */
568: int
569: rcs_sym_remove(RCSFILE *file, const char *sym)
570: {
571: struct rcs_sym *symp;
572:
573: if (!rcs_sym_check(sym)) {
574: rcs_errno = RCS_ERR_BADSYM;
575: return (-1);
576: }
577:
578: TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
579: if (strcmp(symp->rs_name, sym) == 0)
580: break;
581:
582: if (symp == NULL) {
583: rcs_errno = RCS_ERR_NOENT;
584: return (-1);
585: }
586:
587: TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
588: xfree(symp->rs_name);
589: rcsnum_free(symp->rs_num);
590: xfree(symp);
591:
592: /* not synced anymore */
593: file->rf_flags &= ~RCS_SYNCED;
594: return (0);
595: }
596:
597: /*
598: * rcs_sym_getrev()
599: *
600: * Retrieve the RCS revision number associated with the symbol <sym> for the
601: * RCS file <file>. The returned value is a dynamically-allocated copy and
602: * should be freed by the caller once they are done with it.
603: * Returns the RCSNUM on success, or NULL on failure.
604: */
605: RCSNUM *
606: rcs_sym_getrev(RCSFILE *file, const char *sym)
607: {
608: RCSNUM *num;
609: struct rcs_sym *symp;
610:
611: if (!rcs_sym_check(sym)) {
612: rcs_errno = RCS_ERR_BADSYM;
613: return (NULL);
614: }
615:
616: num = NULL;
617: TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
618: if (strcmp(symp->rs_name, sym) == 0)
619: break;
620:
621: if (symp == NULL) {
622: rcs_errno = RCS_ERR_NOENT;
623: } else {
624: num = rcsnum_alloc();
625: rcsnum_cpy(symp->rs_num, num, 0);
626: }
627:
628: return (num);
629: }
630:
631: /*
632: * rcs_sym_check()
633: *
634: * Check the RCS symbol name <sym> for any unsupported characters.
635: * Returns 1 if the tag is correct, 0 if it isn't valid.
636: */
637: int
638: rcs_sym_check(const char *sym)
639: {
640: int ret;
641: const char *cp;
642:
643: ret = 1;
644: cp = sym;
645: if (!isalpha(*cp++))
646: return (0);
647:
648: for (; *cp != '\0'; cp++)
649: if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
650: ret = 0;
651: break;
652: }
653:
654: return (ret);
655: }
656:
657: /*
658: * rcs_lock_getmode()
659: *
660: * Retrieve the locking mode of the RCS file <file>.
661: */
662: int
663: rcs_lock_getmode(RCSFILE *file)
664: {
665: return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
666: }
667:
668: /*
669: * rcs_lock_setmode()
670: *
671: * Set the locking mode of the RCS file <file> to <mode>, which must either
672: * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
673: * Returns the previous mode on success, or -1 on failure.
674: */
675: int
676: rcs_lock_setmode(RCSFILE *file, int mode)
677: {
678: int pmode;
679: pmode = rcs_lock_getmode(file);
680:
681: if (mode == RCS_LOCK_STRICT)
682: file->rf_flags |= RCS_SLOCK;
683: else if (mode == RCS_LOCK_LOOSE)
684: file->rf_flags &= ~RCS_SLOCK;
685: else
686: errx(1, "rcs_lock_setmode: invalid mode `%d'", mode);
687:
688: file->rf_flags &= ~RCS_SYNCED;
689: return (pmode);
690: }
691:
692: /*
693: * rcs_lock_add()
694: *
695: * Add an RCS lock for the user <user> on revision <rev>.
696: * Returns 0 on success, or -1 on failure.
697: */
698: int
699: rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
700: {
701: struct rcs_lock *lkp;
702:
703: /* first look for duplication */
704: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
705: if (strcmp(lkp->rl_name, user) == 0 &&
706: rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
707: rcs_errno = RCS_ERR_DUPENT;
708: return (-1);
709: }
710: }
711:
712: lkp = xmalloc(sizeof(*lkp));
713: lkp->rl_name = xstrdup(user);
714: lkp->rl_num = rcsnum_alloc();
715: rcsnum_cpy(rev, lkp->rl_num, 0);
716:
717: TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
718:
719: /* not synced anymore */
720: file->rf_flags &= ~RCS_SYNCED;
721: return (0);
722: }
723:
724:
725: /*
726: * rcs_lock_remove()
727: *
728: * Remove the RCS lock on revision <rev>.
729: * Returns 0 on success, or -1 on failure.
730: */
731: int
732: rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
733: {
734: struct rcs_lock *lkp;
735:
736: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
737: if (strcmp(lkp->rl_name, user) == 0 &&
738: rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
739: break;
740: }
741:
742: if (lkp == NULL) {
743: rcs_errno = RCS_ERR_NOENT;
744: return (-1);
745: }
746:
747: TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
748: rcsnum_free(lkp->rl_num);
749: xfree(lkp->rl_name);
750: xfree(lkp);
751:
752: /* not synced anymore */
753: file->rf_flags &= ~RCS_SYNCED;
754: return (0);
755: }
756:
757: /*
758: * rcs_desc_set()
759: *
760: * Set the description for the RCS file <file>.
761: */
762: void
763: rcs_desc_set(RCSFILE *file, const char *desc)
764: {
765: char *tmp;
766:
767: tmp = xstrdup(desc);
768: if (file->rf_desc != NULL)
769: xfree(file->rf_desc);
770: file->rf_desc = tmp;
771: file->rf_flags &= ~RCS_SYNCED;
772: }
773:
774: /*
775: * rcs_comment_set()
776: *
777: * Set the comment leader for the RCS file <file>.
778: */
779: void
780: rcs_comment_set(RCSFILE *file, const char *comment)
781: {
782: char *tmp;
783:
784: tmp = xstrdup(comment);
785: if (file->rf_comment != NULL)
786: xfree(file->rf_comment);
787: file->rf_comment = tmp;
788: file->rf_flags &= ~RCS_SYNCED;
789: }
790:
791: int
792: rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
793: {
794: char op, *ep;
795: struct rcs_line *lp, *dlp, *ndlp;
796: int i, lineno, nbln;
1.35 niallo 797: u_char tmp;
1.1 joris 798:
799: dlp = TAILQ_FIRST(&(dlines->l_lines));
800: lp = TAILQ_FIRST(&(plines->l_lines));
801:
802: /* skip first bogus line */
803: for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
804: lp = TAILQ_NEXT(lp, l_list)) {
1.35 niallo 805: if (lp->l_len < 2)
806: errx(1, "line too short, RCS patch seems broken");
1.1 joris 807: op = *(lp->l_line);
1.35 niallo 808: /* NUL-terminate line buffer for strtol() safety. */
809: tmp = lp->l_line[lp->l_len - 1];
810: lp->l_line[lp->l_len - 1] = '\0';
1.1 joris 811: lineno = (int)strtol((lp->l_line + 1), &ep, 10);
812: if (lineno > dlines->l_nblines || lineno < 0 ||
813: *ep != ' ')
814: errx(1, "invalid line specification in RCS patch");
815: ep++;
816: nbln = (int)strtol(ep, &ep, 10);
1.35 niallo 817: /* Restore the last byte of the buffer */
818: lp->l_line[lp->l_len - 1] = tmp;
819: if (nbln < 0)
1.1 joris 820: errx(1,
821: "invalid line number specification in RCS patch");
822:
823: /* find the appropriate line */
824: for (;;) {
825: if (dlp == NULL)
826: break;
827: if (dlp->l_lineno == lineno)
828: break;
829: if (dlp->l_lineno > lineno) {
1.58 ray 830: dlp = TAILQ_PREV(dlp, tqh, l_list);
1.1 joris 831: } else if (dlp->l_lineno < lineno) {
832: if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
833: ndlp->l_lineno > lineno)
834: break;
835: dlp = ndlp;
836: }
837: }
838: if (dlp == NULL)
839: errx(1, "can't find referenced line in RCS patch");
840:
841: if (op == 'd') {
842: for (i = 0; (i < nbln) && (dlp != NULL); i++) {
843: ndlp = TAILQ_NEXT(dlp, l_list);
844: TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
1.21 niallo 845: xfree(dlp);
1.1 joris 846: dlp = ndlp;
847: /* last line is gone - reset dlp */
848: if (dlp == NULL) {
849: ndlp = TAILQ_LAST(&(dlines->l_lines),
1.58 ray 850: tqh);
1.1 joris 851: dlp = ndlp;
852: }
853: }
854: } else if (op == 'a') {
855: for (i = 0; i < nbln; i++) {
856: ndlp = lp;
857: lp = TAILQ_NEXT(lp, l_list);
858: if (lp == NULL)
859: errx(1, "truncated RCS patch");
860: TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
861: TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
862: lp, l_list);
863: dlp = lp;
864:
865: /* we don't want lookup to block on those */
866: lp->l_lineno = lineno;
867:
868: lp = ndlp;
869: }
870: } else
871: errx(1, "unknown RCS patch operation `%c'", op);
872:
873: /* last line of the patch, done */
874: if (lp->l_lineno == plines->l_nblines)
875: break;
876: }
877:
878: /* once we're done patching, rebuild the line numbers */
879: lineno = 0;
880: TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
881: lp->l_lineno = lineno++;
882: dlines->l_nblines = lineno - 1;
883:
884: return (0);
885: }
886:
887: /*
888: * rcs_getrev()
889: *
890: * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The
891: * returned buffer is dynamically allocated and should be released using
1.59 ray 892: * buf_free() once the caller is done using it.
1.1 joris 893: */
1.61 tobias 894: BUF *
1.1 joris 895: rcs_getrev(RCSFILE *rfp, RCSNUM *frev)
896: {
897: u_int i, numlen;
1.6 niallo 898: int isbranch, lookonbranch, found;
1.35 niallo 899: size_t dlen, plen, len;
1.1 joris 900: RCSNUM *crev, *rev, *brev;
1.35 niallo 901: BUF *rbuf;
1.1 joris 902: struct rcs_delta *rdp = NULL;
903: struct rcs_branch *rb;
1.35 niallo 904: u_char *data, *patch;
1.1 joris 905:
906: if (rfp->rf_head == NULL)
907: return (NULL);
908:
909: if (frev == RCS_HEAD_REV)
910: rev = rfp->rf_head;
911: else
912: rev = frev;
913:
914: /* XXX rcsnum_cmp() */
915: for (i = 0; i < rfp->rf_head->rn_len; i++) {
916: if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) {
917: rcs_errno = RCS_ERR_NOENT;
918: return (NULL);
919: }
920: }
921:
1.54 jj 922: /* No matter what, we'll need everything parsed up until the description
923: so go for it. */
1.68 tobias 924: if (rcsparse_deltas(rfp, NULL))
925: return (NULL);
1.1 joris 926:
927: rdp = rcs_findrev(rfp, rfp->rf_head);
928: if (rdp == NULL) {
929: warnx("failed to get RCS HEAD revision");
930: return (NULL);
931: }
932:
933: if (rdp->rd_tlen == 0)
1.68 tobias 934: if (rcsparse_deltatexts(rfp, rfp->rf_head))
935: return (NULL);
1.1 joris 936:
937: len = rdp->rd_tlen;
938: if (len == 0) {
1.60 ray 939: rbuf = buf_alloc(1);
1.59 ray 940: buf_empty(rbuf);
1.1 joris 941: return (rbuf);
942: }
943:
1.60 ray 944: rbuf = buf_alloc(len);
1.59 ray 945: buf_append(rbuf, rdp->rd_text, len);
1.1 joris 946:
947: isbranch = 0;
948: brev = NULL;
949:
950: /*
951: * If a branch was passed, get the latest revision on it.
952: */
953: if (RCSNUM_ISBRANCH(rev)) {
954: brev = rev;
955: rdp = rcs_findrev(rfp, rev);
1.64 nicm 956: if (rdp == NULL) {
957: buf_free(rbuf);
1.1 joris 958: return (NULL);
1.64 nicm 959: }
1.1 joris 960:
961: rev = rdp->rd_num;
962: } else {
963: if (RCSNUM_ISBRANCHREV(rev)) {
964: brev = rcsnum_revtobr(rev);
965: isbranch = 1;
966: }
967: }
968:
969: lookonbranch = 0;
970: crev = NULL;
971:
972: /* Apply patches backwards to get the right version.
973: */
974: do {
1.6 niallo 975: found = 0;
1.26 deraadt 976:
1.1 joris 977: if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0)
978: break;
979:
980: if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len &&
981: !TAILQ_EMPTY(&(rdp->rd_branches)))
982: lookonbranch = 1;
983:
984: if (isbranch && lookonbranch == 1) {
985: lookonbranch = 0;
986: TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
987: /* XXX rcsnum_cmp() is totally broken for
988: * this purpose.
989: */
1.50 tobias 990: numlen = MIN(brev->rn_len,
991: rb->rb_num->rn_len - 1);
1.1 joris 992: for (i = 0; i < numlen; i++) {
993: if (rb->rb_num->rn_id[i] !=
994: brev->rn_id[i])
995: break;
996: }
997:
998: if (i == numlen) {
999: crev = rb->rb_num;
1.6 niallo 1000: found = 1;
1.1 joris 1001: break;
1002: }
1003: }
1.6 niallo 1004: if (found == 0)
1005: crev = rdp->rd_next;
1.1 joris 1006: } else {
1007: crev = rdp->rd_next;
1008: }
1009:
1010: rdp = rcs_findrev(rfp, crev);
1011: if (rdp == NULL) {
1.59 ray 1012: buf_free(rbuf);
1.1 joris 1013: return (NULL);
1014: }
1015:
1.35 niallo 1016: plen = rdp->rd_tlen;
1.59 ray 1017: dlen = buf_len(rbuf);
1.35 niallo 1018: patch = rdp->rd_text;
1.59 ray 1019: data = buf_release(rbuf);
1.1 joris 1020: /* check if we have parsed this rev's deltatext */
1021: if (rdp->rd_tlen == 0)
1.68 tobias 1022: if (rcsparse_deltatexts(rfp, rdp->rd_num))
1023: return (NULL);
1.1 joris 1024:
1.35 niallo 1025: rbuf = rcs_patchfile(data, dlen, patch, plen, rcs_patch_lines);
1.64 nicm 1026: xfree(data);
1.1 joris 1027:
1028: if (rbuf == NULL)
1029: break;
1030: } while (rcsnum_cmp(crev, rev, 0) != 0);
1031:
1032: return (rbuf);
1033: }
1.46 xsa 1034:
1035: void
1036: rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved)
1037: {
1038: struct rcs_lines *plines;
1039: struct rcs_line *lp;
1.73 jasper 1040: int added, i, nbln, removed;
1.46 xsa 1041: char op, *ep;
1042: u_char tmp;
1043:
1044: added = removed = 0;
1045:
1046: plines = rcs_splitlines(rdp->rd_text, rdp->rd_tlen);
1047: lp = TAILQ_FIRST(&(plines->l_lines));
1048:
1049: /* skip first bogus line */
1050: for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
1051: lp = TAILQ_NEXT(lp, l_list)) {
1052: if (lp->l_len < 2)
1053: errx(1,
1054: "line too short, RCS patch seems broken");
1055: op = *(lp->l_line);
1056: /* NUL-terminate line buffer for strtol() safety. */
1057: tmp = lp->l_line[lp->l_len - 1];
1058: lp->l_line[lp->l_len - 1] = '\0';
1.74 djm 1059: (void)strtol((lp->l_line + 1), &ep, 10);
1.46 xsa 1060: ep++;
1061: nbln = (int)strtol(ep, &ep, 10);
1062: /* Restore the last byte of the buffer */
1063: lp->l_line[lp->l_len - 1] = tmp;
1064: if (nbln < 0)
1065: errx(1, "invalid line number specification "
1066: "in RCS patch");
1067:
1068: if (op == 'a') {
1069: added += nbln;
1070: for (i = 0; i < nbln; i++) {
1071: lp = TAILQ_NEXT(lp, l_list);
1072: if (lp == NULL)
1073: errx(1, "truncated RCS patch");
1074: }
1075: } else if (op == 'd')
1076: removed += nbln;
1077: else
1078: errx(1, "unknown RCS patch operation '%c'", op);
1079: }
1.47 tobias 1080:
1081: rcs_freelines(plines);
1.46 xsa 1082:
1083: *ladded = added;
1084: *lremoved = removed;
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 *
1.77 nicm 1482: rcs_expand_keywords(char *rcsfile_in, struct rcs_delta *rdp, BUF *bp, int mode)
1.1 joris 1483: {
1.28 ray 1484: BUF *newbuf;
1.77 nicm 1485: u_char *c, *kw, *fin;
1486: char buf[256], *tmpf, resolved[MAXPATHLEN], *rcsfile;
1487: u_char *line, *line2;
1488: u_int i, j;
1.1 joris 1489: int kwtype;
1.77 nicm 1490: int found;
1.1 joris 1491: struct tm tb;
1492:
1493: tb = rdp->rd_date;
1494: if (timezone_flag != NULL)
1495: rcs_set_tz(timezone_flag, rdp, &tb);
1496:
1.77 nicm 1497: if (realpath(rcsfile_in, resolved) == NULL)
1498: rcsfile = rcsfile_in;
1499: else
1500: rcsfile = resolved;
1.12 niallo 1501:
1.77 nicm 1502: newbuf = buf_alloc(buf_len(bp));
1.18 niallo 1503:
1.1 joris 1504: /*
1505: * Keyword formats:
1506: * $Keyword$
1507: * $Keyword: value$
1508: */
1.77 nicm 1509: c = buf_get(bp);
1510: fin = c + buf_len(bp);
1511: /* Copying to newbuf is deferred until the first keyword. */
1512: found = 0;
1513:
1514: while (c < fin) {
1515: kw = memchr(c, '$', fin - c);
1516: if (kw == NULL)
1517: break;
1518: ++kw;
1519: if (found) {
1520: /* Copy everything up to and including the $. */
1521: buf_append(newbuf, c, kw - c);
1522: }
1523: c = kw;
1524: /* c points after the $ now. */
1525: if (c == fin)
1526: break;
1527: if (!isalpha(*c)) /* all valid keywords start with a letter */
1528: continue;
1.1 joris 1529:
1.77 nicm 1530: for (i = 0; i < RCS_NKWORDS; ++i) {
1531: size_t kwlen;
1.28 ray 1532:
1.77 nicm 1533: kwlen = strlen(rcs_expkw[i].kw_str);
1534: /*
1535: * kwlen must be less than clen since clen includes
1536: * either a terminating `$' or a `:'.
1537: */
1538: if (c + kwlen < fin &&
1539: memcmp(c , rcs_expkw[i].kw_str, kwlen) == 0 &&
1540: (c[kwlen] == '$' || c[kwlen] == ':')) {
1541: c += kwlen;
1542: break;
1543: }
1544: }
1545: if (i == RCS_NKWORDS)
1546: continue;
1547: kwtype = rcs_expkw[i].kw_type;
1548:
1549: /*
1550: * If the next character is ':' we need to look for an '$'
1551: * before the end of the line to be sure it is in fact a
1552: * keyword.
1553: */
1554: if (*c == ':') {
1555: for (; c < fin; ++c) {
1556: if (*c == '$' || *c == '\n')
1.1 joris 1557: break;
1558: }
1559:
1.77 nicm 1560: if (*c != '$') {
1561: if (found)
1562: buf_append(newbuf, kw, c - kw);
1.1 joris 1563: continue;
1564: }
1.77 nicm 1565: }
1566: ++c;
1.1 joris 1567:
1.77 nicm 1568: if (!found) {
1569: found = 1;
1570: /* Copy everything up to and including the $. */
1571: buf_append(newbuf, buf_get(bp), kw - buf_get(bp));
1572: }
1573:
1574: if (mode & RCS_KWEXP_NAME) {
1575: buf_puts(newbuf, rcs_expkw[i].kw_str);
1576: if (mode & RCS_KWEXP_VAL)
1577: buf_puts(newbuf, ": ");
1578: }
1.1 joris 1579:
1.77 nicm 1580: /* Order matters because of RCS_KW_ID and RCS_KW_HEADER. */
1581: if (mode & RCS_KWEXP_VAL) {
1582: if (kwtype & (RCS_KW_RCSFILE|RCS_KW_LOG)) {
1583: if ((kwtype & RCS_KW_FULLPATH) ||
1584: (tmpf = strrchr(rcsfile, '/')) == NULL)
1585: buf_puts(newbuf, rcsfile);
1586: else
1587: buf_puts(newbuf, tmpf + 1);
1588: buf_putc(newbuf, ' ');
1.1 joris 1589: }
1590:
1.77 nicm 1591: if (kwtype & RCS_KW_REVISION) {
1592: rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1593: buf_puts(newbuf, buf);
1594: buf_putc(newbuf, ' ');
1595: }
1.1 joris 1596:
1.77 nicm 1597: if (kwtype & RCS_KW_DATE) {
1598: strftime(buf, sizeof(buf),
1599: "%Y/%m/%d %H:%M:%S ", &tb);
1600: buf_puts(newbuf, buf);
1601: }
1.31 ray 1602:
1.77 nicm 1603: if (kwtype & RCS_KW_AUTHOR) {
1604: buf_puts(newbuf, rdp->rd_author);
1605: buf_putc(newbuf, ' ');
1.1 joris 1606: }
1607:
1.77 nicm 1608: if (kwtype & RCS_KW_STATE) {
1609: buf_puts(newbuf, rdp->rd_state);
1610: buf_putc(newbuf, ' ');
1611: }
1.1 joris 1612:
1.77 nicm 1613: /* Order does not matter anymore below. */
1614: if (kwtype & RCS_KW_SOURCE) {
1615: buf_puts(newbuf, rcsfile);
1616: buf_putc(newbuf, ' ');
1617: }
1.31 ray 1618:
1.77 nicm 1619: if (kwtype & RCS_KW_NAME)
1620: buf_putc(newbuf, ' ');
1.1 joris 1621:
1.77 nicm 1622: if ((kwtype & RCS_KW_LOCKER)) {
1623: if (rdp->rd_locker) {
1624: buf_puts(newbuf, rdp->rd_locker);
1625: buf_putc(newbuf, ' ');
1.1 joris 1626: }
1.77 nicm 1627: }
1628: }
1.1 joris 1629:
1.77 nicm 1630: /* End the expansion. */
1631: if (mode & RCS_KWEXP_NAME)
1632: buf_putc(newbuf, '$');
1.31 ray 1633:
1.77 nicm 1634: if (kwtype & RCS_KW_LOG) {
1635: line = memrchr(buf_get(bp), '\n', kw - buf_get(bp) - 1);
1636: if (line == NULL)
1637: line = buf_get(bp);
1638: else
1639: ++line;
1640: line2 = kw - 1;
1641: while (line2 > line && line2[-1] == ' ')
1642: --line2;
1643:
1644: buf_putc(newbuf, '\n');
1645: buf_append(newbuf, line, kw - 1 - line);
1646: buf_puts(newbuf, "Revision ");
1647: rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1648: buf_puts(newbuf, buf);
1649: buf_puts(newbuf, " ");
1650: strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &tb);
1651: buf_puts(newbuf, buf);
1652:
1653: buf_puts(newbuf, " ");
1654: buf_puts(newbuf, rdp->rd_author);
1655: buf_putc(newbuf, '\n');
1656:
1657: for (i = 0; rdp->rd_log[i]; i += j) {
1658: j = strcspn(rdp->rd_log + i, "\n");
1659: if (j == 0)
1660: buf_append(newbuf, line, line2 - line);
1661: else
1662: buf_append(newbuf, line, kw - 1 - line);
1663: if (rdp->rd_log[i + j])
1664: ++j;
1665: buf_append(newbuf, rdp->rd_log + i, j);
1666: }
1667: buf_append(newbuf, line, line2 - line);
1668: for (j = 0; c + j < fin; ++j) {
1669: if (c[j] != ' ')
1670: break;
1.1 joris 1671: }
1.77 nicm 1672: if (c + j == fin || c[j] == '\n')
1673: c += j;
1.1 joris 1674: }
1675: }
1.18 niallo 1676:
1.77 nicm 1677: if (found) {
1678: buf_append(newbuf, c, fin - c);
1679: buf_free(bp);
1680: return (newbuf);
1681: } else {
1682: buf_free(newbuf);
1683: return (bp);
1684: }
1.1 joris 1685: }
1686:
1687: /*
1688: * rcs_deltatext_set()
1689: *
1690: * Set deltatext for <rev> in RCS file <rfp> to <dtext>
1691: * Returns -1 on error, 0 on success.
1692: */
1693: int
1.12 niallo 1694: rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
1.1 joris 1695: {
1696: size_t len;
1.12 niallo 1697: u_char *dtext;
1.1 joris 1698: struct rcs_delta *rdp;
1699:
1700: /* Write operations require full parsing */
1.68 tobias 1701: if (rcsparse_deltatexts(rfp, NULL))
1702: return (-1);
1.1 joris 1703:
1704: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1705: return (-1);
1706:
1707: if (rdp->rd_text != NULL)
1708: xfree(rdp->rd_text);
1709:
1.59 ray 1710: len = buf_len(bp);
1711: dtext = buf_release(bp);
1.12 niallo 1712: bp = NULL;
1.59 ray 1713:
1.1 joris 1714: if (len != 0) {
1.12 niallo 1715: rdp->rd_text = xmalloc(len);
1.1 joris 1716: rdp->rd_tlen = len;
1.12 niallo 1717: (void)memcpy(rdp->rd_text, dtext, len);
1.1 joris 1718: } else {
1719: rdp->rd_text = NULL;
1720: rdp->rd_tlen = 0;
1721: }
1.70 tobias 1722:
1723: if (dtext != NULL)
1724: xfree(dtext);
1.1 joris 1725:
1726: return (0);
1727: }
1728:
1729: /*
1730: * rcs_rev_setlog()
1731: *
1.51 tobias 1732: * Sets the log message of revision <rev> to <logtext>.
1.1 joris 1733: */
1734: int
1735: rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
1736: {
1737: struct rcs_delta *rdp;
1738:
1739: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1740: return (-1);
1741:
1742: if (rdp->rd_log != NULL)
1743: xfree(rdp->rd_log);
1744:
1745: rdp->rd_log = xstrdup(logtext);
1746: rfp->rf_flags &= ~RCS_SYNCED;
1747: return (0);
1748: }
1749: /*
1750: * rcs_rev_getdate()
1751: *
1752: * Get the date corresponding to a given revision.
1753: * Returns the date on success, -1 on failure.
1754: */
1755: time_t
1756: rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
1757: {
1758: struct rcs_delta *rdp;
1759:
1760: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1761: return (-1);
1762:
1763: return (mktime(&rdp->rd_date));
1764: }
1765:
1766: /*
1767: * rcs_state_set()
1768: *
1769: * Sets the state of revision <rev> to <state>
1770: * NOTE: default state is 'Exp'. States may not contain spaces.
1771: *
1772: * Returns -1 on failure, 0 on success.
1773: */
1774: int
1775: rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
1776: {
1777: struct rcs_delta *rdp;
1778:
1779: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1780: return (-1);
1781:
1782: if (rdp->rd_state != NULL)
1783: xfree(rdp->rd_state);
1784:
1785: rdp->rd_state = xstrdup(state);
1786:
1787: rfp->rf_flags &= ~RCS_SYNCED;
1788:
1789: return (0);
1790: }
1791:
1792: /*
1793: * rcs_state_check()
1794: *
1795: * Check if string <state> is valid.
1796: *
1797: * Returns 0 if the string is valid, -1 otherwise.
1798: */
1799: int
1800: rcs_state_check(const char *state)
1801: {
1.66 tobias 1802: int ret;
1803: const char *cp;
1804:
1805: ret = 0;
1806: cp = state;
1807: if (!isalpha(*cp++))
1.1 joris 1808: return (-1);
1809:
1.66 tobias 1810: for (; *cp != '\0'; cp++)
1811: if (!isgraph(*cp) || (strchr(rcs_state_invch, *cp) != NULL)) {
1812: ret = -1;
1813: break;
1814: }
1815:
1816: return (ret);
1.1 joris 1817: }
1818:
1819: /*
1820: * rcs_kwexp_buf()
1821: *
1822: * Do keyword expansion on a buffer if necessary
1823: *
1824: */
1825: BUF *
1826: rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
1827: {
1828: struct rcs_delta *rdp;
1829: int expmode;
1830:
1831: /*
1832: * Do keyword expansion if required.
1833: */
1.71 tobias 1834: expmode = rcs_kwexp_get(rf);
1.1 joris 1835:
1836: if (!(expmode & RCS_KWEXP_NONE)) {
1837: if ((rdp = rcs_findrev(rf, rev)) == NULL)
1.5 ray 1838: errx(1, "could not fetch revision");
1.12 niallo 1839: return (rcs_expand_keywords(rf->rf_path, rdp, bp, expmode));
1.1 joris 1840: }
1841: return (bp);
1842: }