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