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