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