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