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