Annotation of src/usr.bin/rcs/rcs.c, Revision 1.77
1.77 ! nicm 1: /* $OpenBSD: rcs.c,v 1.76 2011/05/20 19:21:10 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: }
1086:
1.1 joris 1087:
1088: /*
1089: * rcs_rev_add()
1090: *
1091: * Add a revision to the RCS file <rf>. The new revision's number can be
1092: * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
1093: * new revision will have a number equal to the previous head revision plus
1094: * one). The <msg> argument specifies the log message for that revision, and
1095: * <date> specifies the revision's date (a value of -1 is
1096: * equivalent to using the current time).
1.57 ray 1097: * If <author> is NULL, set the author for this revision to the current user.
1.1 joris 1098: * Returns 0 on success, or -1 on failure.
1099: */
1100: int
1101: rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
1.57 ray 1102: const char *author)
1.1 joris 1103: {
1104: time_t now;
1105: struct passwd *pw;
1106: struct rcs_delta *ordp, *rdp;
1107:
1108: if (rev == RCS_HEAD_REV) {
1109: if (rf->rf_flags & RCS_CREATE) {
1110: if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
1111: return (-1);
1.62 tobias 1112: rf->rf_head = rev;
1.1 joris 1113: } else {
1114: rev = rcsnum_inc(rf->rf_head);
1115: }
1116: } else {
1117: if ((rdp = rcs_findrev(rf, rev)) != NULL) {
1118: rcs_errno = RCS_ERR_DUPENT;
1119: return (-1);
1120: }
1121: }
1122:
1123: rdp = xcalloc(1, sizeof(*rdp));
1124:
1125: TAILQ_INIT(&(rdp->rd_branches));
1126:
1127: rdp->rd_num = rcsnum_alloc();
1128: rcsnum_cpy(rev, rdp->rd_num, 0);
1129:
1130: rdp->rd_next = rcsnum_alloc();
1131:
1132: if (!(rf->rf_flags & RCS_CREATE)) {
1133: /* next should point to the previous HEAD */
1134: ordp = TAILQ_FIRST(&(rf->rf_delta));
1135: rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
1136: }
1137:
1.57 ray 1138: if (!author && !(author = getlogin())) {
1139: if (!(pw = getpwuid(getuid())))
1140: errx(1, "getpwuid failed");
1141: author = pw->pw_name;
1142: }
1143: rdp->rd_author = xstrdup(author);
1.1 joris 1144: rdp->rd_state = xstrdup(RCS_STATE_EXP);
1145: rdp->rd_log = xstrdup(msg);
1146:
1147: if (date != (time_t)(-1))
1148: now = date;
1149: else
1150: time(&now);
1151: gmtime_r(&now, &(rdp->rd_date));
1152:
1153: TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
1154: rf->rf_ndelta++;
1155:
1156: /* not synced anymore */
1157: rf->rf_flags &= ~RCS_SYNCED;
1158:
1159: return (0);
1160: }
1161:
1162: /*
1163: * rcs_rev_remove()
1164: *
1165: * Remove the revision whose number is <rev> from the RCS file <rf>.
1166: */
1167: int
1168: rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
1169: {
1.12 niallo 1170: char *path_tmp1, *path_tmp2;
1.1 joris 1171: struct rcs_delta *rdp, *prevrdp, *nextrdp;
1.12 niallo 1172: BUF *newdeltatext, *nextbuf, *prevbuf, *newdiff;
1173:
1174: nextrdp = prevrdp = NULL;
1.17 niallo 1175: path_tmp1 = path_tmp2 = NULL;
1.1 joris 1176:
1177: if (rev == RCS_HEAD_REV)
1178: rev = rf->rf_head;
1179:
1180: /* do we actually have that revision? */
1181: if ((rdp = rcs_findrev(rf, rev)) == NULL) {
1182: rcs_errno = RCS_ERR_NOENT;
1183: return (-1);
1184: }
1185:
1186: /*
1187: * This is confusing, the previous delta is next in the TAILQ list.
1188: * the next delta is the previous one in the TAILQ list.
1189: *
1190: * When the HEAD revision got specified, nextrdp will be NULL.
1191: * When the first revision got specified, prevrdp will be NULL.
1192: */
1193: prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
1.58 ray 1194: nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list);
1.1 joris 1195:
1.12 niallo 1196: newdeltatext = prevbuf = nextbuf = NULL;
1.1 joris 1197:
1198: if (prevrdp != NULL) {
1199: if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL)
1200: errx(1, "error getting revision");
1201: }
1202:
1203: if (prevrdp != NULL && nextrdp != NULL) {
1204: if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL)
1205: errx(1, "error getting revision");
1206:
1.60 ray 1207: newdiff = buf_alloc(64);
1.1 joris 1208:
1209: /* calculate new diff */
1.7 xsa 1210: (void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir);
1.59 ray 1211: buf_write_stmp(nextbuf, path_tmp1);
1212: buf_free(nextbuf);
1.1 joris 1213:
1.7 xsa 1214: (void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir);
1.59 ray 1215: buf_write_stmp(prevbuf, path_tmp2);
1216: buf_free(prevbuf);
1.1 joris 1217:
1218: diff_format = D_RCSDIFF;
1.53 ray 1219: if (diffreg(path_tmp1, path_tmp2, newdiff, D_FORCEASCII) == D_ERROR)
1.41 ray 1220: errx(1, "diffreg failed");
1.1 joris 1221:
1.12 niallo 1222: newdeltatext = newdiff;
1.1 joris 1223: } else if (nextrdp == NULL && prevrdp != NULL) {
1.12 niallo 1224: newdeltatext = prevbuf;
1.1 joris 1225: }
1226:
1227: if (newdeltatext != NULL) {
1228: if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
1229: errx(1, "error setting new deltatext");
1230: }
1231:
1232: TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
1233:
1234: /* update pointers */
1235: if (prevrdp != NULL && nextrdp != NULL) {
1236: rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
1237: } else if (prevrdp != NULL) {
1238: if (rcs_head_set(rf, prevrdp->rd_num) < 0)
1239: errx(1, "rcs_head_set failed");
1240: } else if (nextrdp != NULL) {
1241: rcsnum_free(nextrdp->rd_next);
1242: nextrdp->rd_next = rcsnum_alloc();
1243: } else {
1244: rcsnum_free(rf->rf_head);
1245: rf->rf_head = NULL;
1246: }
1247:
1248: rf->rf_ndelta--;
1249: rf->rf_flags &= ~RCS_SYNCED;
1250:
1251: rcs_freedelta(rdp);
1252:
1.7 xsa 1253: if (path_tmp1 != NULL)
1254: xfree(path_tmp1);
1255: if (path_tmp2 != NULL)
1256: xfree(path_tmp2);
1.1 joris 1257:
1258: return (0);
1259: }
1260:
1261: /*
1262: * rcs_findrev()
1263: *
1264: * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
1265: * The revision number is given in <rev>.
1266: *
1267: * If the given revision is a branch number, we translate it into the latest
1268: * revision on the branch.
1269: *
1270: * Returns a pointer to the delta on success, or NULL on failure.
1271: */
1272: struct rcs_delta *
1273: rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
1274: {
1275: u_int cmplen;
1276: struct rcs_delta *rdp;
1277: RCSNUM *brev, *frev;
1278:
1279: /*
1280: * We need to do more parsing if the last revision in the linked list
1281: * is greater than the requested revision.
1282: */
1283: rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1284: if (rdp == NULL ||
1285: rcsnum_cmp(rdp->rd_num, rev, 0) == -1) {
1.68 tobias 1286: if (rcsparse_deltas(rfp, rev))
1287: return (NULL);
1.1 joris 1288: }
1289:
1290: /*
1291: * Translate a branch into the latest revision on the branch itself.
1292: */
1293: if (RCSNUM_ISBRANCH(rev)) {
1294: brev = rcsnum_brtorev(rev);
1295: frev = brev;
1296: for (;;) {
1297: rdp = rcs_findrev(rfp, frev);
1298: if (rdp == NULL)
1299: return (NULL);
1300:
1301: if (rdp->rd_next->rn_len == 0)
1302: break;
1303:
1304: frev = rdp->rd_next;
1305: }
1306:
1307: rcsnum_free(brev);
1308: return (rdp);
1309: }
1310:
1311: cmplen = rev->rn_len;
1312:
1313: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1314: if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0)
1315: return (rdp);
1316: }
1317:
1318: return (NULL);
1319: }
1320:
1321: /*
1322: * rcs_kwexp_set()
1323: *
1324: * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
1325: */
1326: void
1327: rcs_kwexp_set(RCSFILE *file, int mode)
1328: {
1329: int i;
1330: char *tmp, buf[8] = "";
1331:
1332: if (RCS_KWEXP_INVAL(mode))
1333: return;
1334:
1335: i = 0;
1336: if (mode == RCS_KWEXP_NONE)
1337: buf[0] = 'b';
1338: else if (mode == RCS_KWEXP_OLD)
1339: buf[0] = 'o';
1340: else {
1341: if (mode & RCS_KWEXP_NAME)
1342: buf[i++] = 'k';
1343: if (mode & RCS_KWEXP_VAL)
1344: buf[i++] = 'v';
1345: if (mode & RCS_KWEXP_LKR)
1346: buf[i++] = 'l';
1347: }
1348:
1349: tmp = xstrdup(buf);
1350: if (file->rf_expand != NULL)
1351: xfree(file->rf_expand);
1352: file->rf_expand = tmp;
1353: /* not synced anymore */
1354: file->rf_flags &= ~RCS_SYNCED;
1355: }
1356:
1357: /*
1358: * rcs_kwexp_get()
1359: *
1360: * Retrieve the keyword expansion mode to be used for the RCS file <file>.
1361: */
1362: int
1363: rcs_kwexp_get(RCSFILE *file)
1364: {
1.71 tobias 1365: if (file->rf_expand == NULL)
1366: return (RCS_KWEXP_DEFAULT);
1367:
1368: return (rcs_kflag_get(file->rf_expand));
1.1 joris 1369: }
1370:
1371: /*
1372: * rcs_kflag_get()
1373: *
1374: * Get the keyword expansion mode from a set of character flags given in
1375: * <flags> and return the appropriate flag mask. In case of an error, the
1376: * returned mask will have the RCS_KWEXP_ERR bit set to 1.
1377: */
1378: int
1379: rcs_kflag_get(const char *flags)
1380: {
1381: int fl;
1382: size_t len;
1383: const char *fp;
1384:
1.71 tobias 1385: if (flags == NULL || !(len = strlen(flags)))
1386: return (RCS_KWEXP_ERR);
1387:
1.1 joris 1388: fl = 0;
1389: for (fp = flags; *fp != '\0'; fp++) {
1390: if (*fp == 'k')
1391: fl |= RCS_KWEXP_NAME;
1392: else if (*fp == 'v')
1393: fl |= RCS_KWEXP_VAL;
1394: else if (*fp == 'l')
1395: fl |= RCS_KWEXP_LKR;
1396: else if (*fp == 'o') {
1397: if (len != 1)
1398: fl |= RCS_KWEXP_ERR;
1399: fl |= RCS_KWEXP_OLD;
1400: } else if (*fp == 'b') {
1401: if (len != 1)
1402: fl |= RCS_KWEXP_ERR;
1.71 tobias 1403: fl |= RCS_KWEXP_NONE;
1.1 joris 1404: } else /* unknown letter */
1405: fl |= RCS_KWEXP_ERR;
1406: }
1407:
1408: return (fl);
1409: }
1410:
1411: /*
1412: * rcs_freedelta()
1413: *
1414: * Free the contents of a delta structure.
1415: */
1416: static void
1417: rcs_freedelta(struct rcs_delta *rdp)
1418: {
1419: struct rcs_branch *rb;
1420:
1421: if (rdp->rd_num != NULL)
1422: rcsnum_free(rdp->rd_num);
1423: if (rdp->rd_next != NULL)
1424: rcsnum_free(rdp->rd_next);
1425:
1426: if (rdp->rd_author != NULL)
1427: xfree(rdp->rd_author);
1428: if (rdp->rd_locker != NULL)
1429: xfree(rdp->rd_locker);
1430: if (rdp->rd_state != NULL)
1431: xfree(rdp->rd_state);
1432: if (rdp->rd_log != NULL)
1433: xfree(rdp->rd_log);
1434: if (rdp->rd_text != NULL)
1435: xfree(rdp->rd_text);
1436:
1437: while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
1438: TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
1439: rcsnum_free(rb->rb_num);
1440: xfree(rb);
1441: }
1442:
1443: xfree(rdp);
1444: }
1445:
1446: /*
1447: * rcs_strprint()
1448: *
1449: * Output an RCS string <str> of size <slen> to the stream <stream>. Any
1450: * '@' characters are escaped. Otherwise, the string can contain arbitrary
1451: * binary data.
1452: */
1453: static void
1454: rcs_strprint(const u_char *str, size_t slen, FILE *stream)
1455: {
1456: const u_char *ap, *ep, *sp;
1457:
1458: if (slen == 0)
1459: return;
1460:
1461: ep = str + slen - 1;
1462:
1463: for (sp = str; sp <= ep;) {
1464: ap = memchr(sp, '@', ep - sp);
1465: if (ap == NULL)
1466: ap = ep;
1467: (void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
1468:
1469: if (*ap == '@')
1470: putc('@', stream);
1471: sp = ap + 1;
1472: }
1473: }
1474:
1475: /*
1476: * rcs_expand_keywords()
1477: *
1478: * Return expansion any RCS keywords in <data>
1479: *
1480: * On error, return NULL.
1481: */
1.12 niallo 1482: static BUF *
1.77 ! nicm 1483: rcs_expand_keywords(char *rcsfile_in, struct rcs_delta *rdp, BUF *bp, int mode)
1.1 joris 1484: {
1.28 ray 1485: BUF *newbuf;
1.77 ! nicm 1486: u_char *c, *kw, *fin;
! 1487: char buf[256], *tmpf, resolved[MAXPATHLEN], *rcsfile;
! 1488: u_char *line, *line2;
! 1489: u_int i, j;
1.1 joris 1490: int kwtype;
1.77 ! nicm 1491: int found;
1.1 joris 1492: struct tm tb;
1493:
1494: tb = rdp->rd_date;
1495: if (timezone_flag != NULL)
1496: rcs_set_tz(timezone_flag, rdp, &tb);
1497:
1.77 ! nicm 1498: if (realpath(rcsfile_in, resolved) == NULL)
! 1499: rcsfile = rcsfile_in;
! 1500: else
! 1501: rcsfile = resolved;
1.12 niallo 1502:
1.77 ! nicm 1503: newbuf = buf_alloc(buf_len(bp));
1.18 niallo 1504:
1.1 joris 1505: /*
1506: * Keyword formats:
1507: * $Keyword$
1508: * $Keyword: value$
1509: */
1.77 ! nicm 1510: c = buf_get(bp);
! 1511: fin = c + buf_len(bp);
! 1512: /* Copying to newbuf is deferred until the first keyword. */
! 1513: found = 0;
! 1514:
! 1515: while (c < fin) {
! 1516: kw = memchr(c, '$', fin - c);
! 1517: if (kw == NULL)
! 1518: break;
! 1519: ++kw;
! 1520: if (found) {
! 1521: /* Copy everything up to and including the $. */
! 1522: buf_append(newbuf, c, kw - c);
! 1523: }
! 1524: c = kw;
! 1525: /* c points after the $ now. */
! 1526: if (c == fin)
! 1527: break;
! 1528: if (!isalpha(*c)) /* all valid keywords start with a letter */
! 1529: continue;
1.1 joris 1530:
1.77 ! nicm 1531: for (i = 0; i < RCS_NKWORDS; ++i) {
! 1532: size_t kwlen;
1.28 ray 1533:
1.77 ! nicm 1534: kwlen = strlen(rcs_expkw[i].kw_str);
! 1535: /*
! 1536: * kwlen must be less than clen since clen includes
! 1537: * either a terminating `$' or a `:'.
! 1538: */
! 1539: if (c + kwlen < fin &&
! 1540: memcmp(c , rcs_expkw[i].kw_str, kwlen) == 0 &&
! 1541: (c[kwlen] == '$' || c[kwlen] == ':')) {
! 1542: c += kwlen;
! 1543: break;
! 1544: }
! 1545: }
! 1546: if (i == RCS_NKWORDS)
! 1547: continue;
! 1548: kwtype = rcs_expkw[i].kw_type;
! 1549:
! 1550: /*
! 1551: * If the next character is ':' we need to look for an '$'
! 1552: * before the end of the line to be sure it is in fact a
! 1553: * keyword.
! 1554: */
! 1555: if (*c == ':') {
! 1556: for (; c < fin; ++c) {
! 1557: if (*c == '$' || *c == '\n')
1.1 joris 1558: break;
1559: }
1560:
1.77 ! nicm 1561: if (*c != '$') {
! 1562: if (found)
! 1563: buf_append(newbuf, kw, c - kw);
1.1 joris 1564: continue;
1565: }
1.77 ! nicm 1566: }
! 1567: ++c;
1.1 joris 1568:
1.77 ! nicm 1569: if (!found) {
! 1570: found = 1;
! 1571: /* Copy everything up to and including the $. */
! 1572: buf_append(newbuf, buf_get(bp), kw - buf_get(bp));
! 1573: }
! 1574:
! 1575: if (mode & RCS_KWEXP_NAME) {
! 1576: buf_puts(newbuf, rcs_expkw[i].kw_str);
! 1577: if (mode & RCS_KWEXP_VAL)
! 1578: buf_puts(newbuf, ": ");
! 1579: }
1.1 joris 1580:
1.77 ! nicm 1581: /* Order matters because of RCS_KW_ID and RCS_KW_HEADER. */
! 1582: if (mode & RCS_KWEXP_VAL) {
! 1583: if (kwtype & (RCS_KW_RCSFILE|RCS_KW_LOG)) {
! 1584: if ((kwtype & RCS_KW_FULLPATH) ||
! 1585: (tmpf = strrchr(rcsfile, '/')) == NULL)
! 1586: buf_puts(newbuf, rcsfile);
! 1587: else
! 1588: buf_puts(newbuf, tmpf + 1);
! 1589: buf_putc(newbuf, ' ');
1.1 joris 1590: }
1591:
1.77 ! nicm 1592: if (kwtype & RCS_KW_REVISION) {
! 1593: rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
! 1594: buf_puts(newbuf, buf);
! 1595: buf_putc(newbuf, ' ');
! 1596: }
1.1 joris 1597:
1.77 ! nicm 1598: if (kwtype & RCS_KW_DATE) {
! 1599: strftime(buf, sizeof(buf),
! 1600: "%Y/%m/%d %H:%M:%S ", &tb);
! 1601: buf_puts(newbuf, buf);
! 1602: }
1.31 ray 1603:
1.77 ! nicm 1604: if (kwtype & RCS_KW_AUTHOR) {
! 1605: buf_puts(newbuf, rdp->rd_author);
! 1606: buf_putc(newbuf, ' ');
1.1 joris 1607: }
1608:
1.77 ! nicm 1609: if (kwtype & RCS_KW_STATE) {
! 1610: buf_puts(newbuf, rdp->rd_state);
! 1611: buf_putc(newbuf, ' ');
! 1612: }
1.1 joris 1613:
1.77 ! nicm 1614: /* Order does not matter anymore below. */
! 1615: if (kwtype & RCS_KW_SOURCE) {
! 1616: buf_puts(newbuf, rcsfile);
! 1617: buf_putc(newbuf, ' ');
! 1618: }
1.31 ray 1619:
1.77 ! nicm 1620: if (kwtype & RCS_KW_NAME)
! 1621: buf_putc(newbuf, ' ');
1.1 joris 1622:
1.77 ! nicm 1623: if ((kwtype & RCS_KW_LOCKER)) {
! 1624: if (rdp->rd_locker) {
! 1625: buf_puts(newbuf, rdp->rd_locker);
! 1626: buf_putc(newbuf, ' ');
1.1 joris 1627: }
1.77 ! nicm 1628: }
! 1629: }
1.1 joris 1630:
1.77 ! nicm 1631: /* End the expansion. */
! 1632: if (mode & RCS_KWEXP_NAME)
! 1633: buf_putc(newbuf, '$');
1.31 ray 1634:
1.77 ! nicm 1635: if (kwtype & RCS_KW_LOG) {
! 1636: line = memrchr(buf_get(bp), '\n', kw - buf_get(bp) - 1);
! 1637: if (line == NULL)
! 1638: line = buf_get(bp);
! 1639: else
! 1640: ++line;
! 1641: line2 = kw - 1;
! 1642: while (line2 > line && line2[-1] == ' ')
! 1643: --line2;
! 1644:
! 1645: buf_putc(newbuf, '\n');
! 1646: buf_append(newbuf, line, kw - 1 - line);
! 1647: buf_puts(newbuf, "Revision ");
! 1648: rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
! 1649: buf_puts(newbuf, buf);
! 1650: buf_puts(newbuf, " ");
! 1651: strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &tb);
! 1652: buf_puts(newbuf, buf);
! 1653:
! 1654: buf_puts(newbuf, " ");
! 1655: buf_puts(newbuf, rdp->rd_author);
! 1656: buf_putc(newbuf, '\n');
! 1657:
! 1658: for (i = 0; rdp->rd_log[i]; i += j) {
! 1659: j = strcspn(rdp->rd_log + i, "\n");
! 1660: if (j == 0)
! 1661: buf_append(newbuf, line, line2 - line);
! 1662: else
! 1663: buf_append(newbuf, line, kw - 1 - line);
! 1664: if (rdp->rd_log[i + j])
! 1665: ++j;
! 1666: buf_append(newbuf, rdp->rd_log + i, j);
! 1667: }
! 1668: buf_append(newbuf, line, line2 - line);
! 1669: for (j = 0; c + j < fin; ++j) {
! 1670: if (c[j] != ' ')
! 1671: break;
1.1 joris 1672: }
1.77 ! nicm 1673: if (c + j == fin || c[j] == '\n')
! 1674: c += j;
1.1 joris 1675: }
1676: }
1.18 niallo 1677:
1.77 ! nicm 1678: if (found) {
! 1679: buf_append(newbuf, c, fin - c);
! 1680: buf_free(bp);
! 1681: return (newbuf);
! 1682: } else {
! 1683: buf_free(newbuf);
! 1684: return (bp);
! 1685: }
1.1 joris 1686: }
1687:
1688: /*
1689: * rcs_deltatext_set()
1690: *
1691: * Set deltatext for <rev> in RCS file <rfp> to <dtext>
1692: * Returns -1 on error, 0 on success.
1693: */
1694: int
1.12 niallo 1695: rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
1.1 joris 1696: {
1697: size_t len;
1.12 niallo 1698: u_char *dtext;
1.1 joris 1699: struct rcs_delta *rdp;
1700:
1701: /* Write operations require full parsing */
1.68 tobias 1702: if (rcsparse_deltatexts(rfp, NULL))
1703: return (-1);
1.1 joris 1704:
1705: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1706: return (-1);
1707:
1708: if (rdp->rd_text != NULL)
1709: xfree(rdp->rd_text);
1710:
1.59 ray 1711: len = buf_len(bp);
1712: dtext = buf_release(bp);
1.12 niallo 1713: bp = NULL;
1.59 ray 1714:
1.1 joris 1715: if (len != 0) {
1.12 niallo 1716: rdp->rd_text = xmalloc(len);
1.1 joris 1717: rdp->rd_tlen = len;
1.12 niallo 1718: (void)memcpy(rdp->rd_text, dtext, len);
1.1 joris 1719: } else {
1720: rdp->rd_text = NULL;
1721: rdp->rd_tlen = 0;
1722: }
1.70 tobias 1723:
1724: if (dtext != NULL)
1725: xfree(dtext);
1.1 joris 1726:
1727: return (0);
1728: }
1729:
1730: /*
1731: * rcs_rev_setlog()
1732: *
1.51 tobias 1733: * Sets the log message of revision <rev> to <logtext>.
1.1 joris 1734: */
1735: int
1736: rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
1737: {
1738: struct rcs_delta *rdp;
1739:
1740: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1741: return (-1);
1742:
1743: if (rdp->rd_log != NULL)
1744: xfree(rdp->rd_log);
1745:
1746: rdp->rd_log = xstrdup(logtext);
1747: rfp->rf_flags &= ~RCS_SYNCED;
1748: return (0);
1749: }
1750: /*
1751: * rcs_rev_getdate()
1752: *
1753: * Get the date corresponding to a given revision.
1754: * Returns the date on success, -1 on failure.
1755: */
1756: time_t
1757: rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
1758: {
1759: struct rcs_delta *rdp;
1760:
1761: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1762: return (-1);
1763:
1764: return (mktime(&rdp->rd_date));
1765: }
1766:
1767: /*
1768: * rcs_state_set()
1769: *
1770: * Sets the state of revision <rev> to <state>
1771: * NOTE: default state is 'Exp'. States may not contain spaces.
1772: *
1773: * Returns -1 on failure, 0 on success.
1774: */
1775: int
1776: rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
1777: {
1778: struct rcs_delta *rdp;
1779:
1780: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1781: return (-1);
1782:
1783: if (rdp->rd_state != NULL)
1784: xfree(rdp->rd_state);
1785:
1786: rdp->rd_state = xstrdup(state);
1787:
1788: rfp->rf_flags &= ~RCS_SYNCED;
1789:
1790: return (0);
1791: }
1792:
1793: /*
1794: * rcs_state_check()
1795: *
1796: * Check if string <state> is valid.
1797: *
1798: * Returns 0 if the string is valid, -1 otherwise.
1799: */
1800: int
1801: rcs_state_check(const char *state)
1802: {
1.66 tobias 1803: int ret;
1804: const char *cp;
1805:
1806: ret = 0;
1807: cp = state;
1808: if (!isalpha(*cp++))
1.1 joris 1809: return (-1);
1810:
1.66 tobias 1811: for (; *cp != '\0'; cp++)
1812: if (!isgraph(*cp) || (strchr(rcs_state_invch, *cp) != NULL)) {
1813: ret = -1;
1814: break;
1815: }
1816:
1817: return (ret);
1.1 joris 1818: }
1819:
1820: /*
1821: * rcs_kwexp_buf()
1822: *
1823: * Do keyword expansion on a buffer if necessary
1824: *
1825: */
1826: BUF *
1827: rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
1828: {
1829: struct rcs_delta *rdp;
1830: int expmode;
1831:
1832: /*
1833: * Do keyword expansion if required.
1834: */
1.71 tobias 1835: expmode = rcs_kwexp_get(rf);
1.1 joris 1836:
1837: if (!(expmode & RCS_KWEXP_NONE)) {
1838: if ((rdp = rcs_findrev(rf, rev)) == NULL)
1.5 ray 1839: errx(1, "could not fetch revision");
1.12 niallo 1840: return (rcs_expand_keywords(rf->rf_path, rdp, bp, expmode));
1.1 joris 1841: }
1842: return (bp);
1843: }